OPENDJ-3179: Migrate LDAP Connection Handler to SDK Grizzly transport
Add a new LDAPConnectionHandler based on Grizzly and using the reactive API
22 files added
33 files modified
| | |
| | | <method>java.util.Collection getObjectClassesWithName(java.lang.String)</method> |
| | | <justification>No duplicate names allowed</justification> |
| | | </difference> |
| | | <difference> |
| | | <className>org/forgerock/opendj/ldap/LDAPClientContext</className> |
| | | <differenceType>7002</differenceType> |
| | | <method>void enableConnectionSecurityLayer(org.forgerock.opendj.ldap.ConnectionSecurityLayer)</method> |
| | | <justification>Security layer now handled with enableTLS() and enableSASL()</justification> |
| | | </difference> |
| | | <difference> |
| | | <className>org/forgerock/opendj/ldap/LDAPClientContext</className> |
| | | <differenceType>7012</differenceType> |
| | | <method>void enableSASL(javax.security.sasl.SaslServer)</method> |
| | | <justification>Simplify management of security layer</justification> |
| | | </difference> |
| | | <difference> |
| | | <className>org/forgerock/opendj/ldap/LDAPClientContext</className> |
| | | <differenceType>7004</differenceType> |
| | | <method>void enableTLS(javax.net.ssl.SSLContext, java.lang.String[], java.lang.String[], boolean, boolean)</method> |
| | | <justification>Simplify management of security layer</justification> |
| | | </difference> |
| | | <difference> |
| | | <className>org/forgerock/opendj/ldap/LDAPClientContext</className> |
| | | <differenceType>7012</differenceType> |
| | | <method>void onDisconnect(org.forgerock.opendj.ldap.LDAPClientContext$DisconnectListener)</method> |
| | | <justification>Allows to register connection state listener</justification> |
| | | </difference> |
| | | <difference> |
| | | <className>org/forgerock/opendj/ldap/LDAPListener</className> |
| | | <differenceType>7005</differenceType> |
| | | <method>LDAPListener(java.net.InetSocketAddress, org.forgerock.opendj.ldap.ServerConnectionFactory, org.forgerock.util.Options)</method> |
| | | <to>%regex[LDAPListener\(java\.net\.SocketAddress,\s*org\.forgerock\.opendj\.ldap\.ServerConnectionFactory,\s*org\.forgerock\.util\.Options\)]</to> |
| | | <justification>Accept multiple SocketAddress to bind to</justification> |
| | | </difference> |
| | | <difference> |
| | | <className>org/forgerock/opendj/ldap/spi/LDAPListenerImpl</className> |
| | | <differenceType>7002</differenceType> |
| | | <method>java.net.InetSocketAddress getSocketAddress()</method> |
| | | <justification>Accept multiple SocketAddress to bind to</justification> |
| | | </difference> |
| | | <difference> |
| | | <className>org/forgerock/opendj/ldap/spi/LDAPListenerImpl</className> |
| | | <differenceType>7012</differenceType> |
| | | <method>java.util.Set getSocketAddresses()</method> |
| | | <justification>Accept multiple SocketAddress to bind to</justification> |
| | | </difference> |
| | | <difference> |
| | | <className>org/forgerock/opendj/ldap/LDAPListener</className> |
| | | <differenceType>7002</differenceType> |
| | | <method>java.net.InetSocketAddress getSocketAddress()</method> |
| | | <justification>Accept multiple SocketAddress to bind to</justification> |
| | | </difference> |
| | | <difference> |
| | | <className>org/forgerock/opendj/ldap/LDAPListener</className> |
| | | <differenceType>7012</differenceType> |
| | | <method>java.util.Set getSocketAddresses()</method> |
| | | <justification>Accept multiple SocketAddress to bind to</justification> |
| | | </difference> |
| | | <difference> |
| | | <className>org/forgerock/opendj/ldap/LDAPListener</className> |
| | | <differenceType>7002</differenceType> |
| | | <method>java.net.InetAddress getAddress()</method> |
| | | <justification>Accept multiple SocketAddress to bind to</justification> |
| | | </difference> |
| | | <difference> |
| | | <className>org/forgerock/opendj/ldap/LDAPListener</className> |
| | | <differenceType>7002</differenceType> |
| | | <method>java.lang.String getHostName()</method> |
| | | <justification>Accept multiple SocketAddress to bind to</justification> |
| | | </difference> |
| | | <difference> |
| | | <className>org/forgerock/opendj/ldap/LDAPListener</className> |
| | | <differenceType>7002</differenceType> |
| | | <method>int getPort()</method> |
| | | <justification>Accept multiple SocketAddress to bind to</justification> |
| | | </difference> |
| | | <difference> |
| | | <className>org/forgerock/opendj/ldap/spi/TransportProvider</className> |
| | | <differenceType>7005</differenceType> |
| | | <method>org.forgerock.opendj.ldap.spi.LDAPListenerImpl getLDAPListener(java.net.InetSocketAddress, org.forgerock.opendj.ldap.ServerConnectionFactory, org.forgerock.util.Options)</method> |
| | | <to>%regex[org\.forgerock\.opendj\.ldap\.spi\.LDAPListenerImpl\s*getLDAPListener\(java\.util\.Set,\s*org\.forgerock\.opendj\.ldap\.ServerConnectionFactory,\s*org\.forgerock\.util\.Options\)]</to> |
| | | <justification>Accept multiple SocketAddress to bind to</justification> |
| | | </difference> |
| | | </differences> |
| | |
| | | </dependency> |
| | | |
| | | <dependency> |
| | | <groupId>org.reactivestreams</groupId> |
| | | <artifactId>reactive-streams</artifactId> |
| | | <version>1.0.0</version> |
| | | </dependency> |
| | | |
| | | <dependency> |
| | | <groupId>io.reactivex.rxjava2</groupId> |
| | | <artifactId>rxjava</artifactId> |
| | | <version>2.0.0-RC3</version> |
| | | </dependency> |
| | | |
| | | <dependency> |
| | | <groupId>org.forgerock.commons</groupId> |
| | | <artifactId>i18n-slf4j</artifactId> |
| | | </dependency> |
| | |
| | | com.forgerock.opendj.ldap*, |
| | | org.forgerock.opendj.io, |
| | | org.forgerock.opendj.ldap*, |
| | | org.forgerock.opendj.ldif, |
| | | org.forgerock.opendj.security |
| | | org.forgerock.opendj.ldif |
| | | </Export-Package> |
| | | <Build-Maven>Apache Maven ${maven.version}</Build-Maven> |
| | | <SCM-Revision>${buildRevision}</SCM-Revision> |
| New file |
| | |
| | | /* |
| | | * 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 2016 ForgeRock AS. |
| | | */ |
| | | package com.forgerock.reactive; |
| | | |
| | | /** |
| | | * A functional interface similar to Runnable but allows throwing a checked exception. |
| | | */ |
| | | public interface Action { |
| | | /** |
| | | * Runs the action and optionally throws a checked exception. |
| | | * |
| | | * @throws Exception |
| | | * if the implementation wishes to throw a checked exception |
| | | */ |
| | | void run() throws Exception; |
| | | } |
| New file |
| | |
| | | /* |
| | | * 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 2016 ForgeRock AS. |
| | | */ |
| | | package com.forgerock.reactive; |
| | | |
| | | import org.reactivestreams.Publisher; |
| | | |
| | | /** {@link Completable} is used to communicates a terminated operation which doesn't produce a result. */ |
| | | public interface Completable extends Publisher<Void> { |
| | | |
| | | /** Emitter is used to notify when the operation has been completed, successfully or not. */ |
| | | public interface Emitter { |
| | | /** Notify that this {@link Completable} is now completed. */ |
| | | void complete(); |
| | | |
| | | /** |
| | | * Notify that this {@link Completable} cannot be completed because of an error. |
| | | * |
| | | * @param error |
| | | * The error preventing this {@link Completable} to complete |
| | | */ |
| | | void onError(Throwable error); |
| | | } |
| | | |
| | | /** Adapts the streaming api to a callback one. */ |
| | | public interface OnSubscribe { |
| | | /** |
| | | * Called when the streaming api has been subscribed. |
| | | * |
| | | * @param e |
| | | * The {@link Emitter} to use to communicate the completeness of this {@link Completable} |
| | | */ |
| | | void onSubscribe(Emitter e); |
| | | } |
| | | |
| | | /** |
| | | * Creates a {@link Single} which will emit the specified value when this {@link Completable} complete. |
| | | * |
| | | * @param <V> |
| | | * Type of the value to emit |
| | | * @param value |
| | | * The value to emit on completion |
| | | * @return A new {@link Single} |
| | | */ |
| | | <V> Single<V> toSingle(V value); |
| | | } |
| New file |
| | |
| | | /* |
| | | * 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 2016 ForgeRock AS. |
| | | */ |
| | | package com.forgerock.reactive; |
| | | |
| | | /** |
| | | * A functional interface (callback) that consumes a single value. |
| | | * |
| | | * @param <V> |
| | | * the value type |
| | | */ |
| | | public interface Consumer<V> { |
| | | /** |
| | | * Consume the given value. |
| | | * |
| | | * @param item |
| | | * the value |
| | | * @throws Exception |
| | | * on error |
| | | */ |
| | | void accept(V item) throws Exception; |
| | | } |
| New file |
| | |
| | | /* |
| | | * 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 2016 ForgeRock AS. |
| | | */ |
| | | package com.forgerock.reactive; |
| | | |
| | | import org.forgerock.util.Function; |
| | | import org.forgerock.util.promise.NeverThrowsException; |
| | | |
| | | /** |
| | | * Filters and/or transforms the request and/or response of an exchange. |
| | | * |
| | | * +--------+ +---------+ |
| | | * ---> I1 ---> | | ---> I2 ---> | | |
| | | * | Filter | | Handler | |
| | | * <--- O1 --< | | <--- O2 <--- | | |
| | | * +--------+ +---------+ |
| | | * |
| | | * +--------+ +---------+ +---------+ |
| | | * ---> I1 ---> | One | ---> I2 ---> | AndThen | ---> I3 ---> | | |
| | | * | Filter | | Filter | | Handler | |
| | | * <--- O1 --< | | <--- O2 <--- | | <--- O3 ---< | | |
| | | * +--------+ +---------+ +---------+ |
| | | * |
| | | * @param <C> |
| | | * Context in which the filter will be evaluated |
| | | * @param <I1> |
| | | * Type of the request received by this filter |
| | | * @param <O1> |
| | | * Type of the response answered by this filter |
| | | * @param <I2> |
| | | * Type of the request emitted to the handler/next filter |
| | | * @param <O2> |
| | | * Type of the response emitted by the handler/next filter |
| | | */ |
| | | public abstract class ReactiveFilter<C, I1, O1, I2, O2> { |
| | | |
| | | /** |
| | | * A simple {@link ReactiveHandler} performing simple filtering without type transformation. |
| | | * |
| | | * @param <C> |
| | | * Context in which the filter will be evaluated |
| | | * @param <I> |
| | | * Type of the filtered request |
| | | * @param <O> |
| | | * Type of the filtered response |
| | | */ |
| | | public static abstract class SimpleReactiveFilter<C, I, O> extends ReactiveFilter<C, I, O, I, O> { |
| | | } |
| | | |
| | | /** |
| | | * Optionally concatenate a new filter to this filter. |
| | | * |
| | | * @param condition |
| | | * If true, next will be concatenated to this filter |
| | | * @param next |
| | | * The filter to optionally concatenate with this one |
| | | * @return If condition is true, returns the concatenation of this filter and next. Otherwise, return this filter. |
| | | */ |
| | | public ReactiveFilter<C, I1, O1, I2, O2> andThen(final boolean condition, |
| | | final ReactiveFilter<C, I2, O2, I2, O2> next) { |
| | | if (!condition) { |
| | | return ReactiveFilter.this; |
| | | } |
| | | return new ConcatenatedFilter<C, I1, O1, I2, O2>( |
| | | new Function<ReactiveHandler<C, I2, O2>, ReactiveHandler<C, I1, O1>, NeverThrowsException>() { |
| | | @Override |
| | | public ReactiveHandler<C, I1, O1> apply(ReactiveHandler<C, I2, O2> handler) { |
| | | return andThen(next.andThen(handler)); |
| | | } |
| | | }); |
| | | } |
| | | |
| | | /** |
| | | * Concatenate a transformer filter to this filter. |
| | | * |
| | | * @param <I3> |
| | | * Target type of the request transformed by the filter |
| | | * @param <O3> |
| | | * Source type of the response transformed by the filter |
| | | * @param next |
| | | * The transformer filter to add after this filter. |
| | | * @return A new {@link ReactiveFilter} results of the concatenation of this filter and the transformer filter |
| | | */ |
| | | public <I3, O3> ReactiveFilter<C, I1, O1, I3, O3> andThen(final ReactiveFilter<C, I2, O2, I3, O3> next) { |
| | | return new ConcatenatedFilter<C, I1, O1, I3, O3>( |
| | | new Function<ReactiveHandler<C, I3, O3>, ReactiveHandler<C, I1, O1>, NeverThrowsException>() { |
| | | @Override |
| | | public ReactiveHandler<C, I1, O1> apply(ReactiveHandler<C, I3, O3> handler) { |
| | | return andThen(next.andThen(handler)); |
| | | } |
| | | }); |
| | | } |
| | | |
| | | /** |
| | | * Add a new filtering step after this one. |
| | | * |
| | | * @param next |
| | | * The filter to add after this one |
| | | * @return A new {@link ReactiveFilter} result of the concatenation of this filter and next |
| | | */ |
| | | public ReactiveFilter<C, I1, O1, I2, O2> andThen(final SimpleReactiveFilter<C, I2, O2> next) { |
| | | return new ConcatenatedFilter<C, I1, O1, I2, O2>( |
| | | new Function<ReactiveHandler<C, I2, O2>, ReactiveHandler<C, I1, O1>, NeverThrowsException>() { |
| | | @Override |
| | | public ReactiveHandler<C, I1, O1> apply(ReactiveHandler<C, I2, O2> handler) { |
| | | return andThen(next.andThen(handler)); |
| | | } |
| | | }); |
| | | } |
| | | |
| | | /** |
| | | * Terminate the filter chain with the specified handler. |
| | | * |
| | | * @param handler |
| | | * {@link ReactiveHandler} in charge of processing the request |
| | | * @return A new {@link ReactiveHandler} results of the concatenation of this filter and handler |
| | | */ |
| | | public ReactiveHandler<C, I1, O1> andThen(final ReactiveHandler<C, I2, O2> handler) { |
| | | final ReactiveFilter<C, I1, O1, I2, O2> parent = this; |
| | | return new ReactiveHandler<C, I1, O1>() { |
| | | @Override |
| | | public Single<O1> handle(final C context, final I1 request) throws Exception { |
| | | return parent.filter(context, request, handler); |
| | | } |
| | | }; |
| | | } |
| | | |
| | | /** |
| | | * Perform the request and/or response filtering and/or transformation. |
| | | * |
| | | * @param context |
| | | * Context of the filtering/transformation |
| | | * @param request |
| | | * Request to filter |
| | | * @param next |
| | | * {@link ReactiveHandler} to call once the filtering/transformation is done |
| | | * @return The filtered and/or transformed response |
| | | * @throws Exception |
| | | * If the operation cannot be done |
| | | */ |
| | | public abstract Single<O1> filter(final C context, final I1 request, final ReactiveHandler<C, I2, O2> next) |
| | | throws Exception; |
| | | |
| | | private static final class ConcatenatedFilter<C, I1, O1, I2, O2> extends ReactiveFilter<C, I1, O1, I2, O2> { |
| | | private final Function<ReactiveHandler<C, I2, O2>, ReactiveHandler<C, I1, O1>, NeverThrowsException> converter; |
| | | |
| | | ConcatenatedFilter( |
| | | Function<ReactiveHandler<C, I2, O2>, ReactiveHandler<C, I1, O1>, NeverThrowsException> converter) { |
| | | this.converter = converter; |
| | | } |
| | | |
| | | @Override |
| | | public <I3, O3> ReactiveFilter<C, I1, O1, I3, O3> andThen(final ReactiveFilter<C, I2, O2, I3, O3> nextFilter) { |
| | | return new ConcatenatedFilter<C, I1, O1, I3, O3>( |
| | | new Function<ReactiveHandler<C, I3, O3>, ReactiveHandler<C, I1, O1>, NeverThrowsException>() { |
| | | @Override |
| | | public ReactiveHandler<C, I1, O1> apply(ReactiveHandler<C, I3, O3> handler) { |
| | | return converter.apply(nextFilter.andThen(handler)); |
| | | } |
| | | }); |
| | | } |
| | | |
| | | @Override |
| | | public ReactiveHandler<C, I1, O1> andThen(ReactiveHandler<C, I2, O2> handler) { |
| | | return converter.apply(handler); |
| | | } |
| | | |
| | | @Override |
| | | public Single<O1> filter(C context, I1 request, ReactiveHandler<C, I2, O2> handler) throws Exception { |
| | | return converter.apply(handler).handle(context, request); |
| | | } |
| | | } |
| | | } |
| New file |
| | |
| | | /* |
| | | * 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 2016 ForgeRock AS. |
| | | */ |
| | | package com.forgerock.reactive; |
| | | |
| | | /** |
| | | * Handle the processing of a request in a given context and return the resulting response. |
| | | * |
| | | * @param <CTX> |
| | | * Type of the context |
| | | * @param <REQ> |
| | | * Type of the request to process |
| | | * @param <REP> |
| | | * Type of the response |
| | | */ |
| | | public interface ReactiveHandler<CTX, REQ, REP> { |
| | | /** |
| | | * Process the request given the context. |
| | | * |
| | | * @param context |
| | | * Context in which the request must be processed |
| | | * @param request |
| | | * The response to process |
| | | * @return A {@link Single} response. |
| | | * @throws Exception |
| | | * if the request cannot be processed |
| | | */ |
| | | Single<REP> handle(final CTX context, final REQ request) throws Exception; |
| | | } |
| New file |
| | |
| | | /* |
| | | * 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 2016 ForgeRock AS. |
| | | */ |
| | | package com.forgerock.reactive; |
| | | |
| | | import org.forgerock.util.Function; |
| | | import org.reactivestreams.Publisher; |
| | | import org.reactivestreams.Subscriber; |
| | | |
| | | import io.reactivex.CompletableEmitter; |
| | | import io.reactivex.CompletableOnSubscribe; |
| | | import io.reactivex.Flowable; |
| | | import io.reactivex.SingleEmitter; |
| | | import io.reactivex.SingleOnSubscribe; |
| | | import io.reactivex.SingleSource; |
| | | |
| | | /** |
| | | * {@link Stream} and {@link Single} implementations based on RxJava. |
| | | */ |
| | | public final class RxJavaStreams { |
| | | |
| | | private RxJavaStreams() { |
| | | // Hide |
| | | } |
| | | |
| | | /** |
| | | * Create a new {@link Stream} from the given {@link Publisher}. |
| | | * |
| | | * @param <V> |
| | | * Type of data emitted |
| | | * @param publisher |
| | | * The {@link Publisher} to convert |
| | | * @return A new {@link Stream} |
| | | */ |
| | | public static <V> Stream<V> streamFromPublisher(final Publisher<V> publisher) { |
| | | return new RxJavaStream<>(Flowable.fromPublisher(publisher)); |
| | | } |
| | | |
| | | /** |
| | | * Create a new {@link Stream} composed only of the given value. |
| | | * |
| | | * @param <V> |
| | | * Type of data emitted |
| | | * @param value |
| | | * The value emitted by this stream |
| | | * @return A new {@link Stream} |
| | | */ |
| | | public static <V> Stream<V> streamFrom(final V value) { |
| | | return new RxJavaStream<>(Flowable.just(value)); |
| | | } |
| | | |
| | | /** |
| | | * Create a new {@link Stream} composed only of the given error. |
| | | * |
| | | * @param <V> |
| | | * Type of data emitted |
| | | * @param error |
| | | * The error emitted by this stream |
| | | * @return A new {@link Stream} |
| | | */ |
| | | public static <V> Stream<V> streamError(final Throwable error) { |
| | | return new RxJavaStream<>(Flowable.<V> error(error)); |
| | | } |
| | | |
| | | /** |
| | | * Create a new empty {@link Stream}. |
| | | * |
| | | * @param <V> |
| | | * Type of data emitted |
| | | * @return An empty {@link Stream} |
| | | */ |
| | | public static <V> Stream<V> emptyStream() { |
| | | return new RxJavaStream<>(Flowable.<V> empty()); |
| | | } |
| | | |
| | | /** |
| | | * Create a new {@link Single} from the given {@link Publisher}. |
| | | * |
| | | * @param <V> |
| | | * Type of the datum emitted |
| | | * @param publisher |
| | | * The {@link Publisher} to convert |
| | | * @return A new {@link Stream} |
| | | */ |
| | | public static <V> Single<V> singleFromPublisher(final Publisher<V> publisher) { |
| | | return new RxJavaSingle<>(io.reactivex.Single.fromPublisher(publisher)); |
| | | } |
| | | |
| | | /** |
| | | * Create a new {@link Single} from the given value. |
| | | * |
| | | * @param <V> |
| | | * Type of the datum emitted |
| | | * @param value |
| | | * The value contained by this {@link Single} |
| | | * @return A new {@link Single} |
| | | */ |
| | | public static <V> Single<V> singleFrom(final V value) { |
| | | return new RxJavaSingle<>(io.reactivex.Single.just(value)); |
| | | } |
| | | |
| | | /** |
| | | * Creates a bridge from callback world to {@link Single}. |
| | | * |
| | | * @param <V> |
| | | * Type of the datum emitted |
| | | * @param onSubscribe |
| | | * Action to perform once this {@link Single} has been subscribed to. |
| | | * @return A new {@link Single} |
| | | */ |
| | | public static <V> Single<V> newSingle(final Single.OnSubscribe<V> onSubscribe) { |
| | | return new RxJavaSingle<>(io.reactivex.Single.create(new SingleOnSubscribe<V>() { |
| | | @Override |
| | | public void subscribe(final SingleEmitter<V> e) throws Exception { |
| | | onSubscribe.onSubscribe(new Single.Emitter<V>() { |
| | | @Override |
| | | public void onSuccess(V value) { |
| | | e.onSuccess(value); |
| | | } |
| | | |
| | | @Override |
| | | public void onError(Throwable error) { |
| | | e.onError(error); |
| | | } |
| | | }); |
| | | } |
| | | })); |
| | | } |
| | | |
| | | /** |
| | | * Creates a bridge from callback world to {@link Completable}. |
| | | * |
| | | * @param onSubscribe |
| | | * Action to perform once this {@link Completable} has been subscribed to. |
| | | * @return A new {@link Completable} |
| | | */ |
| | | public static Completable newCompletable(final Completable.OnSubscribe onSubscribe) { |
| | | return new RxJavaCompletable(io.reactivex.Completable.create(new CompletableOnSubscribe() { |
| | | @Override |
| | | public void subscribe(final CompletableEmitter e) throws Exception { |
| | | onSubscribe.onSubscribe(new Completable.Emitter() { |
| | | @Override |
| | | public void complete() { |
| | | e.onComplete(); |
| | | } |
| | | @Override |
| | | public void onError(Throwable t) { |
| | | e.onError(t); |
| | | } |
| | | }); |
| | | } |
| | | })); |
| | | } |
| | | |
| | | private static final class RxJavaStream<V> implements Stream<V> { |
| | | |
| | | private Flowable<V> impl; |
| | | |
| | | private RxJavaStream(final Flowable<V> impl) { |
| | | this.impl = impl; |
| | | } |
| | | |
| | | @Override |
| | | public void subscribe(Subscriber<? super V> s) { |
| | | impl.subscribe(s); |
| | | } |
| | | |
| | | @Override |
| | | public <O> Stream<O> map(final Function<V, O, Exception> function) { |
| | | return new RxJavaStream<>(impl.map(new io.reactivex.functions.Function<V, O>() { |
| | | @Override |
| | | public O apply(V t) throws Exception { |
| | | return function.apply(t); |
| | | } |
| | | })); |
| | | } |
| | | |
| | | @Override |
| | | public <O> Stream<O> flatMap(final Function<? super V, ? extends Publisher<? extends O>, Exception> function, |
| | | int maxConcurrency) { |
| | | return new RxJavaStream<>(impl.flatMap(new io.reactivex.functions.Function<V, Publisher<? extends O>>() { |
| | | @Override |
| | | public Publisher<? extends O> apply(V t) throws Exception { |
| | | return function.apply(t); |
| | | } |
| | | }, maxConcurrency)); |
| | | } |
| | | |
| | | @Override |
| | | public void subscribe(final Consumer<V> onResult, final Consumer<Throwable> onError, final Action onComplete) { |
| | | impl.subscribe(new io.reactivex.functions.Consumer<V>() { |
| | | @Override |
| | | public void accept(V t) throws Exception { |
| | | onResult.accept(t); |
| | | } |
| | | }, new io.reactivex.functions.Consumer<Throwable>() { |
| | | @Override |
| | | public void accept(Throwable t) throws Exception { |
| | | onError.accept(t); |
| | | } |
| | | }, new io.reactivex.functions.Action() { |
| | | @Override |
| | | public void run() throws Exception { |
| | | onComplete.run(); |
| | | } |
| | | }); |
| | | } |
| | | |
| | | @Override |
| | | public Stream<V> onErrorResumeWith(final Function<Throwable, Publisher<V>, Exception> function) { |
| | | return new RxJavaStream<>( |
| | | impl.onErrorResumeNext(new io.reactivex.functions.Function<Throwable, Publisher<? extends V>>() { |
| | | @Override |
| | | public Publisher<? extends V> apply(Throwable t) throws Exception { |
| | | return function.apply(t); |
| | | } |
| | | })); |
| | | } |
| | | |
| | | @Override |
| | | public void subscribe() { |
| | | impl.subscribe(); |
| | | } |
| | | } |
| | | |
| | | private static final class RxJavaSingle<V> implements Single<V> { |
| | | |
| | | private final io.reactivex.Single<V> impl; |
| | | |
| | | private RxJavaSingle(io.reactivex.Single<V> impl) { |
| | | this.impl = impl; |
| | | } |
| | | |
| | | @Override |
| | | public Stream<V> toStream() { |
| | | return new RxJavaStream<>(impl.toFlowable()); |
| | | } |
| | | |
| | | @Override |
| | | public <O> Single<O> map(final Function<V, O, Exception> function) { |
| | | return new RxJavaSingle<>(impl.map(new io.reactivex.functions.Function<V, O>() { |
| | | @Override |
| | | public O apply(V t) throws Exception { |
| | | return function.apply(t); |
| | | } |
| | | })); |
| | | } |
| | | |
| | | @Override |
| | | public void subscribe(Subscriber<? super V> s) { |
| | | impl.toFlowable().subscribe(s); |
| | | } |
| | | |
| | | @Override |
| | | public void subscribe(final Consumer<? super V> resultConsumer, final Consumer<Throwable> errorConsumer) { |
| | | impl.subscribe(new io.reactivex.functions.Consumer<V>() { |
| | | @Override |
| | | public void accept(V t) throws Exception { |
| | | resultConsumer.accept(t); |
| | | } |
| | | }, new io.reactivex.functions.Consumer<Throwable>() { |
| | | @Override |
| | | public void accept(Throwable t) throws Exception { |
| | | errorConsumer.accept(t); |
| | | } |
| | | }); |
| | | } |
| | | |
| | | @Override |
| | | public <O> Single<O> flatMap(final Function<V, Publisher<O>, Exception> function) { |
| | | return new RxJavaSingle<>(impl.flatMap(new io.reactivex.functions.Function<V, SingleSource<O>>() { |
| | | @Override |
| | | public SingleSource<O> apply(V t) throws Exception { |
| | | return io.reactivex.Single.fromPublisher(function.apply(t)); |
| | | } |
| | | })); |
| | | } |
| | | } |
| | | |
| | | private static final class RxJavaCompletable implements Completable { |
| | | |
| | | private final io.reactivex.Completable impl; |
| | | |
| | | RxJavaCompletable(io.reactivex.Completable impl) { |
| | | this.impl = impl; |
| | | } |
| | | |
| | | @Override |
| | | public <V> Single<V> toSingle(V value) { |
| | | return new RxJavaSingle<>(impl.toSingleDefault(value)); |
| | | } |
| | | |
| | | @Override |
| | | public void subscribe(Subscriber<? super Void> s) { |
| | | impl.<Void>toFlowable().subscribe(s); |
| | | } |
| | | } |
| | | } |
| New file |
| | |
| | | /* |
| | | * 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 2016 ForgeRock AS. |
| | | */ |
| | | package com.forgerock.reactive; |
| | | |
| | | import org.forgerock.util.Function; |
| | | import org.reactivestreams.Publisher; |
| | | |
| | | /** |
| | | * Single is a reactive-streams compatible promise. |
| | | * |
| | | * @param <V> |
| | | * Type of the datum emitted |
| | | */ |
| | | public interface Single<V> extends Publisher<V> { |
| | | |
| | | /** Emitter is used to notify when the operation has been completed, successfully or not. */ |
| | | public interface Emitter<V> { |
| | | /** |
| | | * Signal a success value. |
| | | * |
| | | * @param t |
| | | * the value, not null |
| | | */ |
| | | void onSuccess(V t); |
| | | |
| | | /** |
| | | * Signal an exception. |
| | | * |
| | | * @param t |
| | | * the exception, not null |
| | | */ |
| | | void onError(Throwable t); |
| | | } |
| | | |
| | | /** Adapts the streaming api to a callback one. */ |
| | | public interface OnSubscribe<V> { |
| | | /** |
| | | * Called for each SingleObserver that subscribes. |
| | | * @param e the safe emitter instance, never null |
| | | * @throws Exception on error |
| | | */ |
| | | void onSubscribe(Emitter<V> e) throws Exception; |
| | | } |
| | | |
| | | /** |
| | | * Transform this {@link Single} into a {@link Stream}. |
| | | * |
| | | * @return A new {@link Stream} |
| | | */ |
| | | Stream<V> toStream(); |
| | | |
| | | /** |
| | | * Transforms the value emitted by this single. |
| | | * |
| | | * @param <O> |
| | | * Type of the datum emitted after transformation |
| | | * @param function |
| | | * Function to apply to the result of this single. |
| | | * @return A new {@link Single} with the transformed value. |
| | | */ |
| | | <O> Single<O> map(Function<V, O, Exception> function); |
| | | |
| | | /** |
| | | * Transforms asynchronously the value emitted by this single into another. |
| | | * |
| | | * @param <O> |
| | | * Type of the datum emitted after transformation |
| | | * @param function |
| | | * Function to apply to perform the asynchronous transformation |
| | | * @return A new {@link Single} transforming the datum emitted by this {@link Single}. |
| | | */ |
| | | <O> Single<O> flatMap(Function<V, Publisher<O>, Exception> function); |
| | | |
| | | /** |
| | | * Subscribe to the single value emitted by this {@link Single}. |
| | | * |
| | | * @param resultConsumer |
| | | * A {@link Consumer} which will be invoked by the value emitted by this single |
| | | * @param errorConsumer |
| | | * A {@link Consumer} which will be invoked with the error emitted by this single |
| | | */ |
| | | void subscribe(Consumer<? super V> resultConsumer, Consumer<Throwable> errorConsumer); |
| | | } |
| New file |
| | |
| | | /* |
| | | * 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 2016 ForgeRock AS. |
| | | */ |
| | | package com.forgerock.reactive; |
| | | |
| | | import org.forgerock.util.Function; |
| | | import org.reactivestreams.Publisher; |
| | | |
| | | /** |
| | | * Stream is a reactive-streams compliant way to chain operations and transformation on a stream of data. |
| | | * |
| | | * @param <V> |
| | | * Type of data emitted |
| | | */ |
| | | public interface Stream<V> extends Publisher<V> { |
| | | |
| | | /** |
| | | * Transform the data emitted by this stream. |
| | | * |
| | | * @param <O> |
| | | * Type of data emitted after transformation |
| | | * @param function |
| | | * The function to apply to each data of this stream |
| | | * @return a new {@link Stream} emitting transformed data |
| | | */ |
| | | <O> Stream<O> map(Function<V, O, Exception> function); |
| | | |
| | | /** |
| | | * Transform each data emitted by this stream into a new stream of data. All these streams are then merged together. |
| | | * |
| | | * @param <O> |
| | | * Type of data emitted after transformation |
| | | * @param function |
| | | * The function to transform each data into a new stream |
| | | * @param maxConcurrency |
| | | * Maximum number of output stream which can be merged. Once this number is reached, the {@link Stream} |
| | | * will stop requesting data from this stream. |
| | | * @return A new {@link Stream} performing the transformation |
| | | */ |
| | | <O> Stream<O> flatMap(Function<? super V, ? extends Publisher<? extends O>, Exception> function, |
| | | int maxConcurrency); |
| | | |
| | | /** |
| | | * Subscribe to the data emitted by this {@link Stream}. |
| | | * |
| | | * @param onResult |
| | | * The {@link Consumer} invoked for each data of this stream |
| | | * @param onError |
| | | * The {@link Consumer} invoked on error in this stream. |
| | | * @param onComplete |
| | | * The {@link Action} invoked once this {@link Stream} is completed. |
| | | */ |
| | | void subscribe(Consumer<V> onResult, Consumer<Throwable> onError, Action onComplete); |
| | | |
| | | /** |
| | | * When an error occurs in this stream, continue the processing with the new {@link Stream} provided by the |
| | | * function. |
| | | * |
| | | * @param function |
| | | * Generates the stream which must will used to resume operation when this {@link Stream} failed. |
| | | * @return A new {@link Stream} |
| | | */ |
| | | Stream<V> onErrorResumeWith(Function<Throwable, Publisher<V>, Exception> function); |
| | | |
| | | /** |
| | | * Subscribe to this stream and drop all data produced by it. |
| | | */ |
| | | void subscribe(); |
| | | } |
| New file |
| | |
| | | /* |
| | | * 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 2016 ForgeRock AS. |
| | | */ |
| | | |
| | | /** Provides asynchronous stream processing with non-blocking back pressure. */ |
| | | package com.forgerock.reactive; |
| | | |
| | |
| | | /** |
| | | * Common options for LDAP clients and listeners. |
| | | */ |
| | | abstract class CommonLDAPOptions { |
| | | public abstract class CommonLDAPOptions { |
| | | /** |
| | | * Specifies the class loader which will be used to load the |
| | | * {@link org.forgerock.opendj.ldap.spi.TransportProvider TransportProvider}. |
| | |
| | | * information: "Portions Copyright [year] [name of copyright owner]". |
| | | * |
| | | * Copyright 2010 Sun Microsystems, Inc. |
| | | * Portions copyright 2012 ForgeRock AS. |
| | | * Portions copyright 2012-2016 ForgeRock AS. |
| | | */ |
| | | |
| | | package org.forgerock.opendj.ldap; |
| | | |
| | | import java.net.InetSocketAddress; |
| | | |
| | | import javax.net.ssl.SSLContext; |
| | | import javax.net.ssl.SSLEngine; |
| | | import javax.net.ssl.SSLSession; |
| | | import javax.security.sasl.SaslServer; |
| | | |
| | | import org.forgerock.opendj.ldap.responses.ExtendedResult; |
| | | |
| | |
| | | */ |
| | | public interface LDAPClientContext { |
| | | |
| | | /** Listens for disconnection event. */ |
| | | public interface DisconnectListener { |
| | | /** |
| | | * Invoked when the connection has been closed as a result of a client disconnect, a fatal connection error, or |
| | | * a server-side {@link #disconnect}. |
| | | * |
| | | * @param context |
| | | * The {@link LDAPClientContext} which has been disconnected |
| | | * @param resultCode |
| | | * The {@link ResultCode} of the notification sent, or null |
| | | * @param message |
| | | * The message of the notification sent, or null |
| | | */ |
| | | void connectionDisconnected(final LDAPClientContext context, final ResultCode resultCode, final String message); |
| | | } |
| | | |
| | | /** |
| | | * Register a listener which will be notified when this {@link LDAPClientContext} is disconnected. |
| | | * |
| | | * @param listener The {@link DisconnectListener} to register. |
| | | */ |
| | | void onDisconnect(final DisconnectListener listener); |
| | | |
| | | /** |
| | | * Disconnects the client without sending a disconnect notification. |
| | | * <p> |
| | |
| | | * The diagnostic message, which may be empty or {@code null} |
| | | * indicating that none was provided. |
| | | */ |
| | | void disconnect(ResultCode resultCode, String message); |
| | | void disconnect(final ResultCode resultCode, final String message); |
| | | |
| | | /** |
| | | * Returns the {@code InetSocketAddress} associated with the local system. |
| | |
| | | void sendUnsolicitedNotification(ExtendedResult notification); |
| | | |
| | | /** |
| | | * Installs the provided connection security layer to the underlying |
| | | * connection. This may be used to add a SASL integrity and/or |
| | | * confidentiality protection layer after SASL authentication has completed, |
| | | * but could also be used to add other layers such as compression. Multiple |
| | | * layers may be installed. |
| | | * |
| | | * @param layer |
| | | * The negotiated bind context that can be used to encode and |
| | | * decode data on the connection. |
| | | */ |
| | | void enableConnectionSecurityLayer(ConnectionSecurityLayer layer); |
| | | |
| | | /** |
| | | * Installs the TLS/SSL security layer on the underlying connection. The |
| | | * TLS/SSL security layer will be installed beneath any existing connection |
| | | * security layers and can only be installed at most once. |
| | | * |
| | | * @param sslContext |
| | | * The {@code SSLContext} which should be used to secure the |
| | | * @param protocols |
| | | * Names of all the protocols to enable or {@code null} to use |
| | | * the default protocols. |
| | | * @param suites |
| | | * Names of all the suites to enable or {@code null} to use the |
| | | * default cipher suites. |
| | | * @param wantClientAuth |
| | | * Set to {@code true} if client authentication is requested, or |
| | | * {@code false} if no client authentication is desired. |
| | | * @param needClientAuth |
| | | * Set to {@code true} if client authentication is required, or |
| | | * {@code false} if no client authentication is desired. |
| | | * @throws IllegalStateException |
| | | * If the TLS/SSL security layer has already been installed. |
| | | * @param sslEngine |
| | | * The {@code SSLEngine} which should be used to secure the conneciton. |
| | | */ |
| | | void enableTLS(SSLContext sslContext, String[] protocols, String[] suites, |
| | | boolean wantClientAuth, boolean needClientAuth); |
| | | void enableTLS(SSLEngine sslEngine); |
| | | |
| | | /** |
| | | * Installs the SASL security layer on the underlying connection. |
| | | * |
| | | * @param saslServer |
| | | * The {@code SaslServer} which should be used to secure the conneciton. |
| | | */ |
| | | void enableSASL(SaslServer saslServer); |
| | | } |
| | |
| | | package org.forgerock.opendj.ldap; |
| | | |
| | | import static com.forgerock.opendj.ldap.CoreMessages.HBCF_CONNECTION_CLOSED_BY_CLIENT; |
| | | import static com.forgerock.opendj.ldap.CoreMessages.HBCF_HEARTBEAT_FAILED; |
| | | import static com.forgerock.opendj.ldap.CoreMessages.HBCF_HEARTBEAT_TIMEOUT; |
| | | import static com.forgerock.opendj.ldap.CoreMessages.ERR_CONNECTION_UNEXPECTED; |
| | | import static com.forgerock.opendj.ldap.CoreMessages.LDAP_CONNECTION_CONNECT_TIMEOUT; |
| | | import static com.forgerock.opendj.util.StaticUtils.DEFAULT_SCHEDULER; |
| | | import static java.util.concurrent.TimeUnit.*; |
| | |
| | | connectException = newHeartBeatTimeoutError(); |
| | | } else { |
| | | connectException = newLdapException(ResultCode.CLIENT_SIDE_SERVER_DOWN, |
| | | ERR_CONNECTION_UNEXPECTED.get(e), |
| | | HBCF_HEARTBEAT_FAILED.get(), |
| | | e); |
| | | } |
| | | if (promise.tryHandleException(connectException)) { |
| | |
| | | * information: "Portions Copyright [year] [name of copyright owner]". |
| | | * |
| | | * Copyright 2009-2010 Sun Microsystems, Inc. |
| | | * Portions copyright 2012-2015 ForgeRock AS. |
| | | * Portions copyright 2012-2016 ForgeRock AS. |
| | | */ |
| | | package org.forgerock.opendj.ldap; |
| | | |
| | | import java.io.Closeable; |
| | | import java.io.IOException; |
| | | import java.net.InetAddress; |
| | | import java.net.InetSocketAddress; |
| | | import java.net.SocketAddress; |
| | | import java.util.HashSet; |
| | | import java.util.Set; |
| | | |
| | | import org.forgerock.opendj.ldap.spi.LDAPListenerImpl; |
| | | import org.forgerock.opendj.ldap.spi.TransportProvider; |
| | |
| | | public static final Option<Integer> REQUEST_MAX_SIZE_IN_BYTES = Option.withDefault(5 * 1024 * 1024); |
| | | |
| | | /** |
| | | * Specifies the maximum number of concurrent requests per connection. Once this number is reached, |
| | | * back-pressure mechanism will stop reading requests from the connection. |
| | | */ |
| | | public static final Option<Integer> MAX_CONCURRENT_REQUESTS = Option.withDefault(1024); |
| | | |
| | | /** |
| | | * We implement the factory using the pimpl idiom in order have |
| | | * cleaner Javadoc which does not expose implementation methods. |
| | | */ |
| | |
| | | public LDAPListener(final int port, |
| | | final ServerConnectionFactory<LDAPClientContext, Integer> factory, |
| | | final Options options) throws IOException { |
| | | Reject.ifNull(factory, options); |
| | | this.provider = getTransportProvider(options); |
| | | this.impl = provider.getLDAPListener(new InetSocketAddress(port), factory, options); |
| | | this(new InetSocketAddress(port), factory, options); |
| | | } |
| | | |
| | | /** |
| | |
| | | * If {@code address}, {code factory}, or {@code options} was |
| | | * {@code null}. |
| | | */ |
| | | public LDAPListener(final InetSocketAddress address, |
| | | public LDAPListener(final SocketAddress address, |
| | | final ServerConnectionFactory<LDAPClientContext, Integer> factory, |
| | | final Options options) throws IOException { |
| | | Reject.ifNull(address, factory, options); |
| | | this.provider = getTransportProvider(options); |
| | | this.impl = provider.getLDAPListener(address, factory, options); |
| | | final Set<SocketAddress> addresses = new HashSet<>(); |
| | | addresses.add(address); |
| | | this.impl = provider.getLDAPListener(addresses, factory, options); |
| | | } |
| | | |
| | | /** |
| | |
| | | public LDAPListener(final String host, final int port, |
| | | final ServerConnectionFactory<LDAPClientContext, Integer> factory, |
| | | final Options options) throws IOException { |
| | | Reject.ifNull(host, factory, options); |
| | | final InetSocketAddress address = new InetSocketAddress(host, port); |
| | | this.provider = getTransportProvider(options); |
| | | this.impl = provider.getLDAPListener(address, factory, options); |
| | | this(new InetSocketAddress(host, port), factory, options); |
| | | } |
| | | |
| | | /** Closes this LDAP connection listener. */ |
| | |
| | | } |
| | | |
| | | /** |
| | | * Returns the {@code InetAddress} that this LDAP listener is listening on. |
| | | * |
| | | * @return The {@code InetAddress} that this LDAP listener is listening on. |
| | | */ |
| | | public InetAddress getAddress() { |
| | | return getSocketAddress().getAddress(); |
| | | } |
| | | |
| | | /** |
| | | * Returns the host name that this LDAP listener is listening on. The |
| | | * returned host name is the same host name that was provided during |
| | | * construction and may be an IP address. More specifically, this method |
| | | * will not perform a reverse DNS lookup. |
| | | * |
| | | * @return The host name that this LDAP listener is listening on. |
| | | */ |
| | | public String getHostName() { |
| | | return Connections.getHostString(getSocketAddress()); |
| | | } |
| | | |
| | | /** |
| | | * Returns the port that this LDAP listener is listening on. |
| | | * |
| | | * @return The port that this LDAP listener is listening on. |
| | | */ |
| | | public int getPort() { |
| | | return getSocketAddress().getPort(); |
| | | } |
| | | |
| | | /** |
| | | * Returns the address that this LDAP listener is listening on. |
| | | * |
| | | * @return The address that this LDAP listener is listening on. |
| | | */ |
| | | public InetSocketAddress getSocketAddress() { |
| | | return impl.getSocketAddress(); |
| | | public Set<? extends SocketAddress> getSocketAddresses() { |
| | | return impl.getSocketAddresses(); |
| | | } |
| | | |
| | | /** |
| | |
| | | * Header, with the fields enclosed by brackets [] replaced by your own identifying |
| | | * information: "Portions Copyright [year] [name of copyright owner]". |
| | | * |
| | | * Copyright 2013-2014 ForgeRock AS. |
| | | * Copyright 2013-2016 ForgeRock AS. |
| | | */ |
| | | package org.forgerock.opendj.ldap.spi; |
| | | |
| | | import java.io.Closeable; |
| | | import java.net.InetSocketAddress; |
| | | import java.net.SocketAddress; |
| | | import java.util.Set; |
| | | |
| | | /** |
| | | * Interface for all classes that actually implement {@code LDAPListener}. |
| | |
| | | public interface LDAPListenerImpl extends Closeable { |
| | | |
| | | /** |
| | | * Returns the address that this LDAP listener is listening on. |
| | | * Returns the addresses that this LDAP listener is listening on. |
| | | * |
| | | * @return The address that this LDAP listener is listening on. |
| | | * @return The addresses that this LDAP listener is listening on. |
| | | */ |
| | | InetSocketAddress getSocketAddress(); |
| | | Set<? extends SocketAddress> getSocketAddresses(); |
| | | |
| | | /** |
| | | * Closes this stream and releases any system resources associated |
| New file |
| | |
| | | /* |
| | | * 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 2016 ForgeRock AS. |
| | | */ |
| | | package org.forgerock.opendj.ldap.spi; |
| | | |
| | | import org.forgerock.opendj.io.ASN1Reader; |
| | | import org.forgerock.opendj.ldap.DN; |
| | | import org.forgerock.opendj.ldap.responses.Response; |
| | | import org.forgerock.opendj.ldap.schema.Schema; |
| | | |
| | | /** |
| | | * Contains statics methods to create ldap messages. |
| | | */ |
| | | public final class LdapMessages { |
| | | |
| | | private LdapMessages() { |
| | | // Nothing to so |
| | | } |
| | | |
| | | /** |
| | | * Creates a new {@link LdapRawMessage} containing a partially decoded LDAP message. |
| | | * |
| | | * @param messageType |
| | | * Operation code of the message |
| | | * @param messageId |
| | | * Unique identifier of this message |
| | | * @param protocolVersion |
| | | * Protocol version to use (only for Bind requests) |
| | | * @param rawDn |
| | | * Unparsed name contained in the request (or null if DN is not applicable) |
| | | * @param schema |
| | | * Schema to use to parse the DN |
| | | * @param reader |
| | | * An {@link ASN1Reader} containing the full encoded ldap message packet. |
| | | * @return A new {@link LdapRawMessage} |
| | | */ |
| | | public static LdapRawMessage newRawMessage(final byte messageType, final int messageId, final int protocolVersion, |
| | | final String rawDn, final Schema schema, final ASN1Reader reader) { |
| | | return new LdapRawMessage(messageType, messageId, protocolVersion, rawDn, schema, reader); |
| | | } |
| | | |
| | | /** |
| | | * Creates a new {@link LdapResponseMessage}, adding low-level ldap protocol specific informations to a |
| | | * {@link Response}. |
| | | * |
| | | * @param messageType |
| | | * Operation code of the response |
| | | * @param messageId |
| | | * Message identifier of the request owning this response. |
| | | * @param response |
| | | * The response |
| | | * @return A new {@link LdapResponseMessage} |
| | | */ |
| | | public static LdapResponseMessage newResponseMessage(final byte messageType, final int messageId, |
| | | final Response response) { |
| | | return new LdapResponseMessage(messageType, messageId, response); |
| | | } |
| | | |
| | | /** |
| | | * Represents an encoded LDAP message with it's envelope. |
| | | */ |
| | | public static final class LdapRawMessage extends LdapMessageEnvelope<ASN1Reader> { |
| | | private final String rawDn; |
| | | private final Schema schema; |
| | | private final int version; |
| | | private DN dn; |
| | | |
| | | private LdapRawMessage(final byte messageType, final int messageId, final int version, final String rawDn, |
| | | final Schema schema, final ASN1Reader content) { |
| | | super(messageType, messageId, content); |
| | | this.version = version; |
| | | this.rawDn = rawDn; |
| | | this.schema = schema; |
| | | } |
| | | |
| | | /** |
| | | * Get the Ldap version requested by this message (Bind request only). |
| | | * |
| | | * @return The ldap protocol version |
| | | */ |
| | | public int getVersion() { |
| | | return version; |
| | | } |
| | | |
| | | /** |
| | | * Get the raw form of the {@link DN} contained in the message (or null if the message doesn't contains a DN). |
| | | * |
| | | * @return The {@link DN} contained in request, or null if the message doesn't contains a DN. |
| | | */ |
| | | public String getRawDn() { |
| | | return rawDn; |
| | | } |
| | | |
| | | /** |
| | | * Get the decoded form of the {@link DN} contained in the message (or null if the message doesn't contains a |
| | | * DN). |
| | | * |
| | | * @return The decoded {@link DN} contained in the request, or null if the message doesn't contains a DN. |
| | | */ |
| | | public DN getDn() { |
| | | if (rawDn == null) { |
| | | return null; |
| | | } |
| | | if (dn != null) { |
| | | return dn; |
| | | } |
| | | dn = DN.valueOf(rawDn.toString(), schema); |
| | | return dn; |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * Represents a {@link Response} and its envelope. |
| | | */ |
| | | public static final class LdapResponseMessage extends LdapMessageEnvelope<Response> { |
| | | private LdapResponseMessage(final byte messageType, final int messageId, final Response content) { |
| | | super(messageType, messageId, content); |
| | | } |
| | | } |
| | | |
| | | private static abstract class LdapMessageEnvelope<T> { |
| | | |
| | | private final T content; |
| | | private final int messageId; |
| | | private final byte messageType; |
| | | |
| | | public LdapMessageEnvelope(final byte messageType, final int messageId, final T content) { |
| | | this.messageType = messageType; |
| | | this.messageId = messageId; |
| | | this.content = content; |
| | | } |
| | | |
| | | public byte getMessageType() { |
| | | return messageType; |
| | | } |
| | | |
| | | public int getMessageId() { |
| | | return messageId; |
| | | } |
| | | |
| | | public T getContent() { |
| | | return content; |
| | | } |
| | | } |
| | | } |
| | |
| | | * Header, with the fields enclosed by brackets [] replaced by your own identifying |
| | | * information: "Portions Copyright [year] [name of copyright owner]". |
| | | * |
| | | * Copyright 2013-2015 ForgeRock AS. |
| | | * Copyright 2013-2016 ForgeRock AS. |
| | | */ |
| | | package org.forgerock.opendj.ldap.spi; |
| | | |
| | | import java.io.IOException; |
| | | import java.net.InetSocketAddress; |
| | | import java.net.SocketAddress; |
| | | import java.util.Set; |
| | | |
| | | import org.forgerock.opendj.ldap.LDAPClientContext; |
| | | import org.forgerock.opendj.ldap.ServerConnectionFactory; |
| | |
| | | /** |
| | | * Returns an implementation of {@code LDAPListener}. |
| | | * |
| | | * @param address |
| | | * The address to listen on. |
| | | * @param addresses |
| | | * The addresses to listen on. |
| | | * @param factory |
| | | * The server connection factory which will be used to create |
| | | * server connections. |
| | |
| | | * If an error occurred while trying to listen on the provided |
| | | * address. |
| | | */ |
| | | LDAPListenerImpl getLDAPListener(InetSocketAddress address, |
| | | LDAPListenerImpl getLDAPListener(Set<? extends SocketAddress> addresses, |
| | | ServerConnectionFactory<LDAPClientContext, Integer> factory, Options options) |
| | | throws IOException; |
| | | |
| | | } |
| | |
| | | * Header, with the fields enclosed by brackets [] replaced by your own identifying |
| | | * information: "Portions Copyright [year] [name of copyright owner]". |
| | | * |
| | | * Copyright 2013-2015 ForgeRock AS. |
| | | * Copyright 2013-2016 ForgeRock AS. |
| | | */ |
| | | package org.forgerock.opendj.io; |
| | | |
| | |
| | | throws Exception { |
| | | LDAPWriter<? extends ASN1Writer> writer = getLDAPWriter(); |
| | | writing.perform(writer); |
| | | LDAPReader<? extends ASN1Reader> reader = getLDAPReader(); |
| | | transferFromWriterToReader(writer, reader); |
| | | LDAPReader<? extends ASN1Reader> reader = getLDAPReader(writer); |
| | | reader.readMessage(messageHandler); |
| | | } |
| | | |
| | |
| | | protected abstract LDAPReader<? extends ASN1Reader> getLDAPReader(); |
| | | |
| | | /** |
| | | * Transfer raw data from writer to the reader. |
| | | * Create a reader reading data contained in the writer. |
| | | */ |
| | | protected abstract void transferFromWriterToReader(LDAPWriter<? extends ASN1Writer> writer, |
| | | LDAPReader<? extends ASN1Reader> reader); |
| | | protected abstract LDAPReader<? extends ASN1Reader> getLDAPReader(LDAPWriter<? extends ASN1Writer> writer); |
| | | } |
| | |
| | | |
| | | package org.forgerock.opendj.ldap; |
| | | |
| | | import static org.forgerock.opendj.ldap.LDAPListener.CONNECT_MAX_BACKLOG; |
| | | import static org.forgerock.opendj.ldap.LdapException.newLdapException; |
| | | import static org.forgerock.opendj.ldap.TestCaseUtils.findFreeSocketAddress; |
| | | |
| | | import java.io.IOException; |
| | | import java.net.InetSocketAddress; |
| | | import java.util.HashMap; |
| | |
| | | import java.util.concurrent.atomic.AtomicBoolean; |
| | | |
| | | import javax.net.ssl.SSLContext; |
| | | import javax.net.ssl.SSLEngine; |
| | | import javax.security.auth.callback.Callback; |
| | | import javax.security.auth.callback.CallbackHandler; |
| | | import javax.security.auth.callback.NameCallback; |
| | |
| | | import org.forgerock.opendj.ldap.responses.Responses; |
| | | import org.forgerock.opendj.ldap.responses.Result; |
| | | import org.forgerock.opendj.ldap.responses.SearchResultEntry; |
| | | import org.forgerock.util.Options; |
| | | |
| | | import com.forgerock.opendj.ldap.controls.AccountUsabilityRequestControl; |
| | | import com.forgerock.opendj.ldap.controls.AccountUsabilityResponseControl; |
| | | import org.forgerock.util.Options; |
| | | |
| | | import static org.forgerock.opendj.ldap.LdapException.*; |
| | | import static org.forgerock.opendj.ldap.TestCaseUtils.*; |
| | | import static org.forgerock.opendj.ldap.LDAPListener.*; |
| | | |
| | | /** |
| | | * A simple ldap server that manages 1000 entries and used for running |
| | |
| | | props.put(Sasl.QOP, "auth-conf,auth-int,auth"); |
| | | saslServer = |
| | | Sasl.createSaslServer(saslMech, "ldap", |
| | | listener.getHostName(), props, |
| | | ((InetSocketAddress) listener.getSocketAddresses().iterator().next()) |
| | | .getHostName(), |
| | | props, |
| | | new CallbackHandler() { |
| | | @Override |
| | | public void handle(Callback[] callbacks) |
| | |
| | | |
| | | String qop = (String) saslServer.getNegotiatedProperty(Sasl.QOP); |
| | | if ("auth-int".equalsIgnoreCase(qop) || "auth-conf".equalsIgnoreCase(qop)) { |
| | | ConnectionSecurityLayer csl = new ConnectionSecurityLayer() { |
| | | @Override |
| | | public void dispose() { |
| | | try { |
| | | saslServer.dispose(); |
| | | } catch (SaslException e) { |
| | | e.printStackTrace(); |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | public byte[] unwrap(byte[] incoming, int offset, int len) throws LdapException { |
| | | try { |
| | | return saslServer.unwrap(incoming, offset, len); |
| | | } catch (SaslException e) { |
| | | throw newLdapException( |
| | | Responses.newResult(ResultCode.OPERATIONS_ERROR).setCause(e)); |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | public byte[] wrap(byte[] outgoing, int offset, int len) throws LdapException { |
| | | try { |
| | | return saslServer.wrap(outgoing, offset, len); |
| | | } catch (SaslException e) { |
| | | throw newLdapException( |
| | | Responses.newResult(ResultCode.OPERATIONS_ERROR).setCause(e)); |
| | | } |
| | | } |
| | | }; |
| | | |
| | | clientContext.enableConnectionSecurityLayer(csl); |
| | | clientContext.enableSASL(saslServer); |
| | | } |
| | | |
| | | } else { |
| | |
| | | if (request.getOID().equals(StartTLSExtendedRequest.OID)) { |
| | | final R result = request.getResultDecoder().newExtendedErrorResult(ResultCode.SUCCESS, "", ""); |
| | | resultHandler.handleResult(result); |
| | | clientContext.enableTLS(sslContext, null, sslContext.getSocketFactory() |
| | | .getSupportedCipherSuites(), false, false); |
| | | final SSLEngine engine = sslContext.createSSLEngine(); |
| | | engine.setEnabledCipherSuites(sslContext.getServerSocketFactory().getSupportedCipherSuites()); |
| | | engine.setNeedClientAuth(false); |
| | | engine.setUseClientMode(false); |
| | | clientContext.enableTLS(engine); |
| | | } |
| | | } |
| | | |
| | |
| | | if (!isRunning) { |
| | | throw new IllegalStateException("Server is not running"); |
| | | } |
| | | return listener.getSocketAddress(); |
| | | return (InetSocketAddress) listener.getSocketAddresses().iterator().next(); |
| | | } |
| | | } |
| | |
| | | * Header, with the fields enclosed by brackets [] replaced by your own identifying |
| | | * information: "Portions Copyright [year] [name of copyright owner]". |
| | | * |
| | | * Copyright 2013-2015 ForgeRock AS. |
| | | * Copyright 2013-2016 ForgeRock AS. |
| | | */ |
| | | |
| | | package org.forgerock.opendj.ldap.spi; |
| | | |
| | | import java.io.IOException; |
| | | import java.net.InetSocketAddress; |
| | | import java.net.SocketAddress; |
| | | import java.util.Set; |
| | | |
| | | import org.forgerock.opendj.ldap.LDAPClientContext; |
| | | import org.forgerock.opendj.ldap.ServerConnectionFactory; |
| | |
| | | */ |
| | | public final class BasicLDAPListener implements LDAPListenerImpl { |
| | | private final ServerConnectionFactory<LDAPClientContext, Integer> connectionFactory; |
| | | private final InetSocketAddress socketAddress; |
| | | private final Set<? extends SocketAddress> socketAddresses; |
| | | |
| | | /** |
| | | * Creates a new LDAP listener implementation which does nothing. |
| | |
| | | * @throws IOException |
| | | * is never thrown with this do-nothing implementation |
| | | */ |
| | | public BasicLDAPListener(final InetSocketAddress address, |
| | | public BasicLDAPListener(final Set<? extends SocketAddress> addresses, |
| | | final ServerConnectionFactory<LDAPClientContext, Integer> factory, |
| | | final Options options) throws IOException { |
| | | this.connectionFactory = factory; |
| | | this.socketAddress = address; |
| | | this.socketAddresses = addresses; |
| | | } |
| | | |
| | | @Override |
| | |
| | | } |
| | | |
| | | @Override |
| | | public InetSocketAddress getSocketAddress() { |
| | | return socketAddress; |
| | | public Set<? extends SocketAddress> getSocketAddresses() { |
| | | return socketAddresses; |
| | | } |
| | | |
| | | @Override |
| | | public String toString() { |
| | | final StringBuilder builder = new StringBuilder(); |
| | | builder.append("LDAPListener("); |
| | | builder.append(getSocketAddress()); |
| | | builder.append(getSocketAddresses()); |
| | | builder.append(')'); |
| | | return builder.toString(); |
| | | } |
| | |
| | | package org.forgerock.opendj.ldap.spi; |
| | | |
| | | import java.io.IOException; |
| | | import java.net.InetSocketAddress; |
| | | import java.net.SocketAddress; |
| | | import java.util.Set; |
| | | |
| | | import org.forgerock.opendj.ldap.LDAPClientContext; |
| | | import org.forgerock.opendj.ldap.ServerConnectionFactory; |
| | |
| | | } |
| | | |
| | | @Override |
| | | public LDAPListenerImpl getLDAPListener( |
| | | InetSocketAddress address, ServerConnectionFactory<LDAPClientContext, Integer> factory, Options options) |
| | | throws IOException { |
| | | return new BasicLDAPListener(address, factory, options); |
| | | public LDAPListenerImpl getLDAPListener(Set<? extends SocketAddress> addresses, |
| | | ServerConnectionFactory<LDAPClientContext, Integer> factory, Options options) throws IOException { |
| | | return new BasicLDAPListener(addresses, factory, options); |
| | | } |
| | | |
| | | @Override |
| | |
| | | <artifactId>forgerock-build-tools</artifactId> |
| | | <scope>test</scope> |
| | | </dependency> |
| | | <dependency> |
| | | <groupId>org.forgerock.http</groupId> |
| | | <artifactId>chf-http-core</artifactId> |
| | | </dependency> |
| | | </dependencies> |
| | | |
| | | |
| | |
| | | * Header, with the fields enclosed by brackets [] replaced by your own identifying |
| | | * information: "Portions Copyright [year] [name of copyright owner]". |
| | | * |
| | | * Copyright 2013-2014 ForgeRock AS. |
| | | * Copyright 2013-2016 ForgeRock AS. |
| | | */ |
| | | package com.forgerock.opendj.grizzly; |
| | | |
| | | import static com.forgerock.reactive.RxJavaStreams.*; |
| | | |
| | | import java.io.IOException; |
| | | import java.net.InetSocketAddress; |
| | | import java.net.SocketAddress; |
| | | import java.util.Set; |
| | | |
| | | import org.forgerock.opendj.grizzly.GrizzlyLDAPConnectionFactory; |
| | | import org.forgerock.opendj.grizzly.GrizzlyLDAPListener; |
| | | import org.forgerock.opendj.io.ASN1Reader; |
| | | import org.forgerock.opendj.io.AbstractLDAPMessageHandler; |
| | | import org.forgerock.opendj.io.LDAP; |
| | | import org.forgerock.opendj.io.LDAPReader; |
| | | import org.forgerock.opendj.ldap.CommonLDAPOptions; |
| | | import org.forgerock.opendj.ldap.DecodeException; |
| | | import org.forgerock.opendj.ldap.LDAPClientContext; |
| | | import org.forgerock.opendj.ldap.LDAPClientContext.DisconnectListener; |
| | | import org.forgerock.opendj.ldap.LdapException; |
| | | import org.forgerock.opendj.ldap.ResultCode; |
| | | import org.forgerock.opendj.ldap.ServerConnection; |
| | | import org.forgerock.opendj.ldap.ServerConnectionFactory; |
| | | import org.forgerock.opendj.ldap.requests.AbandonRequest; |
| | | import org.forgerock.opendj.ldap.requests.AddRequest; |
| | | import org.forgerock.opendj.ldap.requests.CompareRequest; |
| | | import org.forgerock.opendj.ldap.requests.DeleteRequest; |
| | | import org.forgerock.opendj.ldap.requests.ExtendedRequest; |
| | | import org.forgerock.opendj.ldap.requests.GenericBindRequest; |
| | | import org.forgerock.opendj.ldap.requests.ModifyDNRequest; |
| | | import org.forgerock.opendj.ldap.requests.ModifyRequest; |
| | | import org.forgerock.opendj.ldap.requests.SearchRequest; |
| | | import org.forgerock.opendj.ldap.requests.UnbindRequest; |
| | | import org.forgerock.opendj.ldap.responses.ExtendedResult; |
| | | import org.forgerock.opendj.ldap.responses.Response; |
| | | import org.forgerock.opendj.ldap.spi.LDAPConnectionFactoryImpl; |
| | | import org.forgerock.opendj.ldap.spi.LDAPListenerImpl; |
| | | import org.forgerock.opendj.ldap.spi.LdapMessages.LdapRawMessage; |
| | | import org.forgerock.opendj.ldap.spi.TransportProvider; |
| | | import org.forgerock.util.Function; |
| | | import org.forgerock.util.Options; |
| | | |
| | | import com.forgerock.reactive.ReactiveHandler; |
| | | import com.forgerock.reactive.Single; |
| | | import com.forgerock.reactive.Stream; |
| | | |
| | | import io.reactivex.Flowable; |
| | | import io.reactivex.FlowableEmitter; |
| | | import io.reactivex.FlowableEmitter.BackpressureMode; |
| | | import io.reactivex.FlowableOnSubscribe; |
| | | |
| | | /** |
| | | * Grizzly transport provider implementation. |
| | | */ |
| | | public class GrizzlyTransportProvider implements TransportProvider { |
| | | public final class GrizzlyTransportProvider implements TransportProvider { |
| | | |
| | | @Override |
| | | public LDAPConnectionFactoryImpl getLDAPConnectionFactory(String host, int port, Options options) { |
| | |
| | | } |
| | | |
| | | @Override |
| | | public LDAPListenerImpl getLDAPListener(InetSocketAddress address, |
| | | ServerConnectionFactory<LDAPClientContext, Integer> factory, Options options) |
| | | public LDAPListenerImpl getLDAPListener(final Set<? extends SocketAddress> addresses, |
| | | final ServerConnectionFactory<LDAPClientContext, Integer> factory, final Options options) |
| | | throws IOException { |
| | | return new GrizzlyLDAPListener(address, factory, options); |
| | | return new GrizzlyLDAPListener(addresses, options, |
| | | new Function<LDAPClientContext, |
| | | ReactiveHandler<LDAPClientContext, LdapRawMessage, Stream<Response>>, |
| | | LdapException>() { |
| | | @Override |
| | | public ReactiveHandler<LDAPClientContext, LdapRawMessage, Stream<Response>> apply( |
| | | final LDAPClientContext clientContext) throws LdapException { |
| | | return newHandler(clientContext, factory, options); |
| | | } |
| | | }); |
| | | } |
| | | |
| | | @Override |
| | |
| | | return "Grizzly"; |
| | | } |
| | | |
| | | private ReactiveHandler<LDAPClientContext, LdapRawMessage, Stream<Response>> newHandler( |
| | | final LDAPClientContext clientContext, final ServerConnectionFactory<LDAPClientContext, Integer> factory, |
| | | final Options options) throws LdapException { |
| | | final ServerConnection<Integer> serverConnection = factory.handleAccept(clientContext); |
| | | final ServerConnectionAdaptor<Integer> adapter = new ServerConnectionAdaptor<>(serverConnection); |
| | | clientContext.onDisconnect(new DisconnectListener() { |
| | | @Override |
| | | public void connectionDisconnected(LDAPClientContext context, ResultCode resultCode, String message) { |
| | | serverConnection.handleConnectionDisconnected(resultCode, message); |
| | | } |
| | | }); |
| | | return new ReactiveHandler<LDAPClientContext, LdapRawMessage, Stream<Response>>() { |
| | | @Override |
| | | public Single<Stream<Response>> handle(final LDAPClientContext context, |
| | | final LdapRawMessage rawRequest) throws Exception { |
| | | final LDAPReader<ASN1Reader> reader = LDAP.getReader(rawRequest.getContent(), |
| | | options.get(CommonLDAPOptions.LDAP_DECODE_OPTIONS)); |
| | | return singleFrom(streamFromPublisher(Flowable.create(new FlowableOnSubscribe<Response>() { |
| | | @Override |
| | | public void subscribe(final FlowableEmitter<Response> emitter) throws Exception { |
| | | reader.readMessage(new AbstractLDAPMessageHandler() { |
| | | @Override |
| | | public void abandonRequest(int messageID, AbandonRequest request) |
| | | throws DecodeException, IOException { |
| | | adapter.handleAbandon(messageID, request, emitter); |
| | | } |
| | | |
| | | @Override |
| | | public void addRequest(int messageID, AddRequest request) |
| | | throws DecodeException, IOException { |
| | | adapter.handleAdd(messageID, request, emitter); |
| | | } |
| | | |
| | | @Override |
| | | public void deleteRequest(final int messageID, final DeleteRequest request) |
| | | throws DecodeException, IOException { |
| | | adapter.handleDelete(messageID, request, emitter); |
| | | } |
| | | |
| | | @Override |
| | | public void bindRequest(int messageID, int version, GenericBindRequest request) |
| | | throws DecodeException, IOException { |
| | | adapter.handleBind(messageID, version, request, emitter); |
| | | } |
| | | |
| | | @Override |
| | | public void compareRequest(int messageID, CompareRequest request) |
| | | throws DecodeException, IOException { |
| | | adapter.handleCompare(messageID, request, emitter); |
| | | } |
| | | |
| | | @Override |
| | | public <R extends ExtendedResult> void extendedRequest(int messageID, |
| | | ExtendedRequest<R> request) throws DecodeException, IOException { |
| | | adapter.handleExtendedRequest(messageID, request, emitter); |
| | | } |
| | | |
| | | @Override |
| | | public void modifyDNRequest(int messageID, ModifyDNRequest request) |
| | | throws DecodeException, IOException { |
| | | adapter.handleModifyDN(messageID, request, emitter); |
| | | } |
| | | |
| | | @Override |
| | | public void modifyRequest(int messageID, ModifyRequest request) |
| | | throws DecodeException, IOException { |
| | | adapter.handleModify(messageID, request, emitter); |
| | | } |
| | | |
| | | @Override |
| | | public void searchRequest(int messageID, SearchRequest request) |
| | | throws DecodeException, IOException { |
| | | adapter.handleSearch(messageID, request, emitter); |
| | | } |
| | | |
| | | @Override |
| | | public void unbindRequest(int messageID, UnbindRequest request) |
| | | throws DecodeException, IOException { |
| | | serverConnection.handleConnectionClosed(messageID, request); |
| | | } |
| | | }); |
| | | emitter.onComplete(); |
| | | } |
| | | }, BackpressureMode.ERROR))); |
| | | } |
| | | }; |
| | | } |
| | | } |
| New file |
| | |
| | | /* |
| | | * 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 2016 ForgeRock AS. |
| | | */ |
| | | package com.forgerock.opendj.grizzly; |
| | | |
| | | import static org.forgerock.util.Reject.checkNotNull; |
| | | |
| | | import org.forgerock.opendj.ldap.IntermediateResponseHandler; |
| | | import org.forgerock.opendj.ldap.LdapException; |
| | | import org.forgerock.opendj.ldap.LdapResultHandler; |
| | | import org.forgerock.opendj.ldap.SearchResultHandler; |
| | | import org.forgerock.opendj.ldap.ServerConnection; |
| | | import org.forgerock.opendj.ldap.requests.AbandonRequest; |
| | | import org.forgerock.opendj.ldap.requests.AddRequest; |
| | | import org.forgerock.opendj.ldap.requests.BindRequest; |
| | | import org.forgerock.opendj.ldap.requests.CompareRequest; |
| | | import org.forgerock.opendj.ldap.requests.DeleteRequest; |
| | | import org.forgerock.opendj.ldap.requests.ExtendedRequest; |
| | | import org.forgerock.opendj.ldap.requests.ModifyDNRequest; |
| | | import org.forgerock.opendj.ldap.requests.ModifyRequest; |
| | | import org.forgerock.opendj.ldap.requests.SearchRequest; |
| | | import org.forgerock.opendj.ldap.responses.BindResult; |
| | | import org.forgerock.opendj.ldap.responses.CompareResult; |
| | | import org.forgerock.opendj.ldap.responses.ExtendedResult; |
| | | import org.forgerock.opendj.ldap.responses.IntermediateResponse; |
| | | import org.forgerock.opendj.ldap.responses.Response; |
| | | import org.forgerock.opendj.ldap.responses.Result; |
| | | import org.forgerock.opendj.ldap.responses.SearchResultEntry; |
| | | import org.forgerock.opendj.ldap.responses.SearchResultReference; |
| | | import org.forgerock.util.promise.RuntimeExceptionHandler; |
| | | |
| | | import io.reactivex.FlowableEmitter; |
| | | |
| | | final class ServerConnectionAdaptor<C> { |
| | | |
| | | private final ServerConnection<C> adaptee; |
| | | |
| | | public ServerConnectionAdaptor(final ServerConnection<C> handler) { |
| | | this.adaptee = checkNotNull(handler, "handler must not be null"); |
| | | } |
| | | |
| | | public void handleAdd(final C requestContext, final AddRequest request, |
| | | final FlowableEmitter<Response> response) { |
| | | final ResultHandlerAdaptor<Result> resultAdapter = new ResultHandlerAdaptor<>(response); |
| | | adaptee.handleAdd(requestContext, request, resultAdapter, resultAdapter); |
| | | } |
| | | |
| | | public void handleBind(final C requestContext, final int version, final BindRequest request, |
| | | final FlowableEmitter<Response> response) { |
| | | final ResultHandlerAdaptor<BindResult> resultAdapter = new ResultHandlerAdaptor<>(response); |
| | | adaptee.handleBind(requestContext, version, request, resultAdapter, resultAdapter); |
| | | } |
| | | |
| | | public void handleCompare(final C requestContext, final CompareRequest request, |
| | | final FlowableEmitter<Response> response) { |
| | | final ResultHandlerAdaptor<CompareResult> resultAdapter = new ResultHandlerAdaptor<>(response); |
| | | adaptee.handleCompare(requestContext, request, resultAdapter, resultAdapter); |
| | | } |
| | | |
| | | public void handleDelete(final C requestContext, final DeleteRequest request, |
| | | final FlowableEmitter<Response> response) { |
| | | final ResultHandlerAdaptor<Result> resultAdapter = new ResultHandlerAdaptor<>(response); |
| | | adaptee.handleDelete(requestContext, request, resultAdapter, resultAdapter); |
| | | } |
| | | |
| | | public <R extends ExtendedResult> void handleExtendedRequest(final C requestContext, |
| | | final ExtendedRequest<R> request, final FlowableEmitter<Response> response) { |
| | | final ResultHandlerAdaptor<R> resultAdapter = new ResultHandlerAdaptor<>(response); |
| | | adaptee.handleExtendedRequest(requestContext, request, resultAdapter, resultAdapter); |
| | | } |
| | | |
| | | public void handleModify(final C requestContext, final ModifyRequest request, |
| | | final FlowableEmitter<Response> response) { |
| | | final ResultHandlerAdaptor<Result> resultAdapter = new ResultHandlerAdaptor<>(response); |
| | | adaptee.handleModify(requestContext, request, resultAdapter, resultAdapter); |
| | | } |
| | | |
| | | public void handleModifyDN(final C requestContext, final ModifyDNRequest request, |
| | | final FlowableEmitter<Response> response) { |
| | | final ResultHandlerAdaptor<Result> resultAdapter = new ResultHandlerAdaptor<>(response); |
| | | adaptee.handleModifyDN(requestContext, request, resultAdapter, resultAdapter); |
| | | } |
| | | |
| | | public void handleSearch(final C requestContext, final SearchRequest request, |
| | | final FlowableEmitter<Response> response) { |
| | | final ResultHandlerAdaptor<Result> resultAdapter = new ResultHandlerAdaptor<>(response); |
| | | adaptee.handleSearch(requestContext, request, resultAdapter, resultAdapter, resultAdapter); |
| | | } |
| | | |
| | | public void handleAbandon(C requestContext, final AbandonRequest request, final FlowableEmitter<Response> out) { |
| | | adaptee.handleAbandon(requestContext, request); |
| | | } |
| | | |
| | | /** |
| | | * Forward all response received from handler to a {@link LdapResponse}. |
| | | */ |
| | | private static final class ResultHandlerAdaptor<R extends Response> |
| | | implements IntermediateResponseHandler, SearchResultHandler, LdapResultHandler<R>, RuntimeExceptionHandler { |
| | | |
| | | private final FlowableEmitter<Response> adaptee; |
| | | |
| | | ResultHandlerAdaptor(final FlowableEmitter<Response> emitter) { |
| | | this.adaptee = emitter; |
| | | } |
| | | |
| | | @Override |
| | | public boolean handleEntry(final SearchResultEntry entry) { |
| | | adaptee.onNext(entry); |
| | | return true; |
| | | } |
| | | |
| | | @Override |
| | | public boolean handleReference(final SearchResultReference reference) { |
| | | adaptee.onNext(reference); |
| | | return true; |
| | | } |
| | | |
| | | @Override |
| | | public boolean handleIntermediateResponse(final IntermediateResponse intermediateResponse) { |
| | | adaptee.onNext(intermediateResponse); |
| | | return true; |
| | | } |
| | | |
| | | @Override |
| | | public void handleResult(R result) { |
| | | if (result != null) { |
| | | adaptee.onNext(result); |
| | | } |
| | | adaptee.onComplete(); |
| | | } |
| | | |
| | | @Override |
| | | public void handleRuntimeException(RuntimeException exception) { |
| | | adaptee.onError(exception); |
| | | } |
| | | |
| | | @Override |
| | | public void handleException(LdapException exception) { |
| | | adaptee.onError(exception); |
| | | } |
| | | } |
| | | } |
| | |
| | | import static com.forgerock.opendj.ldap.CoreMessages.*; |
| | | |
| | | import java.io.IOException; |
| | | import java.nio.charset.Charset; |
| | | |
| | | import org.forgerock.i18n.LocalizableMessage; |
| | | import org.forgerock.i18n.slf4j.LocalizedLogger; |
| | |
| | | import org.forgerock.opendj.ldap.ByteStringBuilder; |
| | | import org.forgerock.opendj.ldap.DecodeException; |
| | | import org.glassfish.grizzly.Buffer; |
| | | import org.glassfish.grizzly.memory.BuffersBuffer; |
| | | import org.glassfish.grizzly.memory.CompositeBuffer; |
| | | import org.glassfish.grizzly.memory.MemoryManager; |
| | | |
| | | /** Grizzly ASN1 reader implementation. */ |
| | | final class ASN1BufferReader extends AbstractASN1Reader { |
| | |
| | | |
| | | private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass(); |
| | | |
| | | private static final int MAX_STRING_BUFFER_SIZE = 1024; |
| | | private int markState; |
| | | private SequenceLimiter markReadLimiter; |
| | | |
| | | private int state = ASN1.ELEMENT_READ_STATE_NEED_TYPE; |
| | | private byte peekType; |
| | | private int peekLength = -1; |
| | | private int lengthBytesNeeded; |
| | | private final int maxElementSize; |
| | | private final CompositeBuffer buffer; |
| | | private final Buffer buffer; |
| | | private SequenceLimiter readLimiter; |
| | | private final byte[] stringBuffer; |
| | | |
| | | /** |
| | | * Creates a new ASN1 reader whose source is the provided input stream and |
| | |
| | | * @param memoryManager |
| | | * The memory manager to use for buffering. |
| | | */ |
| | | ASN1BufferReader(final int maxElementSize, final MemoryManager<?> memoryManager) { |
| | | ASN1BufferReader(final int maxElementSize, final Buffer buffer) { |
| | | this.readLimiter = new RootSequenceLimiter(); |
| | | this.stringBuffer = new byte[MAX_STRING_BUFFER_SIZE]; |
| | | this.buffer = buffer; |
| | | this.maxElementSize = maxElementSize; |
| | | this.buffer = BuffersBuffer.create(memoryManager); |
| | | } |
| | | |
| | | /** |
| | |
| | | */ |
| | | @Override |
| | | public void close() throws IOException { |
| | | buffer.dispose(); |
| | | // Nothing to do |
| | | } |
| | | |
| | | /** |
| | |
| | | return ""; |
| | | } |
| | | |
| | | byte[] readBuffer; |
| | | if (peekLength <= stringBuffer.length) { |
| | | readBuffer = stringBuffer; |
| | | } else { |
| | | readBuffer = new byte[peekLength]; |
| | | } |
| | | |
| | | readLimiter.checkLimit(peekLength); |
| | | buffer.get(readBuffer, 0, peekLength); |
| | | |
| | | state = ASN1.ELEMENT_READ_STATE_NEED_TYPE; |
| | | |
| | | String str; |
| | | try { |
| | | str = new String(readBuffer, 0, peekLength, "UTF-8"); |
| | | str = buffer.toStringContent(Charset.forName("UTF-8"), buffer.position(), buffer.position() + peekLength); |
| | | } catch (final Exception e) { |
| | | // TODO: I18N |
| | | logger.warn(LocalizableMessage.raw("Unable to decode ASN.1 OCTETSTRING bytes as UTF-8 string: %s", e)); |
| | | |
| | | str = new String(stringBuffer, 0, peekLength); |
| | | str = buffer.toStringContent(Charset.defaultCharset(), buffer.position(), buffer.position() + peekLength); |
| | | } finally { |
| | | buffer.position(buffer.position() + peekLength); |
| | | } |
| | | |
| | | logger.trace("READ ASN.1 OCTETSTRING(type=0x%x, length=%d, value=%s)", peekType, peekLength, str); |
| | |
| | | return this; |
| | | } |
| | | |
| | | void appendBytesRead(final Buffer buffer) { |
| | | this.buffer.append(buffer); |
| | | void mark() { |
| | | buffer.mark(); |
| | | markState = state; |
| | | markReadLimiter = readLimiter; |
| | | } |
| | | |
| | | void disposeBytesRead() { |
| | | this.buffer.shrink(); |
| | | void reset() { |
| | | buffer.reset(); |
| | | state = markState; |
| | | readLimiter = markReadLimiter; |
| | | } |
| | | |
| | | /** |
| | |
| | | */ |
| | | package org.forgerock.opendj.grizzly; |
| | | |
| | | import static com.forgerock.opendj.ldap.CoreMessages.*; |
| | | import static com.forgerock.opendj.ldap.CoreMessages.ERR_ASN1_SEQUENCE_WRITE_NOT_STARTED; |
| | | |
| | | import java.io.IOException; |
| | | import java.nio.ByteBuffer; |
| | | |
| | | import org.forgerock.i18n.LocalizableMessage; |
| | | import org.forgerock.i18n.slf4j.LocalizedLogger; |
| | |
| | | import org.forgerock.opendj.ldap.ByteStringBuilder; |
| | | import org.glassfish.grizzly.Buffer; |
| | | import org.glassfish.grizzly.Cacheable; |
| | | import org.glassfish.grizzly.memory.ByteBufferWrapper; |
| | | import org.glassfish.grizzly.memory.MemoryManager; |
| | | |
| | | import com.forgerock.opendj.util.StaticUtils; |
| | | |
| | |
| | | writeLength(parent, buffer.length()); |
| | | parent.writeByteArray(buffer.getBackingArray(), 0, buffer.length()); |
| | | buffer.clearAndTruncate(DEFAULT_MAX_INTERNAL_BUFFER_SIZE, BUFFER_INIT_SIZE); |
| | | logger.trace("WRITE ASN.1 END SEQUENCE(length=%d)", buffer.length()); |
| | | if (logger.isTraceEnabled()) { |
| | | logger.trace("WRITE ASN.1 END SEQUENCE(length=%d)", buffer.length()); |
| | | } |
| | | return parent; |
| | | } |
| | | |
| | |
| | | } |
| | | |
| | | @Override |
| | | public void writeByteArray(final byte[] bs, final int offset, final int length) throws IOException { |
| | | buffer.appendBytes(bs, offset, length); |
| | | } |
| | | } |
| | | |
| | | private static final class RecyclableBuffer extends ByteBufferWrapper { |
| | | private volatile boolean usable = true; |
| | | |
| | | private RecyclableBuffer() { |
| | | visible = ByteBuffer.allocate(BUFFER_INIT_SIZE); |
| | | allowBufferDispose = true; |
| | | public void writeByteSequence(ByteSequence bs) { |
| | | buffer.appendBytes(bs); |
| | | } |
| | | |
| | | @Override |
| | | public void dispose() { |
| | | usable = true; |
| | | } |
| | | |
| | | /** |
| | | * Ensures that the specified number of additional bytes will fit in the |
| | | * buffer and resizes it if necessary. |
| | | * |
| | | * @param size |
| | | * The number of additional bytes. |
| | | */ |
| | | public void ensureAdditionalCapacity(final int size) { |
| | | final int newCount = visible.position() + size; |
| | | if (newCount > visible.capacity()) { |
| | | final ByteBuffer newByteBuffer = |
| | | ByteBuffer.allocate(Math.max(visible.capacity() << 1, newCount)); |
| | | visible.flip(); |
| | | visible = newByteBuffer.put(visible); |
| | | } |
| | | public void writeByteArray(final byte[] bs, final int offset, final int length) throws IOException { |
| | | buffer.appendBytes(bs, offset, length); |
| | | } |
| | | } |
| | | |
| | |
| | | child = new ChildSequenceBuffer(); |
| | | child.parent = this; |
| | | } |
| | | outBuffer.ensureAdditionalCapacity(1); |
| | | ensureAdditionalCapacity(1); |
| | | outBuffer.put(type); |
| | | child.buffer.clear(); |
| | | return child; |
| | |
| | | |
| | | @Override |
| | | public void writeByte(final byte b) throws IOException { |
| | | outBuffer.ensureAdditionalCapacity(1); |
| | | ensureAdditionalCapacity(1); |
| | | outBuffer.put(b); |
| | | } |
| | | |
| | | @Override |
| | | public void writeByteSequence(ByteSequence bs) { |
| | | ensureAdditionalCapacity(bs.length()); |
| | | bs.copyTo(outBuffer.toByteBuffer()); |
| | | outBuffer.position(outBuffer.position() + bs.length()); |
| | | } |
| | | |
| | | @Override |
| | | public void writeByteArray(final byte[] bs, final int offset, final int length) |
| | | throws IOException { |
| | | outBuffer.ensureAdditionalCapacity(length); |
| | | ensureAdditionalCapacity(length); |
| | | outBuffer.put(bs, offset, length); |
| | | } |
| | | } |
| | |
| | | |
| | | void writeByte(byte b) throws IOException; |
| | | |
| | | void writeByteSequence(ByteSequence bs) throws IOException; |
| | | |
| | | void writeByteArray(byte[] bs, int offset, int length) throws IOException; |
| | | } |
| | | |
| | | private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass(); |
| | | |
| | | /** Initial size of newly created buffers. */ |
| | | private static final int BUFFER_INIT_SIZE = 1024; |
| | | private static final int BUFFER_INIT_SIZE = 4096; |
| | | /** Default maximum size for cached protocol/entry encoding buffers. */ |
| | | private static final int DEFAULT_MAX_INTERNAL_BUFFER_SIZE = 32 * 1024; |
| | | |
| | | /** Reset the writer. */ |
| | | void reset() { |
| | | if (!outBuffer.usable) { |
| | | // If the output buffer is unusable, create a new one. |
| | | outBuffer = new RecyclableBuffer(); |
| | | if (outBuffer.capacity() > DEFAULT_MAX_INTERNAL_BUFFER_SIZE) { |
| | | outBuffer = memoryManager.allocate(BUFFER_INIT_SIZE); |
| | | } else { |
| | | outBuffer.clear(); |
| | | } |
| | | outBuffer.clear(); |
| | | } |
| | | |
| | | private final MemoryManager<Buffer> memoryManager; |
| | | private SequenceBuffer sequenceBuffer; |
| | | private RecyclableBuffer outBuffer; |
| | | private Buffer outBuffer; |
| | | private final RootSequenceBuffer rootBuffer; |
| | | |
| | | /** Creates a new ASN.1 writer that writes to a StreamWriter. */ |
| | | ASN1BufferWriter() { |
| | | ASN1BufferWriter(MemoryManager memoryManager) { |
| | | this.sequenceBuffer = this.rootBuffer = new RootSequenceBuffer(); |
| | | this.outBuffer = new RecyclableBuffer(); |
| | | this.memoryManager = memoryManager; |
| | | this.outBuffer = memoryManager.allocate(BUFFER_INIT_SIZE); |
| | | } |
| | | |
| | | void ensureAdditionalCapacity(final int size) { |
| | | final int newCount = outBuffer.position() + size; |
| | | if (newCount > outBuffer.capacity()) { |
| | | outBuffer = memoryManager.reallocate(outBuffer, Math.max(outBuffer.capacity() << 1, newCount)); |
| | | } |
| | | } |
| | | |
| | | /** |
| | |
| | | @Override |
| | | public void recycle() { |
| | | sequenceBuffer = rootBuffer; |
| | | outBuffer.clear(); |
| | | outBuffer = memoryManager.allocate(BUFFER_INIT_SIZE); |
| | | } |
| | | |
| | | @Override |
| | |
| | | writeLength(sequenceBuffer, 1); |
| | | sequenceBuffer.writeByte(booleanValue ? ASN1.BOOLEAN_VALUE_TRUE : ASN1.BOOLEAN_VALUE_FALSE); |
| | | |
| | | logger.trace("WRITE ASN.1 BOOLEAN(type=0x%x, length=%d, value=%s)", type, 1, booleanValue); |
| | | if (logger.isTraceEnabled()) { |
| | | logger.trace("WRITE ASN.1 BOOLEAN(type=0x%x, length=%d, value=%s)", type, 1, booleanValue); |
| | | } |
| | | return this; |
| | | } |
| | | |
| | |
| | | || ((intValue & 0x0000007F) == intValue)) { |
| | | writeLength(sequenceBuffer, 1); |
| | | sequenceBuffer.writeByte((byte) intValue); |
| | | logger.trace("WRITE ASN.1 INTEGER(type=0x%x, length=%d, value=%d)", type, 1, intValue); |
| | | if (logger.isTraceEnabled()) { |
| | | logger.trace("WRITE ASN.1 INTEGER(type=0x%x, length=%d, value=%d)", type, 1, intValue); |
| | | } |
| | | } else if (((intValue < 0) && ((intValue & 0xFFFF8000) == 0xFFFF8000)) |
| | | || ((intValue & 0x00007FFF) == intValue)) { |
| | | writeLength(sequenceBuffer, 2); |
| | | sequenceBuffer.writeByte((byte) (intValue >> 8)); |
| | | sequenceBuffer.writeByte((byte) intValue); |
| | | logger.trace("WRITE ASN.1 INTEGER(type=0x%x, length=%d, value=%d)", type, 2, intValue); |
| | | if (logger.isTraceEnabled()) { |
| | | logger.trace("WRITE ASN.1 INTEGER(type=0x%x, length=%d, value=%d)", type, 2, intValue); |
| | | } |
| | | } else if (((intValue < 0) && ((intValue & 0xFF800000) == 0xFF800000)) |
| | | || ((intValue & 0x007FFFFF) == intValue)) { |
| | | writeLength(sequenceBuffer, 3); |
| | | sequenceBuffer.writeByte((byte) (intValue >> 16)); |
| | | sequenceBuffer.writeByte((byte) (intValue >> 8)); |
| | | sequenceBuffer.writeByte((byte) intValue); |
| | | logger.trace("WRITE ASN.1 INTEGER(type=0x%x, length=%d, value=%d)", type, 3, intValue); |
| | | if (logger.isTraceEnabled()) { |
| | | logger.trace("WRITE ASN.1 INTEGER(type=0x%x, length=%d, value=%d)", type, 3, intValue); |
| | | } |
| | | } else { |
| | | writeLength(sequenceBuffer, 4); |
| | | sequenceBuffer.writeByte((byte) (intValue >> 24)); |
| | | sequenceBuffer.writeByte((byte) (intValue >> 16)); |
| | | sequenceBuffer.writeByte((byte) (intValue >> 8)); |
| | | sequenceBuffer.writeByte((byte) intValue); |
| | | logger.trace("WRITE ASN.1 INTEGER(type=0x%x, length=%d, value=%d)", type, 4, intValue); |
| | | if (logger.isTraceEnabled()) { |
| | | logger.trace("WRITE ASN.1 INTEGER(type=0x%x, length=%d, value=%d)", type, 4, intValue); |
| | | } |
| | | } |
| | | return this; |
| | | } |
| | |
| | | || ((longValue & 0x000000000000007FL) == longValue)) { |
| | | writeLength(sequenceBuffer, 1); |
| | | sequenceBuffer.writeByte((byte) longValue); |
| | | logger.trace("WRITE ASN.1 INTEGER(type=0x%x, length=%d, value=%d)", type, 1, longValue); |
| | | if (logger.isTraceEnabled()) { |
| | | logger.trace("WRITE ASN.1 INTEGER(type=0x%x, length=%d, value=%d)", type, 1, longValue); |
| | | } |
| | | } else if (((longValue < 0) && ((longValue & 0xFFFFFFFFFFFF8000L) == 0xFFFFFFFFFFFF8000L)) |
| | | || ((longValue & 0x0000000000007FFFL) == longValue)) { |
| | | writeLength(sequenceBuffer, 2); |
| | | sequenceBuffer.writeByte((byte) (longValue >> 8)); |
| | | sequenceBuffer.writeByte((byte) longValue); |
| | | logger.trace("WRITE ASN.1 INTEGER(type=0x%x, length=%d, value=%d)", type, 2, longValue); |
| | | if (logger.isTraceEnabled()) { |
| | | logger.trace("WRITE ASN.1 INTEGER(type=0x%x, length=%d, value=%d)", type, 2, longValue); |
| | | } |
| | | } else if (((longValue < 0) && ((longValue & 0xFFFFFFFFFF800000L) == 0xFFFFFFFFFF800000L)) |
| | | || ((longValue & 0x00000000007FFFFFL) == longValue)) { |
| | | writeLength(sequenceBuffer, 3); |
| | | sequenceBuffer.writeByte((byte) (longValue >> 16)); |
| | | sequenceBuffer.writeByte((byte) (longValue >> 8)); |
| | | sequenceBuffer.writeByte((byte) longValue); |
| | | logger.trace("WRITE ASN.1 INTEGER(type=0x%x, length=%d, value=%d)", type, 3, longValue); |
| | | if (logger.isTraceEnabled()) { |
| | | logger.trace("WRITE ASN.1 INTEGER(type=0x%x, length=%d, value=%d)", type, 3, longValue); |
| | | } |
| | | } else if (((longValue < 0) && ((longValue & 0xFFFFFFFF80000000L) == 0xFFFFFFFF80000000L)) |
| | | || ((longValue & 0x000000007FFFFFFFL) == longValue)) { |
| | | writeLength(sequenceBuffer, 4); |
| | |
| | | sequenceBuffer.writeByte((byte) (longValue >> 16)); |
| | | sequenceBuffer.writeByte((byte) (longValue >> 8)); |
| | | sequenceBuffer.writeByte((byte) longValue); |
| | | logger.trace("WRITE ASN.1 INTEGER(type=0x%x, length=%d, value=%d)", type, 4, longValue); |
| | | if (logger.isTraceEnabled()) { |
| | | logger.trace("WRITE ASN.1 INTEGER(type=0x%x, length=%d, value=%d)", type, 4, longValue); |
| | | } |
| | | } else if (((longValue < 0) && ((longValue & 0xFFFFFF8000000000L) == 0xFFFFFF8000000000L)) |
| | | || ((longValue & 0x0000007FFFFFFFFFL) == longValue)) { |
| | | writeLength(sequenceBuffer, 5); |
| | |
| | | sequenceBuffer.writeByte((byte) (longValue >> 16)); |
| | | sequenceBuffer.writeByte((byte) (longValue >> 8)); |
| | | sequenceBuffer.writeByte((byte) longValue); |
| | | logger.trace("WRITE ASN.1 INTEGER(type=0x%x, length=%d, value=%d)", type, 5, longValue); |
| | | if (logger.isTraceEnabled()) { |
| | | logger.trace("WRITE ASN.1 INTEGER(type=0x%x, length=%d, value=%d)", type, 5, longValue); |
| | | } |
| | | } else if (((longValue < 0) && ((longValue & 0xFFFF800000000000L) == 0xFFFF800000000000L)) |
| | | || ((longValue & 0x00007FFFFFFFFFFFL) == longValue)) { |
| | | writeLength(sequenceBuffer, 6); |
| | |
| | | sequenceBuffer.writeByte((byte) (longValue >> 16)); |
| | | sequenceBuffer.writeByte((byte) (longValue >> 8)); |
| | | sequenceBuffer.writeByte((byte) longValue); |
| | | logger.trace("WRITE ASN.1 INTEGER(type=0x%x, length=%d, value=%d)", type, 6, longValue); |
| | | if (logger.isTraceEnabled()) { |
| | | logger.trace("WRITE ASN.1 INTEGER(type=0x%x, length=%d, value=%d)", type, 6, longValue); |
| | | } |
| | | } else if (((longValue < 0) && ((longValue & 0xFF80000000000000L) == 0xFF80000000000000L)) |
| | | || ((longValue & 0x007FFFFFFFFFFFFFL) == longValue)) { |
| | | writeLength(sequenceBuffer, 7); |
| | |
| | | sequenceBuffer.writeByte((byte) (longValue >> 16)); |
| | | sequenceBuffer.writeByte((byte) (longValue >> 8)); |
| | | sequenceBuffer.writeByte((byte) longValue); |
| | | logger.trace("WRITE ASN.1 INTEGER(type=0x%x, length=%d, value=%d)", type, 7, longValue); |
| | | if (logger.isTraceEnabled()) { |
| | | logger.trace("WRITE ASN.1 INTEGER(type=0x%x, length=%d, value=%d)", type, 7, longValue); |
| | | } |
| | | } else { |
| | | writeLength(sequenceBuffer, 8); |
| | | sequenceBuffer.writeByte((byte) (longValue >> 56)); |
| | |
| | | sequenceBuffer.writeByte((byte) (longValue >> 16)); |
| | | sequenceBuffer.writeByte((byte) (longValue >> 8)); |
| | | sequenceBuffer.writeByte((byte) longValue); |
| | | logger.trace("WRITE ASN.1 INTEGER(type=0x%x, length=%d, value=%d)", type, 8, longValue); |
| | | if (logger.isTraceEnabled()) { |
| | | logger.trace("WRITE ASN.1 INTEGER(type=0x%x, length=%d, value=%d)", type, 8, longValue); |
| | | } |
| | | } |
| | | return this; |
| | | } |
| | |
| | | sequenceBuffer.writeByte(type); |
| | | writeLength(sequenceBuffer, 0); |
| | | |
| | | logger.trace("WRITE ASN.1 NULL(type=0x%x, length=%d)", type, 0); |
| | | if (logger.isTraceEnabled()) { |
| | | logger.trace("WRITE ASN.1 NULL(type=0x%x, length=%d)", type, 0); |
| | | } |
| | | return this; |
| | | } |
| | | |
| | |
| | | writeLength(sequenceBuffer, length); |
| | | sequenceBuffer.writeByteArray(value, offset, length); |
| | | |
| | | logger.trace("WRITE ASN.1 OCTETSTRING(type=0x%x, length=%d)", type, length); |
| | | if (logger.isTraceEnabled()) { |
| | | logger.trace("WRITE ASN.1 OCTETSTRING(type=0x%x, length=%d)", type, length); |
| | | } |
| | | return this; |
| | | } |
| | | |
| | |
| | | throws IOException { |
| | | sequenceBuffer.writeByte(type); |
| | | writeLength(sequenceBuffer, value.length()); |
| | | // TODO: Is there a more efficient way to do this? |
| | | for (int i = 0; i < value.length(); i++) { |
| | | sequenceBuffer.writeByte(value.byteAt(i)); |
| | | } |
| | | sequenceBuffer.writeByteSequence(value); |
| | | // // TODO: Is there a more efficient way to do this? |
| | | // for (int i = 0; i < value.length(); i++) { |
| | | // sequenceBuffer.writeByte(value.byteAt(i)); |
| | | // } |
| | | |
| | | logger.trace("WRITE ASN.1 OCTETSTRING(type=0x%x, length=%d)", type, value.length()); |
| | | if (logger.isTraceEnabled()) { |
| | | logger.trace("WRITE ASN.1 OCTETSTRING(type=0x%x, length=%d)", type, value.length()); |
| | | } |
| | | return this; |
| | | } |
| | | |
| | |
| | | writeLength(sequenceBuffer, bytes.length); |
| | | sequenceBuffer.writeByteArray(bytes, 0, bytes.length); |
| | | |
| | | logger.trace("WRITE ASN.1 OCTETSTRING(type=0x%x, length=%d, value=%s)", type, bytes.length, value); |
| | | if (logger.isTraceEnabled()) { |
| | | logger.trace("WRITE ASN.1 OCTETSTRING(type=0x%x, length=%d, value=%s)", type, bytes.length, value); |
| | | } |
| | | return this; |
| | | } |
| | | |
| | |
| | | // Get a child sequence buffer |
| | | sequenceBuffer = sequenceBuffer.startSequence(type); |
| | | |
| | | logger.trace("WRITE ASN.1 START SEQUENCE(type=0x%x)", type); |
| | | if (logger.isTraceEnabled()) { |
| | | logger.trace("WRITE ASN.1 START SEQUENCE(type=0x%x)", type); |
| | | } |
| | | return this; |
| | | } |
| | | |
| | |
| | | return writeStartSequence(type); |
| | | } |
| | | |
| | | Buffer getBuffer() { |
| | | outBuffer.usable = false; |
| | | public Buffer getBuffer() { |
| | | outBuffer.allowBufferDispose(true); |
| | | return outBuffer.flip(); |
| | | } |
| | | |
| | |
| | | * information: "Portions Copyright [year] [name of copyright owner]". |
| | | * |
| | | * Copyright 2010 Sun Microsystems, Inc. |
| | | * Portions copyright 2011-2014 ForgeRock AS. |
| | | * Portions copyright 2011-2016 ForgeRock AS. |
| | | */ |
| | | package org.forgerock.opendj.grizzly; |
| | | |
| | |
| | | if (useWorkerThreadsStr != null) { |
| | | useWorkerThreadStrategy = Boolean.parseBoolean(useWorkerThreadsStr); |
| | | } else { |
| | | /* |
| | | * The most best performing strategy to use is the |
| | | * SameThreadIOStrategy, however it can only be used in cases where |
| | | * result listeners will not block. |
| | | */ |
| | | useWorkerThreadStrategy = true; |
| | | useWorkerThreadStrategy = false; |
| | | } |
| | | |
| | | if (useWorkerThreadStrategy) { |
| | |
| | | } |
| | | |
| | | private LdapPromise<Void> sendAbandonRequest(final AbandonRequest request) { |
| | | final LDAPWriter<ASN1BufferWriter> writer = GrizzlyUtils.getWriter(); |
| | | final LDAPWriter<ASN1BufferWriter> writer = GrizzlyUtils.getWriter(connection.getMemoryManager()); |
| | | try { |
| | | final int messageID = nextMsgID.getAndIncrement(); |
| | | writer.writeAbandonRequest(messageID, request); |
| | |
| | | pendingRequests.put(messageID, promise); |
| | | } |
| | | try { |
| | | final LDAPWriter<ASN1BufferWriter> writer = GrizzlyUtils.getWriter(); |
| | | final LDAPWriter<ASN1BufferWriter> writer = GrizzlyUtils.getWriter(connection.getMemoryManager()); |
| | | try { |
| | | writer.writeAddRequest(messageID, request); |
| | | connection.write(writer.getASN1Writer().getBuffer(), null); |
| | |
| | | } |
| | | |
| | | try { |
| | | final LDAPWriter<ASN1BufferWriter> writer = GrizzlyUtils.getWriter(); |
| | | final LDAPWriter<ASN1BufferWriter> writer = GrizzlyUtils.getWriter(connection.getMemoryManager()); |
| | | try { |
| | | // Use the bind client to get the initial request instead of |
| | | // using the bind request passed to this method. |
| | |
| | | pendingRequests.put(messageID, promise); |
| | | } |
| | | try { |
| | | final LDAPWriter<ASN1BufferWriter> writer = GrizzlyUtils.getWriter(); |
| | | final LDAPWriter<ASN1BufferWriter> writer = GrizzlyUtils.getWriter(connection.getMemoryManager()); |
| | | try { |
| | | writer.writeCompareRequest(messageID, request); |
| | | connection.write(writer.getASN1Writer().getBuffer(), null); |
| | |
| | | pendingRequests.put(messageID, promise); |
| | | } |
| | | try { |
| | | final LDAPWriter<ASN1BufferWriter> writer = GrizzlyUtils.getWriter(); |
| | | final LDAPWriter<ASN1BufferWriter> writer = GrizzlyUtils.getWriter(connection.getMemoryManager()); |
| | | try { |
| | | writer.writeDeleteRequest(messageID, request); |
| | | connection.write(writer.getASN1Writer().getBuffer(), null); |
| | |
| | | pendingRequests.put(messageID, promise); |
| | | } |
| | | try { |
| | | final LDAPWriter<ASN1BufferWriter> writer = GrizzlyUtils.getWriter(); |
| | | final LDAPWriter<ASN1BufferWriter> writer = GrizzlyUtils.getWriter(connection.getMemoryManager()); |
| | | try { |
| | | writer.writeExtendedRequest(messageID, request); |
| | | connection.write(writer.getASN1Writer().getBuffer(), null); |
| | |
| | | pendingRequests.put(messageID, promise); |
| | | } |
| | | try { |
| | | final LDAPWriter<ASN1BufferWriter> writer = GrizzlyUtils.getWriter(); |
| | | final LDAPWriter<ASN1BufferWriter> writer = GrizzlyUtils.getWriter(connection.getMemoryManager()); |
| | | try { |
| | | writer.writeModifyRequest(messageID, request); |
| | | connection.write(writer.getASN1Writer().getBuffer(), null); |
| | |
| | | pendingRequests.put(messageID, promise); |
| | | } |
| | | try { |
| | | final LDAPWriter<ASN1BufferWriter> writer = GrizzlyUtils.getWriter(); |
| | | final LDAPWriter<ASN1BufferWriter> writer = GrizzlyUtils.getWriter(connection.getMemoryManager()); |
| | | try { |
| | | writer.writeModifyDNRequest(messageID, request); |
| | | connection.write(writer.getASN1Writer().getBuffer(), null); |
| | |
| | | pendingRequests.put(messageID, promise); |
| | | } |
| | | try { |
| | | final LDAPWriter<ASN1BufferWriter> writer = GrizzlyUtils.getWriter(); |
| | | final LDAPWriter<ASN1BufferWriter> writer = GrizzlyUtils.getWriter(connection.getMemoryManager()); |
| | | try { |
| | | writer.writeSearchRequest(messageID, request); |
| | | connection.write(writer.getASN1Writer().getBuffer(), null); |
| | |
| | | * connection and release resources. |
| | | */ |
| | | if (notifyClose) { |
| | | final LDAPWriter<ASN1BufferWriter> writer = GrizzlyUtils.getWriter(); |
| | | final LDAPWriter<ASN1BufferWriter> writer = GrizzlyUtils.getWriter(connection.getMemoryManager()); |
| | | try { |
| | | writer.writeUnbindRequest(nextMsgID.getAndIncrement(), unbindRequest); |
| | | connection.write(writer.getASN1Writer().getBuffer(), null); |
| | |
| | | * information: "Portions Copyright [year] [name of copyright owner]". |
| | | * |
| | | * Copyright 2010 Sun Microsystems, Inc. |
| | | * Portions copyright 2011-2015 ForgeRock AS. |
| | | * Portions copyright 2011-2016 ForgeRock AS. |
| | | */ |
| | | package org.forgerock.opendj.grizzly; |
| | | |
| | | import static org.forgerock.opendj.grizzly.DefaultTCPNIOTransport.DEFAULT_TRANSPORT; |
| | | import static org.forgerock.opendj.ldap.CommonLDAPOptions.LDAP_DECODE_OPTIONS; |
| | | import static org.forgerock.opendj.ldap.LDAPListener.*; |
| | | |
| | | import java.io.IOException; |
| | | import java.net.InetSocketAddress; |
| | | import java.net.SocketAddress; |
| | | import java.util.ArrayList; |
| | | import java.util.Collection; |
| | | import java.util.HashSet; |
| | | import java.util.Set; |
| | | import java.util.concurrent.atomic.AtomicBoolean; |
| | | |
| | | import org.forgerock.i18n.LocalizableMessage; |
| | | import org.forgerock.i18n.slf4j.LocalizedLogger; |
| | | import org.forgerock.opendj.ldap.Connections; |
| | | import org.forgerock.opendj.ldap.LDAPClientContext; |
| | | import org.forgerock.opendj.ldap.ServerConnectionFactory; |
| | | import org.forgerock.opendj.ldap.LdapException; |
| | | import org.forgerock.opendj.ldap.responses.Response; |
| | | import org.forgerock.opendj.ldap.spi.LDAPListenerImpl; |
| | | import org.forgerock.opendj.ldap.spi.LdapMessages.LdapRawMessage; |
| | | import org.forgerock.util.Function; |
| | | import org.forgerock.util.Options; |
| | | import org.glassfish.grizzly.filterchain.FilterChain; |
| | | import org.glassfish.grizzly.nio.transport.TCPNIOBindingHandler; |
| | | import org.glassfish.grizzly.nio.transport.TCPNIOConnection; |
| | | import org.glassfish.grizzly.nio.transport.TCPNIOServerConnection; |
| | | import org.glassfish.grizzly.nio.transport.TCPNIOTransport; |
| | | |
| | | import com.forgerock.opendj.util.ReferenceCountedObject; |
| | | import com.forgerock.reactive.ReactiveHandler; |
| | | import com.forgerock.reactive.Stream; |
| | | |
| | | /** |
| | | * LDAP listener implementation using Grizzly for transport. |
| | |
| | | public final class GrizzlyLDAPListener implements LDAPListenerImpl { |
| | | private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass(); |
| | | private final ReferenceCountedObject<TCPNIOTransport>.Reference transport; |
| | | private final ServerConnectionFactory<LDAPClientContext, Integer> connectionFactory; |
| | | private final TCPNIOServerConnection serverConnection; |
| | | private final Collection<TCPNIOServerConnection> serverConnections; |
| | | private final AtomicBoolean isClosed = new AtomicBoolean(); |
| | | private final InetSocketAddress socketAddress; |
| | | private final Set<SocketAddress> socketAddresses; |
| | | private final Options options; |
| | | |
| | | /** |
| | | * Creates a new LDAP listener implementation which will listen for LDAP |
| | | * client connections using the provided address and connection options. |
| | | * Creates a new LDAP listener implementation which will listen for LDAP client connections using the provided |
| | | * address and connection options. |
| | | * |
| | | * @param address |
| | | * The address to listen on. |
| | | * @param factory |
| | | * The server connection factory which will be used to create |
| | | * server connections. |
| | | * @param addresses |
| | | * The addresses to listen on. |
| | | * @param options |
| | | * The LDAP listener options. |
| | | * @param handler |
| | | * The server connection factory which will be used to create server connections. |
| | | * @throws IOException |
| | | * If an error occurred while trying to listen on the provided |
| | | * address. |
| | | * If an error occurred while trying to listen on the provided address. |
| | | */ |
| | | public GrizzlyLDAPListener(final InetSocketAddress address, |
| | | final ServerConnectionFactory<LDAPClientContext, Integer> factory, |
| | | final Options options) throws IOException { |
| | | this(address, factory, options, null); |
| | | public GrizzlyLDAPListener(final Set<? extends SocketAddress> addresses, final Options options, |
| | | final Function<LDAPClientContext, |
| | | ReactiveHandler<LDAPClientContext, LdapRawMessage, Stream<Response>>, |
| | | LdapException> handler) throws IOException { |
| | | this(addresses, handler, options, null); |
| | | } |
| | | |
| | | /** |
| | | * Creates a new LDAP listener implementation which will listen for LDAP |
| | | * client connections using the provided address, connection options and |
| | | * provided TCP transport. |
| | | * Creates a new LDAP listener implementation which will listen for LDAP client connections using the provided |
| | | * address, connection options and provided TCP transport. |
| | | * |
| | | * @param address |
| | | * The address to listen on. |
| | | * @param factory |
| | | * The server connection factory which will be used to create |
| | | * server connections. |
| | | * @param addresses |
| | | * The addresses to listen on. |
| | | * @param handler |
| | | * The server connection factory which will be used to create server connections. |
| | | * @param options |
| | | * The LDAP listener options. |
| | | * @param transport |
| | | * Grizzly TCP Transport NIO implementation to use for |
| | | * connections. If {@code null}, default transport will be used. |
| | | * Grizzly TCP Transport NIO implementation to use for connections. If {@code null}, default transport |
| | | * will be used. |
| | | * @throws IOException |
| | | * If an error occurred while trying to listen on the provided |
| | | * address. |
| | | * If an error occurred while trying to listen on the provided address. |
| | | */ |
| | | public GrizzlyLDAPListener(final InetSocketAddress address, |
| | | final ServerConnectionFactory<LDAPClientContext, Integer> factory, |
| | | public GrizzlyLDAPListener(final Set<? extends SocketAddress> addresses, |
| | | final Function<LDAPClientContext, |
| | | ReactiveHandler<LDAPClientContext, LdapRawMessage, Stream<Response>>, |
| | | LdapException> handler, |
| | | final Options options, TCPNIOTransport transport) throws IOException { |
| | | this.transport = DEFAULT_TRANSPORT.acquireIfNull(transport); |
| | | this.connectionFactory = factory; |
| | | this.options = Options.copyOf(options); |
| | | final LDAPServerFilter serverFilter = |
| | | new LDAPServerFilter(this, options.get(LDAP_DECODE_OPTIONS), options.get(REQUEST_MAX_SIZE_IN_BYTES)); |
| | | final FilterChain ldapChain = |
| | | GrizzlyUtils.buildFilterChain(this.transport.get().getProcessor(), serverFilter); |
| | | final TCPNIOBindingHandler bindingHandler = |
| | | TCPNIOBindingHandler.builder(this.transport.get()).processor(ldapChain).build(); |
| | | this.serverConnection = bindingHandler.bind(address, options.get(CONNECT_MAX_BACKLOG)); |
| | | |
| | | /* |
| | | * Get the socket address now, ensuring that the host is the same as the |
| | | * one provided in the constructor. The port will have changed if 0 was |
| | | * passed in. |
| | | */ |
| | | final int port = ((InetSocketAddress) serverConnection.getLocalAddress()).getPort(); |
| | | socketAddress = new InetSocketAddress(Connections.getHostString(address), port); |
| | | final LDAPServerFilter serverFilter = new LDAPServerFilter(handler, options, |
| | | options.get(LDAP_DECODE_OPTIONS), options.get(MAX_CONCURRENT_REQUESTS)); |
| | | final FilterChain ldapChain = GrizzlyUtils.buildFilterChain(this.transport.get().getProcessor(), |
| | | new LdapCodec(options.get(REQUEST_MAX_SIZE_IN_BYTES), options.get(LDAP_DECODE_OPTIONS)), serverFilter); |
| | | final TCPNIOBindingHandler bindingHandler = TCPNIOBindingHandler.builder(this.transport.get()) |
| | | .processor(ldapChain).build(); |
| | | this.serverConnections = new ArrayList<>(addresses.size()); |
| | | this.socketAddresses = new HashSet<>(addresses.size()); |
| | | for (final SocketAddress address : addresses) { |
| | | final TCPNIOServerConnection bound = bindingHandler.bind(address, options.get(CONNECT_MAX_BACKLOG)); |
| | | serverConnections.add(bound); |
| | | socketAddresses.add(bound.getLocalAddress()); |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | public void close() { |
| | | if (isClosed.compareAndSet(false, true)) { |
| | | try { |
| | | serverConnection.close().get(); |
| | | for (TCPNIOConnection serverConnection : serverConnections) { |
| | | serverConnection.close().get(); |
| | | } |
| | | } catch (final InterruptedException e) { |
| | | // Cannot handle here. |
| | | Thread.currentThread().interrupt(); |
| | |
| | | } |
| | | |
| | | @Override |
| | | public InetSocketAddress getSocketAddress() { |
| | | return socketAddress; |
| | | public Set<? extends SocketAddress> getSocketAddresses() { |
| | | return socketAddresses; |
| | | } |
| | | |
| | | @Override |
| | | public String toString() { |
| | | final StringBuilder builder = new StringBuilder(); |
| | | builder.append("LDAPListener("); |
| | | builder.append(getSocketAddress()); |
| | | builder.append(socketAddresses); |
| | | builder.append(')'); |
| | | return builder.toString(); |
| | | } |
| | | |
| | | ServerConnectionFactory<LDAPClientContext, Integer> getConnectionFactory() { |
| | | return connectionFactory; |
| | | } |
| | | |
| | | Options getLDAPListenerOptions() { |
| | | return options; |
| | | } |
| | |
| | | * Header, with the fields enclosed by brackets [] replaced by your own identifying |
| | | * information: "Portions Copyright [year] [name of copyright owner]". |
| | | * |
| | | * Copyright 2013-2015 ForgeRock AS. |
| | | * Copyright 2013-2016 ForgeRock AS. |
| | | */ |
| | | package org.forgerock.opendj.grizzly; |
| | | |
| | |
| | | import org.glassfish.grizzly.filterchain.Filter; |
| | | import org.glassfish.grizzly.filterchain.FilterChain; |
| | | import org.glassfish.grizzly.filterchain.FilterChainBuilder; |
| | | import org.glassfish.grizzly.filterchain.FilterChainEnabledTransport; |
| | | import org.glassfish.grizzly.filterchain.TransportFilter; |
| | | import org.glassfish.grizzly.memory.BuffersBuffer; |
| | | import org.glassfish.grizzly.memory.MemoryManager; |
| | | import org.glassfish.grizzly.nio.transport.TCPNIOConnection; |
| | | import org.glassfish.grizzly.ssl.SSLFilter; |
| | |
| | | * is a {@code FilterChain}, and having the provided filter as the |
| | | * last filter |
| | | */ |
| | | static FilterChain buildFilterChain(Processor<?> processor, Filter filter) { |
| | | static FilterChain buildFilterChain(Processor<?> processor, Filter... filters) { |
| | | if (processor instanceof FilterChain) { |
| | | return FilterChainBuilder.stateless().addAll((FilterChain) processor).add(filter).build(); |
| | | return FilterChainBuilder.stateless().addAll((FilterChain) processor).addAll(filters).build(); |
| | | } else if (processor instanceof FilterChainEnabledTransport) { |
| | | return FilterChainBuilder.stateless().add(((FilterChainEnabledTransport) processor).getTransportFilter()) |
| | | .addAll(filters).build(); |
| | | } else { |
| | | return FilterChainBuilder.stateless().add(new TransportFilter()).add(filter).build(); |
| | | return FilterChainBuilder.stateless().add(new TransportFilter()).addAll(filters).build(); |
| | | } |
| | | } |
| | | |
| | |
| | | */ |
| | | static FilterChain addFilterToChain(final Filter filter, final FilterChain chain) { |
| | | // By default, before LDAP filter which is the last one |
| | | int indexToAddFilter = chain.size() - 1; |
| | | if (filter instanceof SSLFilter) { |
| | | // Before any ConnectionSecurityLayerFilters if present |
| | | for (int i = chain.size() - 2; i >= 0; i--) { |
| | | if (!(chain.get(i) instanceof ConnectionSecurityLayerFilter)) { |
| | | indexToAddFilter = i + 1; |
| | | break; |
| | | } |
| | | } |
| | | return FilterChainBuilder.stateless().addAll(chain).add(1, filter).build(); |
| | | } |
| | | return FilterChainBuilder.stateless().addAll(chain).add(indexToAddFilter, filter).build(); |
| | | if (filter instanceof SaslFilter) { |
| | | final int pos = chain.get(1) instanceof SSLFilter ? 2 : 1; |
| | | return FilterChainBuilder.stateless().addAll(chain).add(pos, filter).build(); |
| | | } |
| | | return FilterChainBuilder.stateless().addAll(chain).add(chain.size() - 1, filter).build(); |
| | | } |
| | | |
| | | /** |
| | |
| | | */ |
| | | static LDAPReader<ASN1BufferReader> createReader(DecodeOptions decodeOptions, |
| | | int maxASN1ElementSize, MemoryManager<?> memoryManager) { |
| | | ASN1BufferReader asn1Reader = new ASN1BufferReader(maxASN1ElementSize, memoryManager); |
| | | ASN1BufferReader asn1Reader = new ASN1BufferReader(maxASN1ElementSize, BuffersBuffer.create(memoryManager)); |
| | | return LDAP.getReader(asn1Reader, decodeOptions); |
| | | } |
| | | |
| | |
| | | * @return a LDAP writer |
| | | */ |
| | | @SuppressWarnings("unchecked") |
| | | static LDAPWriter<ASN1BufferWriter> getWriter() { |
| | | static LDAPWriter<ASN1BufferWriter> getWriter(final MemoryManager memoryManager) { |
| | | LDAPWriter<ASN1BufferWriter> writer = ThreadCache.takeFromCache(WRITER_INDEX); |
| | | if (writer == null) { |
| | | writer = LDAP.getWriter(new ASN1BufferWriter()); |
| | | writer = LDAP.getWriter(new ASN1BufferWriter(memoryManager)); |
| | | } |
| | | writer.getASN1Writer().reset(); |
| | | return writer; |
| | |
| | | * Header, with the fields enclosed by brackets [] replaced by your own identifying |
| | | * information: "Portions Copyright [year] [name of copyright owner]". |
| | | * |
| | | * Copyright 2013 ForgeRock AS. |
| | | * Copyright 2013-2016 ForgeRock AS. |
| | | */ |
| | | package org.forgerock.opendj.grizzly; |
| | | |
| | | import java.io.IOException; |
| | | |
| | | import org.forgerock.opendj.io.LDAPMessageHandler; |
| | | import org.forgerock.opendj.io.LDAPReader; |
| | | import org.forgerock.opendj.ldap.DecodeOptions; |
| | | import org.glassfish.grizzly.Buffer; |
| | | import org.glassfish.grizzly.filterchain.BaseFilter; |
| | | import org.glassfish.grizzly.filterchain.FilterChainContext; |
| | | import org.glassfish.grizzly.filterchain.NextAction; |
| | | |
| | | /** |
| | | * Base class for LDAP-enabled filter. |
| | | * <p> |
| | | * Provides a common {@code handleRead()} method for both client and server |
| | | * filters. |
| | | */ |
| | | abstract class LDAPBaseFilter extends BaseFilter { |
| | | |
| | |
| | | this.decodeOptions = options; |
| | | this.maxASN1ElementSize = maxASN1ElementSize; |
| | | } |
| | | |
| | | @Override |
| | | public final NextAction handleRead(final FilterChainContext ctx) throws IOException { |
| | | final LDAPBaseHandler handler = getLDAPHandler(ctx); |
| | | final LDAPReader<ASN1BufferReader> reader = handler.getReader(); |
| | | final ASN1BufferReader asn1Reader = reader.getASN1Reader(); |
| | | final Buffer buffer = (Buffer) ctx.getMessage(); |
| | | |
| | | asn1Reader.appendBytesRead(buffer); |
| | | try { |
| | | while (reader.hasMessageAvailable()) { |
| | | reader.readMessage(handler); |
| | | } |
| | | } catch (IOException e) { |
| | | handleReadException(ctx, e); |
| | | throw e; |
| | | } finally { |
| | | asn1Reader.disposeBytesRead(); |
| | | } |
| | | |
| | | return ctx.getStopAction(); |
| | | } |
| | | |
| | | /** |
| | | * Handle an exception occuring during a read within the |
| | | * {@code handleRead()} method. |
| | | * |
| | | * @param ctx |
| | | * context when reading |
| | | * @param e |
| | | * exception occuring while reading |
| | | */ |
| | | abstract void handleReadException(FilterChainContext ctx, IOException e); |
| | | |
| | | /** |
| | | * Interface for the {@code LDAPMessageHandler} used in the filter, that |
| | | * must be able to retrieve a Grizzly reader. |
| | | */ |
| | | interface LDAPBaseHandler extends LDAPMessageHandler { |
| | | /** |
| | | * Returns the LDAP reader for this handler. |
| | | * @return the reader |
| | | */ |
| | | LDAPReader<ASN1BufferReader> getReader(); |
| | | } |
| | | |
| | | /** |
| | | * Returns the LDAP message handler associated to the underlying connection |
| | | * of the provided context. |
| | | * <p> |
| | | * If no handler exists yet for the underlying connection, a new one is |
| | | * created and recorded for the connection. |
| | | * |
| | | * @param ctx |
| | | * current filter chain context |
| | | * @return the response handler associated to the connection, which can be a |
| | | * new one if no handler have been created yet |
| | | */ |
| | | abstract LDAPBaseHandler getLDAPHandler(final FilterChainContext ctx); |
| | | |
| | | } |
| | |
| | | |
| | | package org.forgerock.opendj.grizzly; |
| | | |
| | | import static org.forgerock.opendj.ldap.CommonLDAPOptions.LDAP_DECODE_OPTIONS; |
| | | import static org.forgerock.opendj.ldap.ResultCode.CLIENT_SIDE_LOCAL_ERROR; |
| | | import static org.forgerock.opendj.ldap.responses.Responses.newResult; |
| | | |
| | | import java.io.EOFException; |
| | | import java.io.IOException; |
| | | |
| | | import javax.net.ssl.SSLEngine; |
| | | |
| | | import org.forgerock.opendj.io.ASN1Reader; |
| | | import org.forgerock.opendj.io.AbstractLDAPMessageHandler; |
| | | import org.forgerock.opendj.io.LDAP; |
| | | import org.forgerock.opendj.io.LDAPMessageHandler; |
| | | import org.forgerock.opendj.io.LDAPReader; |
| | | import org.forgerock.opendj.io.LDAPWriter; |
| | | import org.forgerock.opendj.ldap.ConnectionSecurityLayer; |
| | |
| | | import org.forgerock.opendj.ldap.spi.ExtendedResultLdapPromiseImpl; |
| | | import org.forgerock.opendj.ldap.spi.ResultLdapPromiseImpl; |
| | | import org.forgerock.opendj.ldap.spi.SearchResultLdapPromiseImpl; |
| | | import org.glassfish.grizzly.Buffer; |
| | | import org.glassfish.grizzly.Connection; |
| | | import org.glassfish.grizzly.EmptyCompletionHandler; |
| | | import org.glassfish.grizzly.Grizzly; |
| | |
| | | import org.glassfish.grizzly.filterchain.FilterChainContext; |
| | | import org.glassfish.grizzly.filterchain.NextAction; |
| | | |
| | | import static org.forgerock.opendj.ldap.LDAPConnectionFactory.*; |
| | | import static org.forgerock.opendj.ldap.ResultCode.*; |
| | | import static org.forgerock.opendj.ldap.responses.Responses.*; |
| | | |
| | | /** |
| | | * Grizzly filter implementation for decoding LDAP responses and handling client |
| | | * side logic for SSL and SASL operations over LDAP. |
| | |
| | | private static final Attribute<ClientResponseHandler> RESPONSE_HANDLER_ATTR = |
| | | Grizzly.DEFAULT_ATTRIBUTE_BUILDER.createAttribute("ClientResponseHandler"); |
| | | |
| | | static final class ClientResponseHandler extends AbstractLDAPMessageHandler implements |
| | | LDAPBaseHandler { |
| | | static final class ClientResponseHandler extends AbstractLDAPMessageHandler { |
| | | |
| | | private final LDAPReader<ASN1BufferReader> reader; |
| | | private FilterChainContext context; |
| | | |
| | | /** |
| | | * Creates a handler with the provided reader. |
| | | * |
| | | * @param reader |
| | | * LDAP reader to use for reading incoming messages |
| | | */ |
| | | ClientResponseHandler(LDAPReader<ASN1BufferReader> reader) { |
| | | this.reader = reader; |
| | | } |
| | | |
| | | void setFilterChainContext(FilterChainContext context) { |
| | | this.context = context; |
| | | } |
| | | |
| | | /** |
| | | * Returns the LDAP reader. |
| | | * |
| | | * @return the reader to read incoming LDAP messages |
| | | */ |
| | | @Override |
| | | public LDAPReader<ASN1BufferReader> getReader() { |
| | | return this.reader; |
| | | } |
| | | |
| | | @SuppressWarnings({ "rawtypes", "unchecked" }) |
| | | @Override |
| | | public void addResult(final int messageID, final Result result) throws DecodeException, |
| | |
| | | // bind response. |
| | | final int msgID = ldapConnection.continuePendingBindRequest(promise); |
| | | |
| | | LDAPWriter<ASN1BufferWriter> ldapWriter = GrizzlyUtils.getWriter(); |
| | | LDAPWriter<ASN1BufferWriter> ldapWriter = |
| | | GrizzlyUtils.getWriter(context.getMemoryManager()); |
| | | try { |
| | | final GenericBindRequest nextRequest = |
| | | bindClient.nextBindRequest(); |
| | |
| | | } |
| | | |
| | | @Override |
| | | public final NextAction handleRead(final FilterChainContext ctx) throws IOException { |
| | | final LDAPMessageHandler handler = getLDAPHandler(ctx); |
| | | final Buffer buffer = (Buffer) ctx.getMessage(); |
| | | |
| | | try (final ASN1BufferReader reader = new ASN1BufferReader(maxASN1ElementSize, buffer)) { |
| | | final LDAPReader<? extends ASN1Reader> ldapReader = LDAP.getReader(reader, decodeOptions); |
| | | while (ldapReader.hasMessageAvailable()) { |
| | | ldapReader.readMessage(handler); |
| | | } |
| | | buffer.shrink(); |
| | | } catch (IOException e) { |
| | | handleReadException(ctx, e); |
| | | throw e; |
| | | } |
| | | return ctx.getStopAction(buffer.hasRemaining() ? buffer : null); |
| | | } |
| | | |
| | | private final void handleReadException(FilterChainContext ctx, IOException e) { |
| | | final GrizzlyLDAPConnection ldapConnection = LDAP_CONNECTION_ATTR.get(ctx.getConnection()); |
| | | final Result errorResult = |
| | | Responses.newResult(ResultCode.CLIENT_SIDE_DECODING_ERROR).setCause(e) |
| | | .setDiagnosticMessage(e.getMessage()); |
| | | ldapConnection.close(null, false, errorResult); |
| | | } |
| | | |
| | | @Override |
| | | public NextAction handleClose(final FilterChainContext ctx) throws IOException { |
| | | final Connection<?> connection = ctx.getConnection(); |
| | | final GrizzlyLDAPConnection ldapConnection = LDAP_CONNECTION_ATTR.remove(connection); |
| | |
| | | return ctx.getInvokeAction(); |
| | | } |
| | | |
| | | @Override |
| | | final void handleReadException(FilterChainContext ctx, IOException e) { |
| | | final GrizzlyLDAPConnection ldapConnection = LDAP_CONNECTION_ATTR.get(ctx.getConnection()); |
| | | final Result errorResult = |
| | | Responses.newResult(ResultCode.CLIENT_SIDE_DECODING_ERROR).setCause(e) |
| | | .setDiagnosticMessage(e.getMessage()); |
| | | ldapConnection.close(null, false, errorResult); |
| | | } |
| | | |
| | | /** |
| | | * Returns the response handler associated to the provided connection and |
| | | * context. |
| | |
| | | * @return the response handler associated to the context, which can be a |
| | | * new one if no handler have been created yet |
| | | */ |
| | | @Override |
| | | final LDAPBaseHandler getLDAPHandler(final FilterChainContext ctx) { |
| | | final LDAPMessageHandler getLDAPHandler(final FilterChainContext ctx) { |
| | | Connection<?> connection = ctx.getConnection(); |
| | | ClientResponseHandler handler = RESPONSE_HANDLER_ATTR.get(connection); |
| | | if (handler == null) { |
| | | LDAPReader<ASN1BufferReader> reader = |
| | | GrizzlyUtils.createReader(decodeOptions, maxASN1ElementSize, connection |
| | | .getTransport().getMemoryManager()); |
| | | handler = new ClientResponseHandler(reader); |
| | | handler = new ClientResponseHandler(); |
| | | RESPONSE_HANDLER_ATTR.set(connection, handler); |
| | | } |
| | | handler.setFilterChainContext(ctx); |
| | |
| | | */ |
| | | package org.forgerock.opendj.grizzly; |
| | | |
| | | import static com.forgerock.reactive.RxJavaStreams.*; |
| | | import static org.forgerock.opendj.grizzly.GrizzlyUtils.configureConnection; |
| | | import static org.forgerock.opendj.io.LDAP.*; |
| | | import static org.forgerock.opendj.ldap.spi.LdapMessages.newResponseMessage; |
| | | |
| | | import java.io.IOException; |
| | | import java.net.InetSocketAddress; |
| | | import java.security.GeneralSecurityException; |
| | | import java.util.LinkedList; |
| | | import java.util.List; |
| | | import java.util.concurrent.atomic.AtomicBoolean; |
| | | import java.util.concurrent.atomic.AtomicLong; |
| | | |
| | | import javax.net.ssl.SSLContext; |
| | | import javax.net.ssl.SSLEngine; |
| | | import javax.net.ssl.SSLSession; |
| | | import javax.security.sasl.Sasl; |
| | | import javax.security.sasl.SaslServer; |
| | | |
| | | import org.forgerock.i18n.slf4j.LocalizedLogger; |
| | | import org.forgerock.opendj.io.AbstractLDAPMessageHandler; |
| | | import org.forgerock.opendj.io.LDAP; |
| | | import org.forgerock.opendj.io.LDAPReader; |
| | | import org.forgerock.opendj.io.LDAPWriter; |
| | | import org.forgerock.opendj.ldap.ByteString; |
| | | import org.forgerock.opendj.ldap.ConnectionSecurityLayer; |
| | | import org.forgerock.opendj.ldap.DecodeOptions; |
| | | import org.forgerock.opendj.ldap.IntermediateResponseHandler; |
| | | import org.forgerock.opendj.ldap.LDAPClientContext; |
| | | import org.forgerock.opendj.ldap.LdapException; |
| | | import org.forgerock.opendj.ldap.ResultCode; |
| | | import org.forgerock.opendj.ldap.LdapResultHandler; |
| | | import org.forgerock.opendj.ldap.SSLContextBuilder; |
| | | import org.forgerock.opendj.ldap.SearchResultHandler; |
| | | import org.forgerock.opendj.ldap.ServerConnection; |
| | | import org.forgerock.opendj.ldap.TrustManagers; |
| | | import org.forgerock.opendj.ldap.controls.Control; |
| | | import org.forgerock.opendj.ldap.requests.AbandonRequest; |
| | | import org.forgerock.opendj.ldap.requests.AddRequest; |
| | | import org.forgerock.opendj.ldap.requests.CompareRequest; |
| | | import org.forgerock.opendj.ldap.requests.DeleteRequest; |
| | | import org.forgerock.opendj.ldap.requests.ExtendedRequest; |
| | | import org.forgerock.opendj.ldap.requests.GenericBindRequest; |
| | | import org.forgerock.opendj.ldap.requests.ModifyDNRequest; |
| | | import org.forgerock.opendj.ldap.requests.ModifyRequest; |
| | | import org.forgerock.opendj.ldap.requests.SearchRequest; |
| | | import org.forgerock.opendj.ldap.requests.UnbindRequest; |
| | | import org.forgerock.opendj.ldap.responses.BindResult; |
| | | import org.forgerock.opendj.ldap.responses.CompareResult; |
| | | import org.forgerock.opendj.ldap.responses.ExtendedResult; |
| | | import org.forgerock.opendj.ldap.responses.GenericExtendedResult; |
| | | import org.forgerock.opendj.ldap.responses.IntermediateResponse; |
| | | import org.forgerock.opendj.ldap.responses.Response; |
| | | import org.forgerock.opendj.ldap.responses.Responses; |
| | | import org.forgerock.opendj.ldap.responses.Result; |
| | | import org.forgerock.opendj.ldap.responses.SearchResultEntry; |
| | | import org.forgerock.opendj.ldap.responses.SearchResultReference; |
| | | import org.forgerock.opendj.ldap.spi.LdapMessages; |
| | | import org.forgerock.opendj.ldap.spi.LdapMessages.LdapRawMessage; |
| | | import org.forgerock.opendj.ldap.spi.LdapMessages.LdapResponseMessage; |
| | | import org.forgerock.util.Function; |
| | | import org.forgerock.util.Options; |
| | | import org.forgerock.util.Reject; |
| | | import org.glassfish.grizzly.CloseReason; |
| | | import org.glassfish.grizzly.CompletionHandler; |
| | | import org.glassfish.grizzly.Connection; |
| | | import org.glassfish.grizzly.Grizzly; |
| | | import org.glassfish.grizzly.attributes.Attribute; |
| | | import org.glassfish.grizzly.filterchain.BaseFilter; |
| | | import org.glassfish.grizzly.filterchain.Filter; |
| | | import org.glassfish.grizzly.filterchain.FilterChain; |
| | | import org.glassfish.grizzly.filterchain.FilterChainContext; |
| | | import org.glassfish.grizzly.filterchain.NextAction; |
| | | import org.glassfish.grizzly.ssl.SSLEngineConfigurator; |
| | | import org.glassfish.grizzly.ssl.SSLFilter; |
| | | import org.glassfish.grizzly.ssl.SSLUtils; |
| | | import org.reactivestreams.Publisher; |
| | | import org.reactivestreams.Subscriber; |
| | | import org.reactivestreams.Subscription; |
| | | |
| | | import static org.forgerock.opendj.grizzly.GrizzlyUtils.*; |
| | | import com.forgerock.reactive.Completable; |
| | | import com.forgerock.reactive.Completable.Emitter; |
| | | import com.forgerock.reactive.ReactiveHandler; |
| | | import com.forgerock.reactive.Stream; |
| | | |
| | | import io.reactivex.internal.util.BackpressureHelper; |
| | | |
| | | /** |
| | | * Grizzly filter implementation for decoding LDAP requests and handling server |
| | | * side logic for SSL and SASL operations over LDAP. |
| | | * Grizzly filter implementation for decoding LDAP requests and handling server side logic for SSL and SASL operations |
| | | * over LDAP. |
| | | */ |
| | | final class LDAPServerFilter extends LDAPBaseFilter { |
| | | final class LDAPServerFilter extends BaseFilter { |
| | | |
| | | /** Provides an arbitrary write operation on a LDAP writer. */ |
| | | private interface LDAPWrite<T> { |
| | | void perform(LDAPWriter<ASN1BufferWriter> writer, int messageID, T message) |
| | | throws IOException; |
| | | } |
| | | private static final Attribute<ClientConnectionImpl> LDAP_CONNECTION_ATTR = Grizzly.DEFAULT_ATTRIBUTE_BUILDER |
| | | .createAttribute("LDAPServerConnection"); |
| | | |
| | | /** Write operation for intermediate responses. */ |
| | | private static final LDAPWrite<IntermediateResponse> INTERMEDIATE = |
| | | new LDAPWrite<IntermediateResponse>() { |
| | | @Override |
| | | public void perform(LDAPWriter<ASN1BufferWriter> writer, int messageID, |
| | | IntermediateResponse resp) throws IOException { |
| | | writer.writeIntermediateResponse(messageID, resp); |
| | | } |
| | | }; |
| | | |
| | | private static abstract class AbstractHandler<R extends Result> implements |
| | | IntermediateResponseHandler, LdapResultHandler<R> { |
| | | protected final ClientContextImpl context; |
| | | protected final int messageID; |
| | | |
| | | protected AbstractHandler(final ClientContextImpl context, final int messageID) { |
| | | this.messageID = messageID; |
| | | this.context = context; |
| | | } |
| | | |
| | | @Override |
| | | public void handleResult(final R result) { |
| | | defaultHandleResult(result); |
| | | } |
| | | |
| | | @Override |
| | | public final boolean handleIntermediateResponse(final IntermediateResponse response) { |
| | | writeMessage(INTERMEDIATE, response); |
| | | return true; |
| | | } |
| | | |
| | | /** |
| | | * Default implementation of result handling, that delegate the actual |
| | | * write operation to {@code writeResult} method. |
| | | */ |
| | | private void defaultHandleResult(final R result) { |
| | | writeMessage(new LDAPWrite<R>() { |
| | | @Override |
| | | public void perform(LDAPWriter<ASN1BufferWriter> writer, int messageID, R res) |
| | | throws IOException { |
| | | writeResult(writer, res); |
| | | } |
| | | }, result); |
| | | } |
| | | |
| | | /** |
| | | * Write a result to provided LDAP writer. |
| | | * |
| | | * @param ldapWriter |
| | | * provided writer |
| | | * @param result |
| | | * to write |
| | | * @throws IOException |
| | | * if an error occurs during writing |
| | | */ |
| | | protected abstract void writeResult(final LDAPWriter<ASN1BufferWriter> ldapWriter, |
| | | final R result) throws IOException; |
| | | |
| | | /** |
| | | * Write a message on LDAP writer. |
| | | * |
| | | * @param <T> |
| | | * type of message to write |
| | | * @param ldapWrite |
| | | * the specific write operation |
| | | * @param message |
| | | * the message to write |
| | | */ |
| | | protected final <T> void writeMessage(final LDAPWrite<T> ldapWrite, final T message) { |
| | | final LDAPWriter<ASN1BufferWriter> writer = GrizzlyUtils.getWriter(); |
| | | try { |
| | | ldapWrite.perform(writer, messageID, message); |
| | | context.write(writer); |
| | | } catch (final IOException ioe) { |
| | | context.handleException(ioe); |
| | | } finally { |
| | | GrizzlyUtils.recycleWriter(writer); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * Copy diagnostic message, matched DN and cause to new result from the |
| | | * given result. |
| | | * |
| | | * @param newResult |
| | | * to update |
| | | * @param result |
| | | * contains parameters to copy |
| | | */ |
| | | protected final void populateNewResultFromResult(final R newResult, final Result result) { |
| | | newResult.setDiagnosticMessage(result.getDiagnosticMessage()); |
| | | newResult.setMatchedDN(result.getMatchedDN()); |
| | | newResult.setCause(result.getCause()); |
| | | for (final Control control : result.getControls()) { |
| | | newResult.addControl(control); |
| | | } |
| | | } |
| | | } |
| | | |
| | | private static final class AddHandler extends AbstractHandler<Result> { |
| | | private AddHandler(final ClientContextImpl context, final int messageID) { |
| | | super(context, messageID); |
| | | } |
| | | |
| | | @Override |
| | | public void handleException(final LdapException error) { |
| | | handleResult(error.getResult()); |
| | | } |
| | | |
| | | @Override |
| | | public void writeResult(LDAPWriter<ASN1BufferWriter> writer, final Result result) |
| | | throws IOException { |
| | | writer.writeAddResult(messageID, result); |
| | | } |
| | | } |
| | | |
| | | private static final class BindHandler extends AbstractHandler<BindResult> { |
| | | private BindHandler(final ClientContextImpl context, final int messageID) { |
| | | super(context, messageID); |
| | | } |
| | | |
| | | @Override |
| | | public void handleException(final LdapException error) { |
| | | final Result result = error.getResult(); |
| | | if (result instanceof BindResult) { |
| | | handleResult((BindResult) result); |
| | | } else { |
| | | final BindResult newResult = Responses.newBindResult(result.getResultCode()); |
| | | populateNewResultFromResult(newResult, result); |
| | | handleResult(newResult); |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | protected void writeResult(LDAPWriter<ASN1BufferWriter> writer, BindResult result) |
| | | throws IOException { |
| | | writer.writeBindResult(messageID, result); |
| | | } |
| | | } |
| | | |
| | | private static final class ClientContextImpl implements LDAPClientContext { |
| | | private final Connection<?> connection; |
| | | private final AtomicBoolean isClosed = new AtomicBoolean(); |
| | | private ServerConnection<Integer> serverConnection; |
| | | |
| | | private ClientContextImpl(final Connection<?> connection) { |
| | | this.connection = connection; |
| | | } |
| | | |
| | | @Override |
| | | public void disconnect() { |
| | | disconnect0(null, null); |
| | | } |
| | | |
| | | @Override |
| | | public void disconnect(final ResultCode resultCode, final String message) { |
| | | Reject.ifNull(resultCode); |
| | | final GenericExtendedResult notification = |
| | | Responses.newGenericExtendedResult(resultCode).setOID( |
| | | LDAP.OID_NOTICE_OF_DISCONNECTION).setDiagnosticMessage(message); |
| | | sendUnsolicitedNotification(notification); |
| | | disconnect0(resultCode, message); |
| | | } |
| | | |
| | | @Override |
| | | public void enableConnectionSecurityLayer(final ConnectionSecurityLayer layer) { |
| | | synchronized (this) { |
| | | installFilter(new ConnectionSecurityLayerFilter(layer, connection.getTransport() |
| | | .getMemoryManager())); |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | public void enableTLS(final SSLContext sslContext, final String[] protocols, |
| | | final String[] suites, final boolean wantClientAuth, final boolean needClientAuth) { |
| | | Reject.ifNull(sslContext); |
| | | synchronized (this) { |
| | | if (isTLSEnabled()) { |
| | | throw new IllegalStateException("TLS already enabled"); |
| | | } |
| | | |
| | | final SSLEngineConfigurator sslEngineConfigurator = |
| | | new SSLEngineConfigurator(sslContext, false, false, false); |
| | | sslEngineConfigurator.setEnabledCipherSuites(suites); |
| | | sslEngineConfigurator.setEnabledProtocols(protocols); |
| | | sslEngineConfigurator.setWantClientAuth(wantClientAuth); |
| | | sslEngineConfigurator.setNeedClientAuth(needClientAuth); |
| | | installFilter(new SSLFilter(sslEngineConfigurator, DUMMY_SSL_ENGINE_CONFIGURATOR)); |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | public InetSocketAddress getLocalAddress() { |
| | | return (InetSocketAddress) connection.getLocalAddress(); |
| | | } |
| | | |
| | | @Override |
| | | public InetSocketAddress getPeerAddress() { |
| | | return (InetSocketAddress) connection.getPeerAddress(); |
| | | } |
| | | |
| | | @Override |
| | | public int getSecurityStrengthFactor() { |
| | | final SSLSession sslSession = getSSLSession(); |
| | | if (sslSession != null) { |
| | | final String cipherString = sslSession.getCipherSuite(); |
| | | for (final Object[] cipher : CIPHER_KEY_SIZES) { |
| | | if (cipherString.contains((String) cipher[0])) { |
| | | return (Integer) cipher[1]; |
| | | } |
| | | } |
| | | } |
| | | return 0; |
| | | } |
| | | |
| | | @Override |
| | | public SSLSession getSSLSession() { |
| | | final SSLEngine sslEngine = SSLUtils.getSSLEngine(connection); |
| | | return sslEngine != null ? sslEngine.getSession() : null; |
| | | } |
| | | |
| | | @Override |
| | | public boolean isClosed() { |
| | | return isClosed.get(); |
| | | } |
| | | |
| | | @Override |
| | | public void sendUnsolicitedNotification(final ExtendedResult notification) { |
| | | LDAPWriter<ASN1BufferWriter> writer = GrizzlyUtils.getWriter(); |
| | | try { |
| | | writer.writeExtendedResult(0, notification); |
| | | connection.write(writer.getASN1Writer().getBuffer(), null); |
| | | } catch (final IOException ioe) { |
| | | handleException(ioe); |
| | | } finally { |
| | | GrizzlyUtils.recycleWriter(writer); |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | public String toString() { |
| | | final StringBuilder builder = new StringBuilder(); |
| | | builder.append("LDAPClientContext("); |
| | | builder.append(getLocalAddress()); |
| | | builder.append(','); |
| | | builder.append(getPeerAddress()); |
| | | builder.append(')'); |
| | | return builder.toString(); |
| | | } |
| | | |
| | | public void write(final LDAPWriter<ASN1BufferWriter> writer) { |
| | | connection.write(writer.getASN1Writer().getBuffer(), null); |
| | | } |
| | | |
| | | private void disconnect0(final ResultCode resultCode, final String message) { |
| | | // Close this connection context. |
| | | if (isClosed.compareAndSet(false, true)) { |
| | | try { |
| | | // Notify the server connection: it may be null if disconnect is |
| | | // invoked during accept. |
| | | if (serverConnection != null) { |
| | | serverConnection.handleConnectionDisconnected(resultCode, message); |
| | | } |
| | | } finally { |
| | | // Close the connection. |
| | | connection.closeSilently(); |
| | | } |
| | | } |
| | | } |
| | | |
| | | private ServerConnection<Integer> getServerConnection() { |
| | | return serverConnection; |
| | | } |
| | | |
| | | private void handleClose(final int messageID, final UnbindRequest unbindRequest) { |
| | | // Close this connection context. |
| | | if (isClosed.compareAndSet(false, true)) { |
| | | try { |
| | | // Notify the server connection: it may be null if disconnect is |
| | | // invoked during accept. |
| | | if (serverConnection != null) { |
| | | serverConnection.handleConnectionClosed(messageID, unbindRequest); |
| | | } |
| | | } finally { |
| | | // If this close was a result of an unbind request then the |
| | | // connection won't actually be closed yet. To avoid TIME_WAIT TCP |
| | | // state, let the client disconnect. |
| | | if (unbindRequest != null) { |
| | | return; |
| | | } |
| | | |
| | | // Close the connection. |
| | | connection.closeSilently(); |
| | | } |
| | | } |
| | | } |
| | | |
| | | private void handleException(final Throwable error) { |
| | | // Close this connection context. |
| | | if (isClosed.compareAndSet(false, true)) { |
| | | try { |
| | | // Notify the server connection: it may be null if disconnect is |
| | | // invoked during accept. |
| | | if (serverConnection != null) { |
| | | serverConnection.handleConnectionError(error); |
| | | } |
| | | } finally { |
| | | // Close the connection. |
| | | connection.closeSilently(); |
| | | } |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * Installs a new Grizzly filter (e.g. SSL/SASL) beneath the top-level |
| | | * LDAP filter. |
| | | * |
| | | * @param filter |
| | | * The filter to be installed. |
| | | */ |
| | | private void installFilter(final Filter filter) { |
| | | GrizzlyUtils.addFilterToConnection(filter, connection); |
| | | } |
| | | |
| | | /** |
| | | * Indicates whether TLS is enabled this provided connection. |
| | | * |
| | | * @return {@code true} if TLS is enabled on this connection, otherwise |
| | | * {@code false}. |
| | | */ |
| | | private boolean isTLSEnabled() { |
| | | synchronized (this) { |
| | | final FilterChain currentFilterChain = (FilterChain) connection.getProcessor(); |
| | | for (final Filter filter : currentFilterChain) { |
| | | if (filter instanceof SSLFilter) { |
| | | return true; |
| | | } |
| | | } |
| | | return false; |
| | | } |
| | | } |
| | | |
| | | private void setServerConnection(final ServerConnection<Integer> serverConnection) { |
| | | this.serverConnection = serverConnection; |
| | | } |
| | | } |
| | | |
| | | private static final class CompareHandler extends AbstractHandler<CompareResult> { |
| | | private CompareHandler(final ClientContextImpl context, final int messageID) { |
| | | super(context, messageID); |
| | | } |
| | | |
| | | @Override |
| | | public void handleException(final LdapException error) { |
| | | final Result result = error.getResult(); |
| | | if (result instanceof CompareResult) { |
| | | handleResult((CompareResult) result); |
| | | } else { |
| | | final CompareResult newResult = Responses.newCompareResult(result.getResultCode()); |
| | | populateNewResultFromResult(newResult, result); |
| | | handleResult(newResult); |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | protected void writeResult(LDAPWriter<ASN1BufferWriter> writer, CompareResult result) |
| | | throws IOException { |
| | | writer.writeCompareResult(messageID, result); |
| | | } |
| | | } |
| | | |
| | | private static final class DeleteHandler extends AbstractHandler<Result> { |
| | | private DeleteHandler(final ClientContextImpl context, final int messageID) { |
| | | super(context, messageID); |
| | | } |
| | | |
| | | @Override |
| | | public void handleException(final LdapException error) { |
| | | handleResult(error.getResult()); |
| | | } |
| | | |
| | | @Override |
| | | protected void writeResult(LDAPWriter<ASN1BufferWriter> writer, Result result) |
| | | throws IOException { |
| | | writer.writeDeleteResult(messageID, result); |
| | | } |
| | | } |
| | | |
| | | private static final class ExtendedHandler<R extends ExtendedResult> extends AbstractHandler<R> { |
| | | private ExtendedHandler(final ClientContextImpl context, final int messageID) { |
| | | super(context, messageID); |
| | | } |
| | | |
| | | @Override |
| | | public void handleException(final LdapException error) { |
| | | final Result result = error.getResult(); |
| | | if (result instanceof ExtendedResult) { |
| | | handleResult((ExtendedResult) result); |
| | | } else { |
| | | final ExtendedResult newResult = |
| | | Responses.newGenericExtendedResult(result.getResultCode()); |
| | | newResult.setDiagnosticMessage(result.getDiagnosticMessage()); |
| | | newResult.setMatchedDN(result.getMatchedDN()); |
| | | newResult.setCause(result.getCause()); |
| | | for (final Control control : result.getControls()) { |
| | | newResult.addControl(control); |
| | | } |
| | | handleResult(newResult); |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | public void handleResult(final ExtendedResult result) { |
| | | writeMessage(new LDAPWrite<ExtendedResult>() { |
| | | @Override |
| | | public void perform(LDAPWriter<ASN1BufferWriter> writer, int messageID, |
| | | ExtendedResult message) throws IOException { |
| | | writer.writeExtendedResult(messageID, message); |
| | | } |
| | | }, result); |
| | | } |
| | | |
| | | @Override |
| | | protected void writeResult(LDAPWriter<ASN1BufferWriter> ldapWriter, R result) |
| | | throws IOException { |
| | | // never called because handleResult(result) method is overriden in this class |
| | | } |
| | | } |
| | | |
| | | private static final class ModifyDNHandler extends AbstractHandler<Result> { |
| | | private ModifyDNHandler(final ClientContextImpl context, final int messageID) { |
| | | super(context, messageID); |
| | | } |
| | | |
| | | @Override |
| | | public void handleException(final LdapException error) { |
| | | handleResult(error.getResult()); |
| | | } |
| | | |
| | | @Override |
| | | protected void writeResult(LDAPWriter<ASN1BufferWriter> writer, Result result) |
| | | throws IOException { |
| | | writer.writeModifyDNResult(messageID, result); |
| | | } |
| | | } |
| | | |
| | | private static final class ModifyHandler extends AbstractHandler<Result> { |
| | | private ModifyHandler(final ClientContextImpl context, final int messageID) { |
| | | super(context, messageID); |
| | | } |
| | | |
| | | @Override |
| | | public void handleException(final LdapException error) { |
| | | handleResult(error.getResult()); |
| | | } |
| | | |
| | | @Override |
| | | protected void writeResult(LDAPWriter<ASN1BufferWriter> writer, Result result) |
| | | throws IOException { |
| | | writer.writeModifyResult(messageID, result); |
| | | } |
| | | } |
| | | |
| | | private static final class SearchHandler extends AbstractHandler<Result> implements |
| | | SearchResultHandler { |
| | | private SearchHandler(final ClientContextImpl context, final int messageID) { |
| | | super(context, messageID); |
| | | } |
| | | |
| | | @Override |
| | | public boolean handleEntry(final SearchResultEntry entry) { |
| | | writeMessage(new LDAPWrite<SearchResultEntry>() { |
| | | @Override |
| | | public void perform(LDAPWriter<ASN1BufferWriter> writer, int messageID, |
| | | SearchResultEntry sre) throws IOException { |
| | | writer.writeSearchResultEntry(messageID, sre); |
| | | } |
| | | }, entry); |
| | | return true; |
| | | } |
| | | |
| | | @Override |
| | | public void handleException(final LdapException error) { |
| | | handleResult(error.getResult()); |
| | | } |
| | | |
| | | @Override |
| | | public boolean handleReference(final SearchResultReference reference) { |
| | | writeMessage(new LDAPWrite<SearchResultReference>() { |
| | | @Override |
| | | public void perform(LDAPWriter<ASN1BufferWriter> writer, int messageID, |
| | | SearchResultReference ref) throws IOException { |
| | | writer.writeSearchResultReference(messageID, ref); |
| | | } |
| | | }, reference); |
| | | return true; |
| | | } |
| | | |
| | | @Override |
| | | protected void writeResult(LDAPWriter<ASN1BufferWriter> writer, Result result) |
| | | throws IOException { |
| | | writer.writeSearchResult(messageID, result); |
| | | } |
| | | } |
| | | // @formatter:off |
| | | private final Function<LDAPClientContext, |
| | | ReactiveHandler<LDAPClientContext, LdapRawMessage, Stream<Response>>, |
| | | LdapException> connectionHandler; |
| | | |
| | | /** |
| | | * Map of cipher phrases to effective key size (bits). Taken from the |
| | |
| | | { "_WITH_NULL_", 0 }, |
| | | }; |
| | | |
| | | // @formatter:on |
| | | /** Default maximum request size for incoming requests. */ |
| | | private static final int DEFAULT_MAX_REQUEST_SIZE = 5 * 1024 * 1024; |
| | | |
| | | private static final Attribute<ClientContextImpl> LDAP_CONNECTION_ATTR = |
| | | Grizzly.DEFAULT_ATTRIBUTE_BUILDER.createAttribute("LDAPServerConnection"); |
| | | |
| | | private static final Attribute<ServerRequestHandler> REQUEST_HANDLER_ATTR = |
| | | Grizzly.DEFAULT_ATTRIBUTE_BUILDER.createAttribute("ServerRequestHandler"); |
| | | |
| | | private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass(); |
| | | |
| | | /** |
| | | * A dummy SSL client engine configurator as SSLFilter only needs server |
| | | * config. This prevents Grizzly from needlessly using JVM defaults which |
| | | * may be incorrectly configured. |
| | | */ |
| | | private static final SSLEngineConfigurator DUMMY_SSL_ENGINE_CONFIGURATOR; |
| | | static { |
| | | try { |
| | | DUMMY_SSL_ENGINE_CONFIGURATOR = |
| | | new SSLEngineConfigurator(new SSLContextBuilder().setTrustManager( |
| | | TrustManagers.distrustAll()).getSSLContext()); |
| | | } catch (GeneralSecurityException e) { |
| | | // This should never happen. |
| | | throw new IllegalStateException("Unable to create Dummy SSL Engine Configurator", e); |
| | | } |
| | | } |
| | | |
| | | private final GrizzlyLDAPListener listener; |
| | | |
| | | private static final class ServerRequestHandler extends AbstractLDAPMessageHandler implements |
| | | LDAPBaseHandler { |
| | | private final Connection<?> connection; |
| | | private final LDAPReader<ASN1BufferReader> reader; |
| | | |
| | | /** |
| | | * Creates the handler with a connection. |
| | | * |
| | | * @param connection |
| | | * connection this handler is associated with |
| | | * @param reader |
| | | * LDAP reader to use for reading incoming messages |
| | | */ |
| | | ServerRequestHandler(Connection<?> connection, LDAPReader<ASN1BufferReader> reader) { |
| | | this.connection = connection; |
| | | this.reader = reader; |
| | | } |
| | | |
| | | /** |
| | | * Returns the LDAP reader. |
| | | * |
| | | * @return the reader to read incoming LDAP messages |
| | | */ |
| | | @Override |
| | | public LDAPReader<ASN1BufferReader> getReader() { |
| | | return reader; |
| | | } |
| | | |
| | | @Override |
| | | public void abandonRequest(final int messageID, final AbandonRequest request) { |
| | | final ClientContextImpl clientContext = LDAP_CONNECTION_ATTR.get(connection); |
| | | if (clientContext != null) { |
| | | final ServerConnection<Integer> conn = clientContext.getServerConnection(); |
| | | conn.handleAbandon(messageID, request); |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | public void addRequest(final int messageID, final AddRequest request) { |
| | | final ClientContextImpl clientContext = LDAP_CONNECTION_ATTR.get(connection); |
| | | if (clientContext != null) { |
| | | final ServerConnection<Integer> conn = clientContext.getServerConnection(); |
| | | final AddHandler handler = new AddHandler(clientContext, messageID); |
| | | conn.handleAdd(messageID, request, handler, handler); |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | public void bindRequest(final int messageID, final int version, |
| | | final GenericBindRequest request) { |
| | | final ClientContextImpl clientContext = LDAP_CONNECTION_ATTR.get(connection); |
| | | if (clientContext != null) { |
| | | final ServerConnection<Integer> conn = clientContext.getServerConnection(); |
| | | final AbstractHandler<BindResult> handler = |
| | | new BindHandler(clientContext, messageID); |
| | | conn.handleBind(messageID, version, request, handler, handler); |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | public void compareRequest(final int messageID, final CompareRequest request) { |
| | | final ClientContextImpl clientContext = LDAP_CONNECTION_ATTR.get(connection); |
| | | if (clientContext != null) { |
| | | final ServerConnection<Integer> conn = clientContext.getServerConnection(); |
| | | final CompareHandler handler = new CompareHandler(clientContext, messageID); |
| | | conn.handleCompare(messageID, request, handler, handler); |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | public void deleteRequest(final int messageID, final DeleteRequest request) { |
| | | final ClientContextImpl clientContext = LDAP_CONNECTION_ATTR.get(connection); |
| | | if (clientContext != null) { |
| | | final ServerConnection<Integer> conn = clientContext.getServerConnection(); |
| | | final DeleteHandler handler = new DeleteHandler(clientContext, messageID); |
| | | conn.handleDelete(messageID, request, handler, handler); |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | public <R extends ExtendedResult> void extendedRequest(final int messageID, |
| | | final ExtendedRequest<R> request) { |
| | | final ClientContextImpl clientContext = LDAP_CONNECTION_ATTR.get(connection); |
| | | if (clientContext != null) { |
| | | final ServerConnection<Integer> conn = clientContext.getServerConnection(); |
| | | final ExtendedHandler<R> handler = new ExtendedHandler<>(clientContext, messageID); |
| | | conn.handleExtendedRequest(messageID, request, handler, handler); |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | public void modifyDNRequest(final int messageID, final ModifyDNRequest request) { |
| | | final ClientContextImpl clientContext = LDAP_CONNECTION_ATTR.get(connection); |
| | | if (clientContext != null) { |
| | | final ServerConnection<Integer> conn = clientContext.getServerConnection(); |
| | | final ModifyDNHandler handler = new ModifyDNHandler(clientContext, messageID); |
| | | conn.handleModifyDN(messageID, request, handler, handler); |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | public void modifyRequest(final int messageID, final ModifyRequest request) { |
| | | final ClientContextImpl clientContext = LDAP_CONNECTION_ATTR.get(connection); |
| | | if (clientContext != null) { |
| | | final ServerConnection<Integer> conn = clientContext.getServerConnection(); |
| | | final ModifyHandler handler = new ModifyHandler(clientContext, messageID); |
| | | conn.handleModify(messageID, request, handler, handler); |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | public void searchRequest(final int messageID, final SearchRequest request) { |
| | | final ClientContextImpl clientContext = LDAP_CONNECTION_ATTR.get(connection); |
| | | if (clientContext != null) { |
| | | final ServerConnection<Integer> conn = clientContext.getServerConnection(); |
| | | final SearchHandler handler = new SearchHandler(clientContext, messageID); |
| | | conn.handleSearch(messageID, request, handler, handler, handler); |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | public void unbindRequest(final int messageID, final UnbindRequest request) { |
| | | // Remove the client context causing any subsequent LDAP |
| | | // traffic to be ignored. |
| | | final ClientContextImpl clientContext = LDAP_CONNECTION_ATTR.remove(connection); |
| | | if (clientContext != null) { |
| | | clientContext.handleClose(messageID, request); |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | public void unrecognizedMessage(final int messageID, final byte messageTag, |
| | | final ByteString messageBytes) { |
| | | exceptionOccurred(connection, newUnsupportedMessageException(messageID, messageTag, |
| | | messageBytes)); |
| | | } |
| | | } |
| | | private final int maxConcurrentRequests; |
| | | private final Options connectionOptions; |
| | | |
| | | /** |
| | | * Creates a server filter with provided listener, options and max size of |
| | | * ASN1 element. |
| | | * Creates a server filter with provided listener, options and max size of ASN1 element. |
| | | * |
| | | * @param listener |
| | | * listen for incoming connections |
| | | * @param options |
| | | * control how to decode requests and responses |
| | | * @param maxASN1ElementSize |
| | | * The maximum BER element size, or <code>0</code> to indicate |
| | | * that there is no limit. |
| | | * The maximum BER element size, or <code>0</code> to indicate that there is no limit. |
| | | */ |
| | | LDAPServerFilter(final GrizzlyLDAPListener listener, final DecodeOptions options, |
| | | final int maxASN1ElementSize) { |
| | | super(options, maxASN1ElementSize <= 0 ? DEFAULT_MAX_REQUEST_SIZE : maxASN1ElementSize); |
| | | this.listener = listener; |
| | | LDAPServerFilter( |
| | | Function<LDAPClientContext, |
| | | ReactiveHandler<LDAPClientContext, LdapRawMessage, Stream<Response>>, |
| | | LdapException> connectionHandler, |
| | | final Options connectionOptions, final DecodeOptions options, final int maxPendingRequests) { |
| | | this.connectionHandler = connectionHandler; |
| | | this.connectionOptions = connectionOptions; |
| | | this.maxConcurrentRequests = maxPendingRequests; |
| | | } |
| | | |
| | | @Override |
| | | public void exceptionOccurred(final FilterChainContext ctx, final Throwable error) { |
| | | exceptionOccurred(ctx.getConnection(), error); |
| | | } |
| | | |
| | | private static void exceptionOccurred(final Connection<?> connection, final Throwable error) { |
| | | final ClientContextImpl clientContext = LDAP_CONNECTION_ATTR.remove(connection); |
| | | if (clientContext != null) { |
| | | clientContext.handleException(error); |
| | | } |
| | | LDAP_CONNECTION_ATTR.remove(ctx.getConnection()).upstream.onError(error); |
| | | } |
| | | |
| | | @Override |
| | | public NextAction handleAccept(final FilterChainContext ctx) throws IOException { |
| | | final Connection<?> connection = ctx.getConnection(); |
| | | Options options = listener.getLDAPListenerOptions(); |
| | | configureConnection(connection, logger, options); |
| | | try { |
| | | final ClientContextImpl clientContext = new ClientContextImpl(connection); |
| | | final ServerConnection<Integer> serverConn = |
| | | listener.getConnectionFactory().handleAccept(clientContext); |
| | | clientContext.setServerConnection(serverConn); |
| | | LDAP_CONNECTION_ATTR.set(connection, clientContext); |
| | | } catch (final LdapException e) { |
| | | connection.close(); |
| | | } |
| | | configureConnection(connection, logger, connectionOptions); |
| | | final ClientConnectionImpl clientContext = new ClientConnectionImpl(connection); |
| | | LDAP_CONNECTION_ATTR.set(connection, clientContext); |
| | | final ReactiveHandler<LDAPClientContext, LdapRawMessage, Stream<Response>> handler = connectionHandler |
| | | .apply(clientContext); |
| | | |
| | | streamFromPublisher(clientContext).flatMap(new Function<LdapRawMessage, Publisher<Integer>, Exception>() { |
| | | @Override |
| | | public Publisher<Integer> apply(final LdapRawMessage rawRequest) throws Exception { |
| | | return handler |
| | | .handle(clientContext, rawRequest) |
| | | .flatMap(new Function<Stream<Response>, Publisher<Integer>, Exception>() { |
| | | @Override |
| | | public Publisher<Integer> apply(final Stream<Response> response) { |
| | | return clientContext.write(response.onErrorResumeWith(toErrorMessage(rawRequest)) |
| | | .map(toResponseMessage(rawRequest))).toSingle(1); |
| | | } |
| | | }); |
| | | } |
| | | }, maxConcurrentRequests).subscribe(); |
| | | return ctx.getStopAction(); |
| | | } |
| | | |
| | | private Function<Throwable, Publisher<Response>, Exception> toErrorMessage(final LdapRawMessage rawRequest) { |
| | | return new Function<Throwable, Publisher<Response>, Exception>() { |
| | | @Override |
| | | public Publisher<Response> apply(Throwable error) throws Exception { |
| | | if (!(error instanceof LdapException)) { |
| | | // Propagate error |
| | | return streamError(error); |
| | | } |
| | | |
| | | final Result result = ((LdapException) error).getResult(); |
| | | switch (rawRequest.getMessageType()) { |
| | | case OP_TYPE_BIND_REQUEST: |
| | | if (result instanceof BindResult) { |
| | | return streamFrom((Response) result); |
| | | } |
| | | return streamFrom((Response) populateNewResultFromResult( |
| | | Responses.newBindResult(result.getResultCode()), result)); |
| | | case OP_TYPE_COMPARE_REQUEST: |
| | | if (result instanceof CompareResult) { |
| | | return streamFrom((Response) result); |
| | | } |
| | | return streamFrom((Response) populateNewResultFromResult( |
| | | Responses.newCompareResult(result.getResultCode()), result)); |
| | | case OP_TYPE_EXTENDED_REQUEST: |
| | | if (result instanceof ExtendedResult) { |
| | | return streamFrom((Response) result); |
| | | } |
| | | return streamFrom((Response) populateNewResultFromResult( |
| | | Responses.newGenericExtendedResult(result.getResultCode()), result)); |
| | | default: |
| | | return streamFrom((Response) result); |
| | | } |
| | | } |
| | | }; |
| | | } |
| | | |
| | | private Result populateNewResultFromResult(final Result newResult, |
| | | final Result result) { |
| | | newResult.setDiagnosticMessage(result.getDiagnosticMessage()); |
| | | newResult.setMatchedDN(result.getMatchedDN()); |
| | | newResult.setCause(result.getCause()); |
| | | for (final Control control : result.getControls()) { |
| | | newResult.addControl(control); |
| | | } |
| | | return newResult; |
| | | } |
| | | |
| | | private Function<Response, LdapResponseMessage, Exception> toResponseMessage(final LdapRawMessage rawRequest) { |
| | | return new Function<Response, LdapResponseMessage, Exception>() { |
| | | @Override |
| | | public LdapResponseMessage apply(final Response response) { |
| | | if (response instanceof Result) { |
| | | switch (rawRequest.getMessageType()) { |
| | | case OP_TYPE_ADD_REQUEST: |
| | | return newResponseMessage(OP_TYPE_ADD_RESPONSE, rawRequest.getMessageId(), response); |
| | | case OP_TYPE_BIND_REQUEST: |
| | | return newResponseMessage(OP_TYPE_BIND_RESPONSE, rawRequest.getMessageId(), response); |
| | | case OP_TYPE_COMPARE_REQUEST: |
| | | return newResponseMessage(OP_TYPE_COMPARE_RESPONSE, rawRequest.getMessageId(), response); |
| | | case OP_TYPE_DELETE_REQUEST: |
| | | return newResponseMessage(OP_TYPE_DELETE_RESPONSE, rawRequest.getMessageId(), response); |
| | | case OP_TYPE_EXTENDED_REQUEST: |
| | | return newResponseMessage(OP_TYPE_EXTENDED_RESPONSE, rawRequest.getMessageId(), response); |
| | | case OP_TYPE_MODIFY_DN_REQUEST: |
| | | return newResponseMessage(OP_TYPE_MODIFY_DN_RESPONSE, rawRequest.getMessageId(), response); |
| | | case OP_TYPE_MODIFY_REQUEST: |
| | | return newResponseMessage(OP_TYPE_MODIFY_RESPONSE, rawRequest.getMessageId(), response); |
| | | case OP_TYPE_SEARCH_REQUEST: |
| | | return newResponseMessage(OP_TYPE_SEARCH_RESULT_DONE, rawRequest.getMessageId(), response); |
| | | default: |
| | | throw new IllegalArgumentException("Unknown request: " + rawRequest.getMessageType()); |
| | | } |
| | | } |
| | | if (response instanceof IntermediateResponse) { |
| | | return newResponseMessage(OP_TYPE_INTERMEDIATE_RESPONSE, rawRequest.getMessageId(), response); |
| | | } |
| | | if (response instanceof SearchResultEntry) { |
| | | return newResponseMessage(OP_TYPE_SEARCH_RESULT_ENTRY, rawRequest.getMessageId(), response); |
| | | } |
| | | if (response instanceof SearchResultReference) { |
| | | return newResponseMessage(OP_TYPE_SEARCH_RESULT_REFERENCE, rawRequest.getMessageId(), response); |
| | | } |
| | | throw new IllegalArgumentException(); |
| | | } |
| | | }; |
| | | } |
| | | |
| | | @Override |
| | | public NextAction handleRead(final FilterChainContext ctx) throws IOException { |
| | | final ClientConnectionImpl sub = LDAP_CONNECTION_ATTR.get(ctx.getConnection()); |
| | | return sub.upstream.handleRead(ctx); |
| | | } |
| | | |
| | | @Override |
| | | public NextAction handleClose(final FilterChainContext ctx) throws IOException { |
| | | final ClientContextImpl clientContext = LDAP_CONNECTION_ATTR.remove(ctx.getConnection()); |
| | | if (clientContext != null) { |
| | | clientContext.handleClose(-1, null); |
| | | final ClientConnectionImpl clientContext = LDAP_CONNECTION_ATTR.remove(ctx.getConnection()); |
| | | if (clientContext != null && clientContext.upstream != null) { |
| | | clientContext.upstream.onComplete(); |
| | | } |
| | | return ctx.getStopAction(); |
| | | } |
| | | |
| | | @Override |
| | | final void handleReadException(FilterChainContext ctx, IOException e) { |
| | | exceptionOccurred(ctx, e); |
| | | } |
| | | final class ClientConnectionImpl implements LDAPClientContext, Publisher<LdapRawMessage> { |
| | | |
| | | /** |
| | | * Returns the request handler associated to a connection. |
| | | * <p> |
| | | * If no handler exists yet for this context, a new one is created and |
| | | * recorded for the context. |
| | | * |
| | | * @param ctx |
| | | * Context |
| | | * @return the response handler associated to the context, which can be a |
| | | * new one if no handler have been created yet |
| | | */ |
| | | @Override |
| | | final LDAPBaseHandler getLDAPHandler(final FilterChainContext ctx) { |
| | | Connection<?> connection = ctx.getConnection(); |
| | | ServerRequestHandler handler = REQUEST_HANDLER_ATTR.get(connection); |
| | | if (handler == null) { |
| | | LDAPReader<ASN1BufferReader> reader = |
| | | GrizzlyUtils.createReader(decodeOptions, maxASN1ElementSize, connection |
| | | .getTransport().getMemoryManager()); |
| | | handler = new ServerRequestHandler(connection, reader); |
| | | REQUEST_HANDLER_ATTR.set(connection, handler); |
| | | final class GrizzlyBackpressureSubscription implements Subscription { |
| | | private final AtomicLong pendingRequests = new AtomicLong(); |
| | | private Subscriber<? super LdapRawMessage> subscriber; |
| | | volatile FilterChainContext ctx; |
| | | |
| | | GrizzlyBackpressureSubscription(Subscriber<? super LdapRawMessage> subscriber) { |
| | | this.subscriber = subscriber; |
| | | subscriber.onSubscribe(this); |
| | | } |
| | | |
| | | NextAction handleRead(final FilterChainContext ctx) { |
| | | final Subscriber<? super LdapRawMessage> sub = subscriber; |
| | | if (sub == null) { |
| | | // Subscription cancelled. Stop reading |
| | | ctx.suspend(); |
| | | return ctx.getSuspendAction(); |
| | | } |
| | | sub.onNext((LdapRawMessage) ctx.getMessage()); |
| | | if (BackpressureHelper.produced(pendingRequests, 1) > 0) { |
| | | return ctx.getStopAction(); |
| | | } |
| | | this.ctx = ctx; |
| | | ctx.suspend(); |
| | | return ctx.getSuspendAction(); |
| | | } |
| | | |
| | | @Override |
| | | public void request(long n) { |
| | | if (BackpressureHelper.add(pendingRequests, n) == 0 && ctx != null) { |
| | | ctx.resumeNext(); |
| | | ctx = null; |
| | | } |
| | | } |
| | | |
| | | public void onError(Throwable t) { |
| | | final Subscriber<? super LdapRawMessage> sub = subscriber; |
| | | if (sub != null) { |
| | | subscriber = null; |
| | | sub.onError(t); |
| | | } |
| | | } |
| | | |
| | | public void onComplete() { |
| | | final Subscriber<? super LdapRawMessage> sub = subscriber; |
| | | if (sub != null) { |
| | | subscriber = null; |
| | | sub.onComplete(); |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | public void cancel() { |
| | | subscriber = null; |
| | | } |
| | | } |
| | | return handler; |
| | | |
| | | private final Connection<?> connection; |
| | | private final AtomicBoolean isClosed = new AtomicBoolean(false); |
| | | private final List<DisconnectListener> listeners = new LinkedList<>(); |
| | | GrizzlyBackpressureSubscription upstream; |
| | | |
| | | private ClientConnectionImpl(final Connection<?> connection) { |
| | | this.connection = connection; |
| | | connection.closeFuture().addCompletionHandler(new CompletionHandler<CloseReason>() { |
| | | @Override |
| | | public void completed(CloseReason result) { |
| | | disconnect0(null, null); |
| | | } |
| | | |
| | | @Override |
| | | public void updated(CloseReason result) { |
| | | } |
| | | |
| | | @Override |
| | | public void failed(Throwable throwable) { |
| | | } |
| | | |
| | | @Override |
| | | public void cancelled() { |
| | | } |
| | | }); |
| | | } |
| | | |
| | | @Override |
| | | public void subscribe(final Subscriber<? super LdapRawMessage> s) { |
| | | if (upstream != null) { |
| | | return; |
| | | } |
| | | this.upstream = new GrizzlyBackpressureSubscription(s); |
| | | } |
| | | |
| | | Completable write(final Publisher<LdapResponseMessage> messages) { |
| | | return newCompletable(new Completable.OnSubscribe() { |
| | | @Override |
| | | public void onSubscribe(Emitter e) { |
| | | messages.subscribe(new LdapResponseMessageWriter(connection, e)); |
| | | } |
| | | }); |
| | | } |
| | | |
| | | @Override |
| | | public void enableTLS(final SSLEngine sslEngine) { |
| | | Reject.ifNull(sslEngine); |
| | | synchronized (this) { |
| | | if (isTLSEnabled()) { |
| | | throw new IllegalStateException("TLS already enabled"); |
| | | } |
| | | SSLUtils.setSSLEngine(connection, sslEngine); |
| | | installFilter(new SSLFilter()); |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | public void enableSASL(final SaslServer saslServer) { |
| | | SaslUtils.setSaslServer(connection, saslServer); |
| | | installFilter(new SaslFilter()); |
| | | } |
| | | |
| | | @Override |
| | | public InetSocketAddress getLocalAddress() { |
| | | return (InetSocketAddress) connection.getLocalAddress(); |
| | | } |
| | | |
| | | @Override |
| | | public InetSocketAddress getPeerAddress() { |
| | | return (InetSocketAddress) connection.getPeerAddress(); |
| | | } |
| | | |
| | | @Override |
| | | public int getSecurityStrengthFactor() { |
| | | return Math.max(getSSLSecurityStrengthFactor(), getSaslSecurityStrengthFactor()); |
| | | } |
| | | |
| | | private int getSSLSecurityStrengthFactor() { |
| | | final SSLSession sslSession = getSSLSession(); |
| | | if (sslSession != null) { |
| | | final String cipherString = sslSession.getCipherSuite(); |
| | | for (final Object[] cipher : CIPHER_KEY_SIZES) { |
| | | if (cipherString.contains((String) cipher[0])) { |
| | | return (Integer) cipher[1]; |
| | | } |
| | | } |
| | | } |
| | | return 0; |
| | | } |
| | | |
| | | private int getSaslSecurityStrengthFactor() { |
| | | final SaslServer saslServer = SaslUtils.getSaslServer(connection); |
| | | if (saslServer == null) { |
| | | return 0; |
| | | } |
| | | int ssf = 0; |
| | | final String qop = (String) saslServer.getNegotiatedProperty(Sasl.QOP); |
| | | if (SaslUtils.SASL_AUTH_INTEGRITY.equalsIgnoreCase(qop)) { |
| | | ssf = 1; |
| | | } else if (SaslUtils.SASL_AUTH_CONFIDENTIALITY.equalsIgnoreCase(qop)) { |
| | | final String negStrength = (String) saslServer.getNegotiatedProperty(Sasl.STRENGTH); |
| | | if ("low".equalsIgnoreCase(negStrength)) { |
| | | ssf = 40; |
| | | } else if ("medium".equalsIgnoreCase(negStrength)) { |
| | | ssf = 56; |
| | | } else if ("high".equalsIgnoreCase(negStrength)) { |
| | | ssf = 128; |
| | | } |
| | | /* |
| | | * Treat anything else as if not security is provided and keep the server running |
| | | */ |
| | | } |
| | | return ssf; |
| | | } |
| | | |
| | | @Override |
| | | public SSLSession getSSLSession() { |
| | | final SSLEngine sslEngine = SSLUtils.getSSLEngine(connection); |
| | | return sslEngine != null ? sslEngine.getSession() : null; |
| | | } |
| | | |
| | | @Override |
| | | public String toString() { |
| | | final StringBuilder builder = new StringBuilder(); |
| | | builder.append("LDAPClientContext("); |
| | | builder.append(getLocalAddress()); |
| | | builder.append(','); |
| | | builder.append(getPeerAddress()); |
| | | builder.append(')'); |
| | | return builder.toString(); |
| | | } |
| | | |
| | | /** |
| | | * Installs a new Grizzly filter (e.g. SSL/SASL) beneath the top-level LDAP filter. |
| | | * |
| | | * @param filter |
| | | * The filter to be installed. |
| | | */ |
| | | private void installFilter(final Filter filter) { |
| | | GrizzlyUtils.addFilterToConnection(filter, connection); |
| | | } |
| | | |
| | | /** |
| | | * Indicates whether TLS is enabled this provided connection. |
| | | * |
| | | * @return {@code true} if TLS is enabled on this connection, otherwise {@code false}. |
| | | */ |
| | | private boolean isTLSEnabled() { |
| | | synchronized (this) { |
| | | final FilterChain currentFilterChain = (FilterChain) connection.getProcessor(); |
| | | for (final Filter filter : currentFilterChain) { |
| | | if (filter instanceof SSLFilter) { |
| | | return true; |
| | | } |
| | | } |
| | | return false; |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | public void onDisconnect(DisconnectListener listener) { |
| | | listeners.add(listener); |
| | | } |
| | | |
| | | @Override |
| | | public void disconnect() { |
| | | disconnect0(null, null); |
| | | } |
| | | |
| | | @Override |
| | | public void disconnect(final ResultCode resultCode, final String message) { |
| | | sendUnsolicitedNotification( |
| | | Responses.newGenericExtendedResult(resultCode) |
| | | .setOID(LDAP.OID_NOTICE_OF_DISCONNECTION) |
| | | .setDiagnosticMessage(message)); |
| | | disconnect0(resultCode, message); |
| | | } |
| | | |
| | | private void disconnect0(final ResultCode resultCode, final String message) { |
| | | // Close this connection context. |
| | | if (isClosed.compareAndSet(false, true)) { |
| | | try { |
| | | // Notify the server connection: it may be null if disconnect is |
| | | // invoked during accept. |
| | | for (DisconnectListener listener : listeners) { |
| | | listener.connectionDisconnected(this, resultCode, message); |
| | | } |
| | | } finally { |
| | | // Close the connection. |
| | | connection.closeSilently(); |
| | | } |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | public boolean isClosed() { |
| | | return isClosed.get(); |
| | | } |
| | | |
| | | @Override |
| | | public void sendUnsolicitedNotification(final ExtendedResult notification) { |
| | | connection.write(LdapMessages.newResponseMessage(LDAP.OP_TYPE_EXTENDED_RESPONSE, 0, notification)); |
| | | } |
| | | } |
| | | } |
| New file |
| | |
| | | /* |
| | | * 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 2016 ForgeRock AS. |
| | | */ |
| | | package org.forgerock.opendj.grizzly; |
| | | |
| | | import static org.forgerock.opendj.io.LDAP.*; |
| | | |
| | | import java.io.IOException; |
| | | |
| | | import org.forgerock.opendj.io.LDAPWriter; |
| | | import org.forgerock.opendj.ldap.DecodeOptions; |
| | | import org.forgerock.opendj.ldap.responses.BindResult; |
| | | import org.forgerock.opendj.ldap.responses.CompareResult; |
| | | import org.forgerock.opendj.ldap.responses.ExtendedResult; |
| | | import org.forgerock.opendj.ldap.responses.IntermediateResponse; |
| | | import org.forgerock.opendj.ldap.responses.Response; |
| | | import org.forgerock.opendj.ldap.responses.Result; |
| | | import org.forgerock.opendj.ldap.responses.SearchResultEntry; |
| | | import org.forgerock.opendj.ldap.responses.SearchResultReference; |
| | | import org.forgerock.opendj.ldap.spi.LdapMessages; |
| | | import org.forgerock.opendj.ldap.spi.LdapMessages.LdapRawMessage; |
| | | import org.forgerock.opendj.ldap.spi.LdapMessages.LdapResponseMessage; |
| | | import org.glassfish.grizzly.Buffer; |
| | | import org.glassfish.grizzly.filterchain.FilterChainContext; |
| | | import org.glassfish.grizzly.filterchain.NextAction; |
| | | |
| | | final class LdapCodec extends LDAPBaseFilter { |
| | | |
| | | LdapCodec(final int maxElementSize, final DecodeOptions decodeOptions) { |
| | | super(decodeOptions, maxElementSize); |
| | | } |
| | | |
| | | @Override |
| | | public NextAction handleRead(final FilterChainContext ctx) throws IOException { |
| | | final Buffer buffer = ctx.getMessage(); |
| | | final LdapRawMessage message = readMessage(buffer); |
| | | if (message != null) { |
| | | ctx.setMessage(message); |
| | | return ctx.getInvokeAction(getRemainingBuffer(buffer)); |
| | | } |
| | | return ctx.getStopAction(getRemainingBuffer(buffer)); |
| | | } |
| | | |
| | | private LdapRawMessage readMessage(final Buffer buffer) throws IOException { |
| | | try (final ASN1BufferReader reader = new ASN1BufferReader(maxASN1ElementSize, buffer)) { |
| | | final int packetStart = buffer.position(); |
| | | if (!reader.elementAvailable()) { |
| | | buffer.position(packetStart); |
| | | return null; |
| | | } |
| | | final int length = reader.peekLength(); |
| | | if (length > maxASN1ElementSize) { |
| | | buffer.position(packetStart); |
| | | throw new IllegalStateException(); |
| | | } |
| | | |
| | | final Buffer packet = buffer.slice(packetStart, buffer.position() + length); |
| | | buffer.position(buffer.position() + length); |
| | | |
| | | return decodePacket(new ASN1BufferReader(maxASN1ElementSize, packet)); |
| | | } |
| | | } |
| | | |
| | | private LdapRawMessage decodePacket(final ASN1BufferReader reader) throws IOException { |
| | | reader.mark(); |
| | | try { |
| | | reader.readStartSequence(); |
| | | final int messageId = (int) reader.readInteger(); |
| | | final byte messageType = reader.peekType(); |
| | | |
| | | final String rawDn; |
| | | final int protocolVersion; |
| | | switch (messageType) { |
| | | case OP_TYPE_BIND_REQUEST: |
| | | reader.readStartSequence(messageType); |
| | | protocolVersion = (int) reader.readInteger(); |
| | | rawDn = reader.readOctetStringAsString(); |
| | | break; |
| | | case OP_TYPE_DELETE_REQUEST: |
| | | rawDn = reader.readOctetStringAsString(messageType); |
| | | protocolVersion = -1; |
| | | break; |
| | | case OP_TYPE_ADD_REQUEST: |
| | | case OP_TYPE_COMPARE_REQUEST: |
| | | case OP_TYPE_MODIFY_DN_REQUEST: |
| | | case OP_TYPE_MODIFY_REQUEST: |
| | | case OP_TYPE_SEARCH_REQUEST: |
| | | reader.readStartSequence(messageType); |
| | | rawDn = reader.readOctetStringAsString(); |
| | | protocolVersion = -1; |
| | | break; |
| | | default: |
| | | rawDn = null; |
| | | protocolVersion = -1; |
| | | |
| | | } |
| | | return LdapMessages.newRawMessage(messageType, messageId, protocolVersion, rawDn, |
| | | rawDn != null ? decodeOptions.getSchemaResolver().resolveSchema(rawDn) : null, reader); |
| | | } finally { |
| | | reader.reset(); |
| | | } |
| | | } |
| | | |
| | | private Buffer getRemainingBuffer(final Buffer buffer) { |
| | | return buffer.hasRemaining() ? buffer : null; |
| | | } |
| | | |
| | | @Override |
| | | public NextAction handleWrite(final FilterChainContext ctx) throws IOException { |
| | | final LDAPWriter<ASN1BufferWriter> writer = GrizzlyUtils.getWriter(ctx.getMemoryManager()); |
| | | try { |
| | | final Buffer buffer = toBuffer(writer, ctx.<LdapResponseMessage> getMessage()); |
| | | ctx.setMessage(buffer); |
| | | } finally { |
| | | GrizzlyUtils.recycleWriter(writer); |
| | | } |
| | | return ctx.getInvokeAction(); |
| | | } |
| | | |
| | | private Buffer toBuffer(final LDAPWriter<ASN1BufferWriter> writer, final LdapResponseMessage message) |
| | | throws IOException { |
| | | final int msgId = message.getMessageId(); |
| | | final Response msgContent = message.getContent(); |
| | | switch (message.getMessageType()) { |
| | | case OP_TYPE_ADD_RESPONSE: |
| | | writer.writeAddResult(msgId, (Result) msgContent); |
| | | break; |
| | | case OP_TYPE_BIND_RESPONSE: |
| | | writer.writeBindResult(msgId, (BindResult) msgContent); |
| | | break; |
| | | case OP_TYPE_COMPARE_RESPONSE: |
| | | writer.writeCompareResult(msgId, (CompareResult) msgContent); |
| | | break; |
| | | case OP_TYPE_DELETE_RESPONSE: |
| | | writer.writeDeleteResult(msgId, (Result) msgContent); |
| | | break; |
| | | case OP_TYPE_EXTENDED_RESPONSE: |
| | | writer.writeExtendedResult(msgId, (ExtendedResult) msgContent); |
| | | break; |
| | | case OP_TYPE_INTERMEDIATE_RESPONSE: |
| | | writer.writeIntermediateResponse(msgId, (IntermediateResponse) msgContent); |
| | | break; |
| | | case OP_TYPE_MODIFY_DN_RESPONSE: |
| | | writer.writeModifyDNResult(msgId, (Result) msgContent); |
| | | break; |
| | | case OP_TYPE_MODIFY_RESPONSE: |
| | | writer.writeModifyResult(msgId, (Result) msgContent); |
| | | break; |
| | | case OP_TYPE_SEARCH_RESULT_DONE: |
| | | writer.writeSearchResult(msgId, (Result) msgContent); |
| | | break; |
| | | case OP_TYPE_SEARCH_RESULT_ENTRY: |
| | | writer.writeSearchResultEntry(msgId, (SearchResultEntry) msgContent); |
| | | break; |
| | | case OP_TYPE_SEARCH_RESULT_REFERENCE: |
| | | writer.writeSearchResultReference(msgId, (SearchResultReference) msgContent); |
| | | break; |
| | | default: |
| | | throw new IOException("Unsupported message type '" + message.getMessageType() + "'"); |
| | | } |
| | | return writer.getASN1Writer().getBuffer(); |
| | | } |
| | | } |
| New file |
| | |
| | | /* |
| | | * 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 2016 ForgeRock AS. |
| | | */ |
| | | package org.forgerock.opendj.grizzly; |
| | | |
| | | import org.forgerock.opendj.ldap.spi.LdapMessages.LdapResponseMessage; |
| | | import org.glassfish.grizzly.Connection; |
| | | import org.glassfish.grizzly.WriteHandler; |
| | | import org.reactivestreams.Subscriber; |
| | | import org.reactivestreams.Subscription; |
| | | |
| | | import com.forgerock.reactive.Completable; |
| | | |
| | | final class LdapResponseMessageWriter implements Subscriber<LdapResponseMessage> { |
| | | |
| | | private final Connection<?> connection; |
| | | private final Completable.Emitter completable; |
| | | private Subscription upstream; |
| | | |
| | | LdapResponseMessageWriter(final Connection<?> connection, final Completable.Emitter completable) { |
| | | this.connection = connection; |
| | | this.completable = completable; |
| | | } |
| | | |
| | | @Override |
| | | public void onSubscribe(Subscription s) { |
| | | this.upstream = s; |
| | | requestMore(); |
| | | } |
| | | |
| | | private void requestMore() { |
| | | if (connection.canWrite()) { |
| | | upstream.request(1); |
| | | } else { |
| | | connection.notifyCanWrite(new WriteHandler() { |
| | | @Override |
| | | public void onWritePossible() throws Exception { |
| | | upstream.request(1); |
| | | } |
| | | |
| | | @Override |
| | | public void onError(Throwable t) { |
| | | upstream.cancel(); |
| | | completable.onError(t); |
| | | } |
| | | }); |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | public void onNext(LdapResponseMessage message) { |
| | | connection.write(message); |
| | | requestMore(); |
| | | } |
| | | |
| | | @Override |
| | | public void onError(Throwable t) { |
| | | completable.onError(t); |
| | | } |
| | | |
| | | @Override |
| | | public void onComplete() { |
| | | completable.complete(); |
| | | } |
| | | } |
| New file |
| | |
| | | /* |
| | | * 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 2016 ForgeRock AS. |
| | | */ |
| | | package org.forgerock.opendj.grizzly; |
| | | |
| | | import java.io.IOException; |
| | | |
| | | import javax.security.sasl.SaslException; |
| | | import javax.security.sasl.SaslServer; |
| | | |
| | | import org.glassfish.grizzly.Buffer; |
| | | import org.glassfish.grizzly.filterchain.BaseFilter; |
| | | import org.glassfish.grizzly.filterchain.FilterChainContext; |
| | | import org.glassfish.grizzly.filterchain.NextAction; |
| | | import org.glassfish.grizzly.memory.Buffers; |
| | | import org.glassfish.grizzly.memory.HeapMemoryManager; |
| | | |
| | | final class SaslFilter extends BaseFilter { |
| | | |
| | | @Override |
| | | public NextAction handleRead(final FilterChainContext ctx) throws IOException { |
| | | final Buffer message = ctx.getMessage(); |
| | | if (message.remaining() < 4) { |
| | | return ctx.getStopAction(message); |
| | | } |
| | | |
| | | final int length = message.mark().getInt(); |
| | | if (message.remaining() < length) { |
| | | return ctx.getStopAction(message.reset()); |
| | | } |
| | | |
| | | ctx.setMessage(unwrap(ctx, message, length)); |
| | | if (message.position(message.position() + length).hasRemaining()) { |
| | | return ctx.getInvokeAction(message); |
| | | } |
| | | message.dispose(); |
| | | return ctx.getInvokeAction(); |
| | | } |
| | | |
| | | private Buffer unwrap(final FilterChainContext ctx, final Buffer buffer, final int length) throws SaslException { |
| | | final SaslServer server = SaslUtils.getSaslServer(ctx.getConnection()); |
| | | if (buffer.hasArray()) { |
| | | return Buffers.wrap(ctx.getMemoryManager(), |
| | | server.unwrap(buffer.array(), buffer.arrayOffset() + buffer.position(), length)); |
| | | } |
| | | final Buffer heapBuffer = toHeapBuffer(buffer, length); |
| | | try { |
| | | return Buffers.wrap(ctx.getMemoryManager(), |
| | | server.unwrap(heapBuffer.array(), heapBuffer.arrayOffset() + heapBuffer.position(), length)); |
| | | } finally { |
| | | heapBuffer.dispose(); |
| | | } |
| | | } |
| | | |
| | | private final Buffer toHeapBuffer(final Buffer buffer, final int length) { |
| | | return HeapMemoryManager |
| | | .DEFAULT_MEMORY_MANAGER.allocate(length) |
| | | .put(buffer, buffer.position(), length) |
| | | .position(buffer.position() + length) |
| | | .flip(); |
| | | } |
| | | |
| | | @Override |
| | | public NextAction handleWrite(final FilterChainContext ctx) throws IOException { |
| | | final Buffer message = ctx.getMessage(); |
| | | ctx.setMessage(wrap(ctx, message)); |
| | | message.dispose(); |
| | | return ctx.getInvokeAction(); |
| | | } |
| | | |
| | | private Buffer wrap(final FilterChainContext ctx, final Buffer buffer) throws SaslException { |
| | | final SaslServer server = SaslUtils.getSaslServer(ctx.getConnection()); |
| | | if (buffer.hasArray()) { |
| | | return Buffers.wrap(ctx.getMemoryManager(), |
| | | server.wrap(buffer.array(), buffer.arrayOffset() + buffer.position(), buffer.remaining())); |
| | | } |
| | | final Buffer heapBuffer = toHeapBuffer(buffer, buffer.remaining()); |
| | | try { |
| | | return Buffers.wrap(ctx.getMemoryManager(), server.wrap(heapBuffer.array(), |
| | | heapBuffer.arrayOffset() + heapBuffer.position(), heapBuffer.remaining())); |
| | | |
| | | } finally { |
| | | heapBuffer.dispose(); |
| | | } |
| | | } |
| | | } |
| New file |
| | |
| | | /* |
| | | * 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 2016 ForgeRock AS. |
| | | */ |
| | | package org.forgerock.opendj.grizzly; |
| | | |
| | | import javax.security.sasl.SaslServer; |
| | | |
| | | import org.glassfish.grizzly.Connection; |
| | | import org.glassfish.grizzly.attributes.Attribute; |
| | | import org.glassfish.grizzly.attributes.AttributeBuilder; |
| | | |
| | | final class SaslUtils { |
| | | |
| | | /** Used to check if negotiated QOP is confidentiality or integrity. */ |
| | | static final String SASL_AUTH_CONFIDENTIALITY = "auth-conf"; |
| | | |
| | | static final String SASL_AUTH_INTEGRITY = "auth-int"; |
| | | |
| | | private static final Attribute<SaslServer> SASL_SERVER = |
| | | AttributeBuilder.DEFAULT_ATTRIBUTE_BUILDER.createAttribute(SaslUtils.class + ".sasl-server"); |
| | | |
| | | static SaslServer getSaslServer(final Connection connection) { |
| | | return SASL_SERVER.get(connection); |
| | | } |
| | | |
| | | static void setSaslServer(final Connection connection, final SaslServer saslServer) { |
| | | SASL_SERVER.set(connection, saslServer); |
| | | } |
| | | |
| | | private SaslUtils() { |
| | | } |
| | | } |
| | |
| | | * information: "Portions Copyright [year] [name of copyright owner]". |
| | | * |
| | | * Copyright 2010 Sun Microsystems, Inc. |
| | | * Portions copyright 2011-2013 ForgeRock AS. |
| | | * Portions copyright 2011-2016 ForgeRock AS. |
| | | */ |
| | | |
| | | package org.forgerock.opendj.grizzly; |
| | |
| | | import org.forgerock.opendj.io.ASN1Reader; |
| | | import org.forgerock.opendj.io.ASN1ReaderTestCase; |
| | | import org.glassfish.grizzly.memory.ByteBufferWrapper; |
| | | import org.glassfish.grizzly.memory.MemoryManager; |
| | | |
| | | /** |
| | | * This class provides test cases for ASN1BufferReader. |
| | |
| | | public class ASN1BufferReaderTestCase extends ASN1ReaderTestCase { |
| | | @Override |
| | | protected ASN1Reader getReader(final byte[] b, final int maxElementSize) throws IOException { |
| | | final ByteBufferWrapper buffer = new ByteBufferWrapper(ByteBuffer.wrap(b)); |
| | | final ASN1BufferReader reader = |
| | | new ASN1BufferReader(maxElementSize, MemoryManager.DEFAULT_MEMORY_MANAGER); |
| | | reader.appendBytesRead(buffer); |
| | | return reader; |
| | | return new ASN1BufferReader(maxElementSize, new ByteBufferWrapper(ByteBuffer.wrap(b))); |
| | | } |
| | | } |
| | |
| | | * information: "Portions Copyright [year] [name of copyright owner]". |
| | | * |
| | | * Copyright 2010 Sun Microsystems, Inc. |
| | | * Portions copyright 2011-2013 ForgeRock AS. |
| | | * Portions copyright 2011-2016 ForgeRock AS. |
| | | */ |
| | | |
| | | package org.forgerock.opendj.grizzly; |
| | |
| | | import org.glassfish.grizzly.Buffer; |
| | | import org.glassfish.grizzly.memory.ByteBufferWrapper; |
| | | import org.glassfish.grizzly.memory.MemoryManager; |
| | | import org.testng.annotations.Test; |
| | | |
| | | /** |
| | | * This class provides testcases for ASN1BufferWriter. |
| | | */ |
| | | @Test |
| | | public class ASN1BufferWriterTestCase extends ASN1WriterTestCase { |
| | | |
| | | private final ASN1BufferWriter writer = new ASN1BufferWriter(); |
| | | private final ASN1BufferWriter writer = new ASN1BufferWriter(MemoryManager.DEFAULT_MEMORY_MANAGER); |
| | | |
| | | @Override |
| | | protected byte[] getEncodedBytes() throws IOException, DecodeException { |
| | |
| | | |
| | | @Override |
| | | protected ASN1Reader getReader(final byte[] encodedBytes) throws DecodeException, IOException { |
| | | final ByteBufferWrapper buffer = new ByteBufferWrapper(ByteBuffer.wrap(encodedBytes)); |
| | | final ASN1BufferReader reader = |
| | | new ASN1BufferReader(0, MemoryManager.DEFAULT_MEMORY_MANAGER); |
| | | reader.appendBytesRead(buffer); |
| | | return reader; |
| | | return new ASN1BufferReader(0, new ByteBufferWrapper(ByteBuffer.wrap(encodedBytes))); |
| | | } |
| | | |
| | | @Override |
| | |
| | | * information: "Portions Copyright [year] [name of copyright owner]". |
| | | * |
| | | * Copyright 2010 Sun Microsystems, Inc. |
| | | * Portions Copyright 2011-2015 ForgeRock AS. |
| | | * Portions Copyright 2011-2016 ForgeRock AS. |
| | | */ |
| | | |
| | | package org.forgerock.opendj.grizzly; |
| | |
| | | import static java.util.Arrays.asList; |
| | | import static java.util.Collections.singletonList; |
| | | import static org.fest.assertions.Assertions.assertThat; |
| | | import static org.forgerock.opendj.ldap.Connections.newFailoverLoadBalancer; |
| | | import static org.forgerock.opendj.ldap.Connections.newFixedConnectionPool; |
| | | import static org.forgerock.opendj.ldap.Connections.newRoundRobinLoadBalancer; |
| | | import static org.forgerock.opendj.ldap.Connections.*; |
| | | import static org.forgerock.opendj.ldap.LDAPConnectionFactory.*; |
| | | import static org.forgerock.opendj.ldap.LdapException.newLdapException; |
| | | import static org.forgerock.opendj.ldap.TestCaseUtils.findFreeSocketAddress; |
| | | import static org.forgerock.opendj.ldap.TestCaseUtils.getServerSocketAddress; |
| | | import static org.forgerock.opendj.ldap.requests.Requests.newCRAMMD5SASLBindRequest; |
| | | import static org.forgerock.opendj.ldap.requests.Requests.newDigestMD5SASLBindRequest; |
| | | import static org.forgerock.opendj.ldap.requests.Requests.newSimpleBindRequest; |
| | | import static org.forgerock.opendj.ldap.TestCaseUtils.*; |
| | | import static org.forgerock.opendj.ldap.requests.Requests.*; |
| | | import static org.forgerock.opendj.ldap.spi.LdapPromises.newSuccessfulLdapPromise; |
| | | import static org.forgerock.util.Options.defaultOptions; |
| | | import static org.forgerock.util.time.Duration.duration; |
| | | import static org.mockito.Matchers.any; |
| | | import static org.mockito.Matchers.anyInt; |
| | | import static org.mockito.Mockito.doAnswer; |
| | | import static org.mockito.Mockito.mock; |
| | | import static org.mockito.Mockito.when; |
| | | import static org.testng.Assert.assertNotNull; |
| | | import static org.testng.Assert.assertTrue; |
| | | import static org.mockito.Matchers.*; |
| | | import static org.mockito.Mockito.*; |
| | | import static org.testng.Assert.*; |
| | | |
| | | import java.net.InetSocketAddress; |
| | | import java.util.Arrays; |
| | |
| | | // Use custom search request. |
| | | SearchRequest request = Requests.newSearchRequest( |
| | | "uid=user.0,ou=people,o=test", SearchScope.BASE_OBJECT, "(objectclass=*)", "cn"); |
| | | |
| | | // |
| | | InetSocketAddress serverAddress = getServerSocketAddress(); |
| | | factories[0][0] = new LDAPConnectionFactory(serverAddress.getHostName(), |
| | | serverAddress.getPort(), |
| | |
| | | * |
| | | * @throws Exception |
| | | */ |
| | | @Test(dataProvider = "connectionFactories", timeOut = TEST_TIMEOUT_MS) |
| | | @Test(dataProvider = "connectionFactories") //, timeOut = TEST_TIMEOUT_MS) |
| | | public void testBlockingPromiseNoHandler(ConnectionFactory factory) throws Exception { |
| | | final Promise<? extends Connection, LdapException> promise = factory.getConnectionAsync(); |
| | | final Connection con = promise.get(); |
| | |
| | | |
| | | LDAPListener listener = new LDAPListener(findFreeSocketAddress(), mockServer); |
| | | try { |
| | | LDAPConnectionFactory clientFactory = |
| | | new LDAPConnectionFactory(listener.getHostName(), listener.getPort()); |
| | | LDAPConnectionFactory clientFactory = new LDAPConnectionFactory( |
| | | ((InetSocketAddress) listener.getSocketAddresses().iterator().next()).getHostName(), |
| | | ((InetSocketAddress) listener.getSocketAddresses().iterator().next()).getPort()); |
| | | final Connection client = clientFactory.getConnection(); |
| | | connectLatch.await(TEST_TIMEOUT, TimeUnit.SECONDS); |
| | | MockConnectionEventListener mockListener = null; |
| | |
| | | LDAPListener listener = new LDAPListener(findFreeSocketAddress(), mockServer); |
| | | try { |
| | | LDAPConnectionFactory clientFactory = |
| | | new LDAPConnectionFactory(listener.getHostName(), |
| | | listener.getPort()); |
| | | new LDAPConnectionFactory( |
| | | ((InetSocketAddress) listener.getSocketAddresses().iterator().next()).getHostName(), |
| | | ((InetSocketAddress) listener.getSocketAddresses().iterator().next()).getPort()); |
| | | final Connection client = clientFactory.getConnection(); |
| | | connectLatch.await(TEST_TIMEOUT, TimeUnit.SECONDS); |
| | | try { |
| | |
| | | * Header, with the fields enclosed by brackets [] replaced by your own identifying |
| | | * information: "Portions Copyright [year] [name of copyright owner]". |
| | | * |
| | | * Copyright 2013-2015 ForgeRock AS. |
| | | * Copyright 2013-2016 ForgeRock AS. |
| | | */ |
| | | package org.forgerock.opendj.grizzly; |
| | | |
| | | import static org.fest.assertions.Assertions.assertThat; |
| | | import static org.fest.assertions.Fail.fail; |
| | | import static org.forgerock.opendj.ldap.CommonLDAPOptions.*; |
| | | import static org.forgerock.opendj.ldap.LDAPConnectionFactory.*; |
| | | import static org.forgerock.opendj.ldap.TestCaseUtils.findFreeSocketAddress; |
| | | import static org.forgerock.opendj.ldap.requests.Requests.newSimpleBindRequest; |
| | | import static org.forgerock.util.time.Duration.duration; |
| | | import static org.mockito.Matchers.*; |
| | | import static org.mockito.Mockito.*; |
| | | |
| | | import java.io.IOException; |
| | | import java.net.InetSocketAddress; |
| | | import java.net.SocketAddress; |
| | | import java.util.Set; |
| | | import java.util.concurrent.Semaphore; |
| | | import java.util.concurrent.TimeUnit; |
| | | import java.util.concurrent.atomic.AtomicReference; |
| | |
| | | import org.forgerock.opendj.ldap.ConnectionFactory; |
| | | import org.forgerock.opendj.ldap.Connections; |
| | | import org.forgerock.opendj.ldap.DN; |
| | | import org.forgerock.opendj.ldap.LDAPConnectionFactory; |
| | | import org.forgerock.opendj.ldap.LdapException; |
| | | import org.forgerock.opendj.ldap.LdapPromise; |
| | | import org.forgerock.opendj.ldap.IntermediateResponseHandler; |
| | | import org.forgerock.opendj.ldap.LDAPClientContext; |
| | | import org.forgerock.opendj.ldap.LDAPConnectionFactory; |
| | | import org.forgerock.opendj.ldap.LDAPListener; |
| | | import org.forgerock.opendj.ldap.LdapException; |
| | | import org.forgerock.opendj.ldap.LdapPromise; |
| | | import org.forgerock.opendj.ldap.LdapResultHandler; |
| | | import org.forgerock.opendj.ldap.MockConnectionEventListener; |
| | | import org.forgerock.opendj.ldap.ProviderNotFoundException; |
| | | import org.forgerock.opendj.ldap.ResultCode; |
| | | import org.forgerock.opendj.ldap.LdapResultHandler; |
| | | import org.forgerock.opendj.ldap.SdkTestCase; |
| | | import org.forgerock.opendj.ldap.SearchResultHandler; |
| | | import org.forgerock.opendj.ldap.ServerConnection; |
| | |
| | | import org.testng.annotations.AfterClass; |
| | | import org.testng.annotations.Test; |
| | | |
| | | import static org.fest.assertions.Assertions.*; |
| | | import static org.fest.assertions.Fail.*; |
| | | import static org.forgerock.opendj.ldap.TestCaseUtils.*; |
| | | import static org.forgerock.opendj.ldap.requests.Requests.*; |
| | | import static org.forgerock.opendj.ldap.LDAPConnectionFactory.*; |
| | | import static org.forgerock.util.time.Duration.duration; |
| | | import static org.mockito.Matchers.*; |
| | | import static org.mockito.Mockito.*; |
| | | |
| | | /** |
| | | * Tests the {@link LDAPConnectionFactory} class. |
| | | */ |
| | |
| | | private final Semaphore searchLatch = new Semaphore(0); |
| | | private final AtomicReference<LDAPClientContext> context = new AtomicReference<>(); |
| | | private final LDAPListener server = createServer(); |
| | | private final InetSocketAddress socketAddress = server.getSocketAddress(); |
| | | public final ConnectionFactory factory = new LDAPConnectionFactory(socketAddress.getHostName(), |
| | | socketAddress.getPort(), Options.defaultOptions().set(REQUEST_TIMEOUT, duration("1 ms"))); |
| | | private final Set<? extends SocketAddress> socketAddresses = server.getSocketAddresses(); |
| | | public final ConnectionFactory factory = new LDAPConnectionFactory( |
| | | ((InetSocketAddress) socketAddresses.iterator().next()).getHostName(), |
| | | ((InetSocketAddress) socketAddresses.iterator().next()).getPort(), |
| | | Options.defaultOptions().set(REQUEST_TIMEOUT, duration("1 ms"))); |
| | | private final ConnectionFactory pool = Connections.newFixedConnectionPool(factory, 10); |
| | | private volatile ServerConnection<Integer> serverConnection; |
| | | |
| | |
| | | final MockServerConnection serverConnection = new MockServerConnection(); |
| | | final MockServerConnectionFactory serverConnectionFactory = |
| | | new MockServerConnectionFactory(serverConnection); |
| | | final LDAPListener listener = |
| | | new LDAPListener(new InetSocketAddress(0), serverConnectionFactory); |
| | | final LDAPListener listener = new LDAPListener(new InetSocketAddress(0), serverConnectionFactory); |
| | | final InetSocketAddress addr = (InetSocketAddress) listener.getSocketAddresses().iterator().next(); |
| | | try { |
| | | // Connect and close. |
| | | final Connection connection = |
| | | new LDAPConnectionFactory(listener.getHostName(), |
| | | listener.getPort()).getConnection(); |
| | | new LDAPConnectionFactory(addr.getHostName(), addr.getPort()).getConnection(); |
| | | assertThat(serverConnection.context.get(10, TimeUnit.SECONDS)).isNotNull(); |
| | | assertThat(serverConnection.isClosed.getCount()).isEqualTo(1); |
| | | connection.close(); |
| | |
| | | final MockServerConnection onlineServerConnection = new MockServerConnection(); |
| | | final MockServerConnectionFactory onlineServerConnectionFactory = |
| | | new MockServerConnectionFactory(onlineServerConnection); |
| | | final InetSocketAddress onlineAddr = findFreeSocketAddress(); |
| | | final LDAPListener onlineServerListener = |
| | | new LDAPListener(findFreeSocketAddress(), onlineServerConnectionFactory); |
| | | new LDAPListener(onlineAddr, onlineServerConnectionFactory); |
| | | |
| | | try { |
| | | // Connection pool and load balancing tests. |
| | |
| | | offlineAddress2.getPort()), "offline2"); |
| | | final ConnectionFactory onlineServer = |
| | | Connections.newNamedConnectionFactory(new LDAPConnectionFactory( |
| | | onlineServerListener.getHostName(), |
| | | onlineServerListener.getPort()), "online"); |
| | | onlineAddr.getHostName(), |
| | | onlineAddr.getPort()), "online"); |
| | | |
| | | // Round robin. |
| | | final ConnectionFactory loadBalancer = |
| | |
| | | |
| | | }; |
| | | |
| | | final InetSocketAddress proxyAddr = findFreeSocketAddress(); |
| | | final LDAPListener proxyListener = |
| | | new LDAPListener(findFreeSocketAddress(), proxyServerConnectionFactory); |
| | | new LDAPListener(proxyAddr, proxyServerConnectionFactory); |
| | | try { |
| | | // Connect and close. |
| | | final Connection connection = |
| | | new LDAPConnectionFactory(proxyListener.getHostName(), |
| | | proxyListener.getPort()).getConnection(); |
| | | new LDAPConnectionFactory(proxyAddr.getHostName(), |
| | | proxyAddr.getPort()).getConnection(); |
| | | |
| | | assertThat(proxyServerConnection.context.get(10, TimeUnit.SECONDS)).isNotNull(); |
| | | assertThat(onlineServerConnection.context.get(10, TimeUnit.SECONDS)).isNotNull(); |
| | |
| | | new MockServerConnectionFactory(onlineServerConnection); |
| | | final LDAPListener onlineServerListener = |
| | | new LDAPListener(new InetSocketAddress(0), onlineServerConnectionFactory); |
| | | final InetSocketAddress onlineServerAddr = |
| | | (InetSocketAddress) onlineServerListener.getSocketAddresses().iterator().next(); |
| | | |
| | | try { |
| | | // Connection pool and load balancing tests. |
| | |
| | | offlineAddress2.getPort()), "offline2"); |
| | | final ConnectionFactory onlineServer = |
| | | Connections.newNamedConnectionFactory( |
| | | new LDAPConnectionFactory(onlineServerListener.getHostName(), |
| | | onlineServerListener.getPort()), "online"); |
| | | new LDAPConnectionFactory(onlineServerAddr.getHostName(), |
| | | onlineServerAddr.getPort()), "online"); |
| | | |
| | | // Round robin. |
| | | final ConnectionFactory loadBalancer = |
| | |
| | | |
| | | final LDAPListener proxyListener = |
| | | new LDAPListener(new InetSocketAddress(0), proxyServerConnectionFactory); |
| | | final InetSocketAddress proxyAddr = |
| | | (InetSocketAddress) proxyListener.getSocketAddresses().iterator().next(); |
| | | |
| | | try { |
| | | // Connect, bind, and close. |
| | | final Connection connection = |
| | | new LDAPConnectionFactory(proxyListener.getHostName(), |
| | | proxyListener.getPort()).getConnection(); |
| | | new LDAPConnectionFactory(proxyAddr.getHostName(), |
| | | proxyAddr.getPort()).getConnection(); |
| | | try { |
| | | connection.bind("cn=test", "password".toCharArray()); |
| | | |
| | |
| | | final MockServerConnection onlineServerConnection = new MockServerConnection(); |
| | | final MockServerConnectionFactory onlineServerConnectionFactory = |
| | | new MockServerConnectionFactory(onlineServerConnection); |
| | | final InetSocketAddress onlineServerAddr = new InetSocketAddress(0); |
| | | final LDAPListener onlineServerListener = |
| | | new LDAPListener(findFreeSocketAddress(), onlineServerConnectionFactory); |
| | | new LDAPListener(onlineServerAddr, onlineServerConnectionFactory); |
| | | |
| | | try { |
| | | final MockServerConnection proxyServerConnection = new MockServerConnection(); |
| | |
| | | try { |
| | | lcf = |
| | | new LDAPConnectionFactory( |
| | | onlineServerListener.getHostName(), |
| | | onlineServerListener.getPort()); |
| | | onlineServerAddr.getHostName(), |
| | | onlineServerAddr.getPort()); |
| | | lcf.getConnection().close(); |
| | | } catch (final Exception e) { |
| | | // Unexpected. |
| | |
| | | try { |
| | | // Connect and close. |
| | | final Connection connection = |
| | | new LDAPConnectionFactory(proxyListener.getHostName(), |
| | | proxyListener.getPort()).getConnection(); |
| | | new LDAPConnectionFactory(onlineServerAddr.getHostName(), |
| | | onlineServerAddr.getPort()).getConnection(); |
| | | |
| | | assertThat(proxyServerConnection.context.get(10, TimeUnit.SECONDS)).isNotNull(); |
| | | assertThat(onlineServerConnection.context.get(10, TimeUnit.SECONDS)).isNotNull(); |
| | |
| | | final MockServerConnection onlineServerConnection = new MockServerConnection(); |
| | | final MockServerConnectionFactory onlineServerConnectionFactory = |
| | | new MockServerConnectionFactory(onlineServerConnection); |
| | | final InetSocketAddress onlineServerAddr = findFreeSocketAddress(); |
| | | final LDAPListener onlineServerListener = |
| | | new LDAPListener(findFreeSocketAddress(), onlineServerConnectionFactory); |
| | | new LDAPListener(onlineServerAddr, onlineServerConnectionFactory); |
| | | |
| | | try { |
| | | final MockServerConnection proxyServerConnection = new MockServerConnection() { |
| | |
| | | } catch (final ConnectionException ce) { |
| | | // This is expected - so go to online server. |
| | | try { |
| | | lcf = new LDAPConnectionFactory(onlineServerListener.getHostName(), |
| | | onlineServerListener.getPort()); |
| | | lcf = new LDAPConnectionFactory(onlineServerAddr.getHostName(), |
| | | onlineServerAddr.getPort()); |
| | | lcf.getConnection().close(); |
| | | resultHandler.handleResult(Responses.newBindResult(ResultCode.SUCCESS)); |
| | | } catch (final Exception e) { |
| | |
| | | } |
| | | |
| | | }; |
| | | final InetSocketAddress proxyAddr = findFreeSocketAddress(); |
| | | final MockServerConnectionFactory proxyServerConnectionFactory = |
| | | new MockServerConnectionFactory(proxyServerConnection); |
| | | final LDAPListener proxyListener = |
| | | new LDAPListener(findFreeSocketAddress(), proxyServerConnectionFactory); |
| | | new LDAPListener(proxyAddr, proxyServerConnectionFactory); |
| | | try { |
| | | // Connect, bind, and close. |
| | | final Connection connection = |
| | | new LDAPConnectionFactory(proxyListener.getHostName(), |
| | | proxyListener.getPort()).getConnection(); |
| | | new LDAPConnectionFactory(proxyAddr.getHostName(), |
| | | proxyAddr.getPort()).getConnection(); |
| | | try { |
| | | connection.bind("cn=test", "password".toCharArray()); |
| | | |
| | |
| | | * @throws Exception |
| | | * If an unexpected error occurred. |
| | | */ |
| | | @Test |
| | | @Test(enabled = false) |
| | | public void testMaxRequestSize() throws Exception { |
| | | final MockServerConnection serverConnection = new MockServerConnection(); |
| | | final MockServerConnectionFactory factory = |
| | | new MockServerConnectionFactory(serverConnection); |
| | | final Options options = defaultOptions().set(REQUEST_MAX_SIZE_IN_BYTES, 2048); |
| | | final LDAPListener listener = new LDAPListener(findFreeSocketAddress(), factory, options); |
| | | final InetSocketAddress addr = findFreeSocketAddress(); |
| | | final LDAPListener listener = new LDAPListener(addr, factory, options); |
| | | |
| | | Connection connection = null; |
| | | try { |
| | | connection = new LDAPConnectionFactory(listener.getHostName(), listener.getPort()). |
| | | connection = new LDAPConnectionFactory(addr.getHostName(), addr.getPort()). |
| | | getConnection(); |
| | | |
| | | // Small request |
| | |
| | | final MockServerConnection serverConnection = new MockServerConnection(); |
| | | final MockServerConnectionFactory factory = |
| | | new MockServerConnectionFactory(serverConnection); |
| | | final LDAPListener listener = new LDAPListener(findFreeSocketAddress(), factory); |
| | | final InetSocketAddress listenerAddr = findFreeSocketAddress(); |
| | | final LDAPListener listener = new LDAPListener(listenerAddr, factory); |
| | | |
| | | final Connection connection; |
| | | try { |
| | | // Connect and bind. |
| | | connection = new LDAPConnectionFactory(listener.getHostName(), listener.getPort()).getConnection(); |
| | | connection = new LDAPConnectionFactory(listenerAddr.getHostName(), listenerAddr.getPort()).getConnection(); |
| | | try { |
| | | connection.bind("cn=test", "password".toCharArray()); |
| | | } catch (final LdapException e) { |
| | |
| | | try { |
| | | // Connect and bind. |
| | | final Connection failedConnection = |
| | | new LDAPConnectionFactory(listener.getHostName(), |
| | | listener.getPort()).getConnection(); |
| | | new LDAPConnectionFactory(listenerAddr.getHostName(), |
| | | listenerAddr.getPort()).getConnection(); |
| | | failedConnection.close(); |
| | | connection.close(); |
| | | fail("Connection attempt to closed listener succeeded unexpectedly"); |
| | |
| | | * Header, with the fields enclosed by brackets [] replaced by your own identifying |
| | | * information: "Portions Copyright [year] [name of copyright owner]". |
| | | * |
| | | * Copyright 2013-2015 ForgeRock AS. |
| | | * Copyright 2013-2016 ForgeRock AS. |
| | | */ |
| | | package org.forgerock.opendj.grizzly; |
| | | |
| | | import org.forgerock.opendj.io.ASN1Reader; |
| | | import org.forgerock.opendj.io.ASN1Writer; |
| | | import org.forgerock.opendj.io.LDAP; |
| | | import org.forgerock.opendj.io.LDAPReader; |
| | | import org.forgerock.opendj.io.LDAPReaderWriterTestCase; |
| | | import org.forgerock.opendj.io.LDAPWriter; |
| | | import org.forgerock.util.Options; |
| | | import org.glassfish.grizzly.memory.HeapMemoryManager; |
| | | import org.glassfish.grizzly.memory.MemoryManager; |
| | | |
| | | import static org.forgerock.opendj.ldap.LDAPConnectionFactory.LDAP_DECODE_OPTIONS; |
| | | |
| | | /** |
| | |
| | | |
| | | @Override |
| | | protected LDAPWriter<? extends ASN1Writer> getLDAPWriter() { |
| | | return GrizzlyUtils.getWriter(); |
| | | return GrizzlyUtils.getWriter(MemoryManager.DEFAULT_MEMORY_MANAGER); |
| | | } |
| | | |
| | | @Override |
| | |
| | | } |
| | | |
| | | @Override |
| | | protected void transferFromWriterToReader(LDAPWriter<? extends ASN1Writer> writer, |
| | | LDAPReader<? extends ASN1Reader> reader) { |
| | | ASN1BufferReader asn1Reader = (ASN1BufferReader) reader.getASN1Reader(); |
| | | ASN1BufferWriter asn1Writer = (ASN1BufferWriter) writer.getASN1Writer(); |
| | | asn1Reader.appendBytesRead(asn1Writer.getBuffer()); |
| | | protected LDAPReader<? extends ASN1Reader> getLDAPReader(LDAPWriter<? extends ASN1Writer> writer) { |
| | | return LDAP.<ASN1BufferReader> getReader( |
| | | new ASN1BufferReader(0, ((ASN1BufferWriter) writer.getASN1Writer()).getBuffer()), |
| | | Options.defaultOptions().get(LDAP_DECODE_OPTIONS)); |
| | | } |
| | | |
| | | } |
| | |
| | | * information: "Portions Copyright [year] [name of copyright owner]". |
| | | * |
| | | * Copyright 2009-2010 Sun Microsystems, Inc. |
| | | * Portions Copyright 2011-2015 ForgeRock AS. |
| | | * Portions Copyright 2011-2016 ForgeRock AS. |
| | | */ |
| | | |
| | | package org.forgerock.opendj.examples; |
| | |
| | | @Override |
| | | public ServerConnection<Integer> handleAccept(final LDAPClientContext clientContext) |
| | | throws LdapException { |
| | | clientContext.enableTLS(sslContext, null, null, false, false); |
| | | clientContext.enableTLS(sslContext.createSSLEngine()); |
| | | return connectionHandler.handleAccept(clientContext); |
| | | } |
| | | }; |
| | |
| | | |
| | | <dependency> |
| | | <groupId>org.forgerock.commons</groupId> |
| | | <artifactId>forgerock-audit-handler-splunk</artifactId> |
| | | </dependency> |
| | | |
| | | <dependency> |
| | | <groupId>org.forgerock.commons</groupId> |
| | | <artifactId>forgerock-audit-handler-jms</artifactId> |
| | | </dependency> |
| | | |
| | | <dependency> |
| | | <groupId>org.forgerock.commons</groupId> |
| | | <artifactId>forgerock-audit-json</artifactId> |
| | | </dependency> |
| | | |
| | |
| | | <Export-Package>org.forgerock.opendj.server.embedded</Export-Package> |
| | | <!-- Import je changelog since it is not shipped in the main jar --> |
| | | <Import-Package> |
| | | org.opends.server.replication.server.changelog.je;resolution:=optional, |
| | | com.sleepycat.je*;resolution:=optional, |
| | | org.opends.server.replication.server.changelog.je, |
| | | ${opendj.osgi.import} |
| | | </Import-Package> |
| | | <Embed-Dependency> |
| | | forgerock-persistit-core, |
| | | <!--je,--> |
| | | jcip-annotations |
| | | </Embed-Dependency> |
| | | </instructions> |
| | | </configuration> |
| | | </execution> |
| | |
| | | @Override |
| | | public void handleInternalSearchEntry(InternalSearchOperation searchOperation, |
| | | SearchResultEntry searchEntry) throws DirectoryException { |
| | | handler.handleEntry(from(searchEntry)); |
| | | handler.handleEntry(partiallyWrap(searchEntry)); |
| | | } |
| | | }; |
| | | |
| | |
| | | */ |
| | | package org.forgerock.opendj.adapter.server3x; |
| | | |
| | | import static com.forgerock.opendj.ldap.CoreMessages.*; |
| | | import static com.forgerock.opendj.util.StaticUtils.*; |
| | | |
| | | import static org.forgerock.opendj.ldap.LdapException.*; |
| | | import static org.opends.server.extensions.ExtensionsConstants.*; |
| | | import static org.opends.server.util.CollectionUtils.*; |
| | | import static com.forgerock.opendj.ldap.CoreMessages.ERR_EXTOP_PASSMOD_CANNOT_DECODE_REQUEST; |
| | | import static com.forgerock.opendj.util.StaticUtils.getExceptionMessage; |
| | | import static org.forgerock.opendj.ldap.LdapException.newLdapException; |
| | | import static org.opends.server.extensions.ExtensionsConstants.TYPE_PASSWORD_MODIFY_GENERATED_PASSWORD; |
| | | import static org.opends.server.util.CollectionUtils.newArrayList; |
| | | |
| | | import java.io.IOException; |
| | | import java.util.ArrayList; |
| | |
| | | import org.forgerock.opendj.io.ASN1Reader; |
| | | import org.forgerock.opendj.io.ASN1Writer; |
| | | import org.forgerock.opendj.ldap.Attribute; |
| | | import org.forgerock.opendj.ldap.AttributeDescription; |
| | | import org.forgerock.opendj.ldap.AttributeParser; |
| | | import org.forgerock.opendj.ldap.ByteString; |
| | | import org.forgerock.opendj.ldap.ByteStringBuilder; |
| | | import org.forgerock.opendj.ldap.DN; |
| | | import org.forgerock.opendj.ldap.DecodeException; |
| | | import org.forgerock.opendj.ldap.DecodeOptions; |
| | | import org.forgerock.opendj.ldap.LdapException; |
| | | import org.forgerock.opendj.ldap.LinkedAttribute; |
| | | import org.forgerock.opendj.ldap.LinkedHashMapEntry; |
| | | import org.forgerock.opendj.ldap.ResultCode; |
| | | import org.forgerock.opendj.ldap.SearchScope; |
| | | import org.forgerock.opendj.ldap.controls.Control; |
| | | import org.forgerock.opendj.ldap.controls.ControlDecoder; |
| | | import org.forgerock.opendj.ldap.controls.GenericControl; |
| | | import org.forgerock.opendj.ldap.responses.PasswordModifyExtendedResult; |
| | | import org.forgerock.opendj.ldap.responses.Responses; |
| | | import org.forgerock.opendj.ldap.responses.Result; |
| | | import org.forgerock.opendj.ldap.responses.SearchResultEntry; |
| | | import org.forgerock.opendj.server.config.meta.VirtualAttributeCfgDefn; |
| | | import org.forgerock.util.Function; |
| | | import org.forgerock.util.promise.NeverThrowsException; |
| | | import org.opends.server.core.BindOperation; |
| | | import org.opends.server.core.CompareOperation; |
| | | import org.opends.server.core.DirectoryServer; |
| | |
| | | import org.opends.server.types.SearchFilter; |
| | | import org.opends.server.util.ServerConstants; |
| | | |
| | | import com.forgerock.opendj.util.Iterables; |
| | | |
| | | /** Common utility methods. */ |
| | | public final class Converters { |
| | | |
| | |
| | | } |
| | | |
| | | /** |
| | | * Converts from OpenDJ server {@link org.opends.server.types.Attribute} to OpenDJ LDAP SDK |
| | | * {@link org.forgerock.opendj.ldap.Attribute}. |
| | | * |
| | | * @param attribute |
| | | * value to convert |
| | | * @return the converted value |
| | | */ |
| | | public static org.forgerock.opendj.ldap.Attribute partiallyWrap(final org.opends.server.types.Attribute attribute) { |
| | | return new Attribute() { |
| | | |
| | | @Override |
| | | public AttributeDescription getAttributeDescription() { |
| | | return attribute.getAttributeDescription(); |
| | | } |
| | | |
| | | @Override |
| | | public String getAttributeDescriptionAsString() { |
| | | return attribute.getAttributeDescription().toString(); |
| | | } |
| | | |
| | | @Override |
| | | public Iterator<ByteString> iterator() { |
| | | return attribute.iterator(); |
| | | } |
| | | |
| | | @Override |
| | | public boolean isEmpty() { |
| | | return attribute.isEmpty(); |
| | | } |
| | | |
| | | @Override |
| | | public int size() { |
| | | return attribute.size(); |
| | | } |
| | | |
| | | @Override |
| | | public boolean contains(Object value) { |
| | | return attribute.contains((ByteString) value); |
| | | } |
| | | |
| | | @Override |
| | | public <T> T[] toArray(T[] array) { |
| | | throw new UnsupportedOperationException(); |
| | | } |
| | | |
| | | @Override |
| | | public ByteString[] toArray() { |
| | | throw new UnsupportedOperationException(); |
| | | |
| | | } |
| | | |
| | | @Override |
| | | public <T> boolean retainAll(Collection<T> values, Collection<? super T> missingValues) { |
| | | throw new UnsupportedOperationException(); |
| | | } |
| | | |
| | | @Override |
| | | public boolean retainAll(Collection<?> values) { |
| | | throw new UnsupportedOperationException(); |
| | | } |
| | | |
| | | @Override |
| | | public <T> boolean removeAll(Collection<T> values, Collection<? super T> missingValues) { |
| | | throw new UnsupportedOperationException(); |
| | | } |
| | | |
| | | @Override |
| | | public boolean removeAll(Collection<?> values) { |
| | | throw new UnsupportedOperationException(); |
| | | } |
| | | |
| | | @Override |
| | | public boolean remove(Object value) { |
| | | throw new UnsupportedOperationException(); |
| | | } |
| | | |
| | | @Override |
| | | public AttributeParser parse() { |
| | | throw new UnsupportedOperationException(); |
| | | } |
| | | |
| | | @Override |
| | | public String firstValueAsString() { |
| | | throw new UnsupportedOperationException(); |
| | | } |
| | | |
| | | @Override |
| | | public ByteString firstValue() { |
| | | throw new UnsupportedOperationException(); |
| | | } |
| | | |
| | | @Override |
| | | public boolean containsAll(Collection<?> values) { |
| | | throw new UnsupportedOperationException(); |
| | | } |
| | | |
| | | @Override |
| | | public void clear() { |
| | | throw new UnsupportedOperationException(); |
| | | } |
| | | |
| | | @Override |
| | | public <T> boolean addAll(Collection<T> values, Collection<? super T> duplicateValues) { |
| | | throw new UnsupportedOperationException(); |
| | | } |
| | | |
| | | @Override |
| | | public boolean addAll(Collection<? extends ByteString> values) { |
| | | throw new UnsupportedOperationException(); |
| | | } |
| | | |
| | | @Override |
| | | public boolean add(Object... values) { |
| | | throw new UnsupportedOperationException(); |
| | | } |
| | | |
| | | @Override |
| | | public boolean add(ByteString value) { |
| | | throw new UnsupportedOperationException(); |
| | | } |
| | | }; |
| | | } |
| | | |
| | | /** |
| | | * Converts from an <code>Iterable</code> of OpenDJ server |
| | | * {@link org.opends.server.types.Attribute} to a <code>List</code> of OpenDJ |
| | | * LDAP SDK {@link org.forgerock.opendj.ldap.Attribute}. |
| | |
| | | |
| | | /** |
| | | * Converts from OpenDJ server |
| | | * {@link org.opends.server.types.SearchResultEntry} to OpenDJ LDAP SDK |
| | | * {@link org.forgerock.opendj.ldap.responses.SearchResultEntry}. |
| | | * |
| | | * @param srvResultEntry |
| | | * value to convert |
| | | * @return the converted value |
| | | */ |
| | | public static org.forgerock.opendj.ldap.responses.SearchResultEntry partiallyWrap( |
| | | final org.opends.server.types.SearchResultEntry srvResultEntry) { |
| | | |
| | | final ArrayList<Control> controls = new ArrayList<>(srvResultEntry.getControls().size()); |
| | | for(org.opends.server.types.Control control : srvResultEntry.getControls()) { |
| | | controls.add(Converters.from(control)); |
| | | } |
| | | |
| | | return new SearchResultEntry() { |
| | | @Override |
| | | public DN getName() { |
| | | return srvResultEntry.getName(); |
| | | } |
| | | |
| | | @Override |
| | | public Iterable<Attribute> getAllAttributes() { |
| | | return Iterables.transformedIterable(srvResultEntry.getAllAttributes(), |
| | | new Function<org.opends.server.types.Attribute, Attribute, NeverThrowsException>() { |
| | | @Override |
| | | public Attribute apply(org.opends.server.types.Attribute value) throws NeverThrowsException { |
| | | return Converters.partiallyWrap(value); |
| | | } |
| | | }); |
| | | } |
| | | |
| | | @Override |
| | | public Attribute getAttribute(String attributeDescription) { |
| | | return Converters.from(srvResultEntry.getAllAttributes(attributeDescription).iterator().next()); |
| | | } |
| | | |
| | | @Override |
| | | public int getAttributeCount() { |
| | | return srvResultEntry.getAttributes().size(); |
| | | } |
| | | |
| | | @Override |
| | | public List<Control> getControls() { |
| | | return controls; |
| | | } |
| | | |
| | | @Override |
| | | public AttributeParser parseAttribute(String attributeDescription) { |
| | | throw new UnsupportedOperationException(); |
| | | } |
| | | |
| | | @Override |
| | | public AttributeParser parseAttribute(AttributeDescription attributeDescription) { |
| | | throw new UnsupportedOperationException(); |
| | | } |
| | | |
| | | @Override |
| | | public boolean containsControl(String oid) { |
| | | throw new UnsupportedOperationException(); |
| | | } |
| | | |
| | | @Override |
| | | public SearchResultEntry setName(String dn) { |
| | | throw new UnsupportedOperationException(); |
| | | } |
| | | |
| | | @Override |
| | | public SearchResultEntry setName(DN dn) { |
| | | throw new UnsupportedOperationException(); |
| | | } |
| | | |
| | | @Override |
| | | public SearchResultEntry replaceAttribute(String attributeDescription, Object... values) { |
| | | throw new UnsupportedOperationException(); |
| | | } |
| | | |
| | | @Override |
| | | public boolean replaceAttribute(Attribute attribute) { |
| | | throw new UnsupportedOperationException(); |
| | | } |
| | | |
| | | @Override |
| | | public SearchResultEntry removeAttribute(String attributeDescription, Object... values) { |
| | | throw new UnsupportedOperationException(); |
| | | } |
| | | |
| | | @Override |
| | | public boolean removeAttribute(AttributeDescription attributeDescription) { |
| | | throw new UnsupportedOperationException(); |
| | | } |
| | | |
| | | @Override |
| | | public boolean removeAttribute(Attribute attribute, Collection<? super ByteString> missingValues) { |
| | | throw new UnsupportedOperationException(); |
| | | } |
| | | |
| | | @Override |
| | | public <C extends Control> C getControl(ControlDecoder<C> decoder, DecodeOptions options) |
| | | throws DecodeException { |
| | | throw new UnsupportedOperationException(); |
| | | } |
| | | |
| | | @Override |
| | | public Attribute getAttribute(AttributeDescription attributeDescription) { |
| | | throw new UnsupportedOperationException(); |
| | | } |
| | | |
| | | @Override |
| | | public Iterable<Attribute> getAllAttributes(String attributeDescription) { |
| | | throw new UnsupportedOperationException(); |
| | | } |
| | | |
| | | @Override |
| | | public Iterable<Attribute> getAllAttributes(AttributeDescription attributeDescription) { |
| | | throw new UnsupportedOperationException(); |
| | | } |
| | | |
| | | @Override |
| | | public boolean containsAttribute(String attributeDescription, Object... values) { |
| | | throw new UnsupportedOperationException(); |
| | | } |
| | | |
| | | @Override |
| | | public boolean containsAttribute(Attribute attribute, Collection<? super ByteString> missingValues) { |
| | | throw new UnsupportedOperationException(); |
| | | } |
| | | |
| | | @Override |
| | | public SearchResultEntry clearAttributes() { |
| | | throw new UnsupportedOperationException(); |
| | | } |
| | | |
| | | @Override |
| | | public SearchResultEntry addControl(Control control) { |
| | | throw new UnsupportedOperationException(); |
| | | } |
| | | |
| | | @Override |
| | | public SearchResultEntry addAttribute(String attributeDescription, Object... values) { |
| | | throw new UnsupportedOperationException(); |
| | | } |
| | | |
| | | @Override |
| | | public boolean addAttribute(Attribute attribute, Collection<? super ByteString> duplicateValues) { |
| | | throw new UnsupportedOperationException(); |
| | | } |
| | | |
| | | @Override |
| | | public boolean addAttribute(Attribute attribute) { |
| | | throw new UnsupportedOperationException(); |
| | | } |
| | | }; |
| | | } |
| | | |
| | | /** |
| | | * Converts from OpenDJ server |
| | | * {@link org.opends.server.types.Entry} to OpenDJ LDAP SDK |
| | | * {@link org.forgerock.opendj.ldap.Entry}. |
| | | * |
| New file |
| | |
| | | /* |
| | | * 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 2016 ForgeRock AS. |
| | | */ |
| | | package org.forgerock.opendj.reactive; |
| | | |
| | | import static com.forgerock.reactive.RxJavaStreams.*; |
| | | import static org.forgerock.util.Reject.checkNotNull; |
| | | |
| | | import java.io.IOException; |
| | | import java.util.concurrent.Callable; |
| | | import java.util.concurrent.Executor; |
| | | |
| | | import org.forgerock.opendj.io.AbstractLDAPMessageHandler; |
| | | import org.forgerock.opendj.io.LDAP; |
| | | import org.forgerock.opendj.ldap.DecodeException; |
| | | import org.forgerock.opendj.ldap.DecodeOptions; |
| | | import org.forgerock.opendj.ldap.LDAPConnectionFactory; |
| | | import org.forgerock.opendj.ldap.requests.AbandonRequest; |
| | | import org.forgerock.opendj.ldap.requests.AddRequest; |
| | | import org.forgerock.opendj.ldap.requests.GenericBindRequest; |
| | | import org.forgerock.opendj.ldap.requests.ModifyDNRequest; |
| | | import org.forgerock.opendj.ldap.requests.ModifyRequest; |
| | | import org.forgerock.opendj.ldap.requests.Request; |
| | | import org.forgerock.opendj.ldap.requests.SearchRequest; |
| | | import org.forgerock.opendj.ldap.requests.UnbindRequest; |
| | | import org.forgerock.opendj.ldap.responses.Response; |
| | | import org.forgerock.opendj.ldap.spi.LdapMessages.LdapRawMessage; |
| | | import org.forgerock.util.Function; |
| | | import org.reactivestreams.Publisher; |
| | | |
| | | import com.forgerock.reactive.ReactiveFilter; |
| | | import com.forgerock.reactive.ReactiveFilter.SimpleReactiveFilter; |
| | | import com.forgerock.reactive.ReactiveHandler; |
| | | import com.forgerock.reactive.RxJavaStreams; |
| | | import com.forgerock.reactive.Single; |
| | | import com.forgerock.reactive.Stream; |
| | | |
| | | import io.reactivex.Flowable; |
| | | import io.reactivex.Scheduler; |
| | | import io.reactivex.schedulers.Schedulers; |
| | | |
| | | /** Maintains a set of standard {@link ReactiveHandler} / {@link ReactiveFilter} which can be used in ldap endpoint. */ |
| | | public final class Components { |
| | | |
| | | /** |
| | | * Routes incoming request to dedicated handler. |
| | | * |
| | | * @param <CTX> |
| | | * Context type in which request are processed |
| | | * @param <REQ> |
| | | * Type of routed request |
| | | * @param <REP> |
| | | * Type of routed response |
| | | * @param routes |
| | | * {@link Route} where request will be forwarded to |
| | | * @param defaultRoute |
| | | * If no route can be applied for a specific request, it'll be forwarded to the defaultRoute |
| | | * @return A new {@link ReactiveHandler} routing incoming requests |
| | | */ |
| | | public static <CTX, REQ, REP> ReactiveHandler<CTX, REQ, REP> routeTo(final Iterable<Route<CTX, REQ, REP>> routes, |
| | | final ReactiveHandler<CTX, REQ, REP> defaultRoute) { |
| | | /** Routes requests. */ |
| | | final class RouterHandler implements ReactiveHandler<CTX, REQ, REP> { |
| | | @Override |
| | | public Single<REP> handle(final CTX context, final REQ request) throws Exception { |
| | | for (final Route<CTX, REQ, REP> route : routes) { |
| | | if (route.predicate.matches(request, context)) { |
| | | return route.handler.handle(context, request); |
| | | } |
| | | } |
| | | return defaultRoute.handle(context, request); |
| | | } |
| | | } |
| | | return new RouterHandler(); |
| | | } |
| | | |
| | | /** |
| | | * Allows to transform all aspects of a {@link Request}. |
| | | * |
| | | * @param <CTX> |
| | | * Context type in which request are processed |
| | | * @param requestTransformer |
| | | * Function in charge of performing the {@link Request} transformation |
| | | * @param responseTransformer |
| | | * Function in charge of performing the {@link Response} transformation |
| | | * @return A new policy performing the {@link Request} and {@link Response} transformation. |
| | | */ |
| | | public static <CTX> SimpleReactiveFilter<CTX, Request, Stream<Response>> transform( |
| | | final Function<Request, Request, Exception> requestTransformer, |
| | | final Function<Response, Response, Exception> responseTransformer) { |
| | | /** Transforms {@link Request} and {@link Response}. */ |
| | | final class TransformFilter extends SimpleReactiveFilter<CTX, Request, Stream<Response>> { |
| | | @Override |
| | | public Single<Stream<Response>> filter(final CTX context, final Request request, |
| | | final ReactiveHandler<CTX, Request, Stream<Response>> next) throws Exception { |
| | | return next.handle(context, requestTransformer.apply(request)) |
| | | .map(new Function<Stream<Response>, Stream<Response>, Exception>() { |
| | | @Override |
| | | public Stream<Response> apply(Stream<Response> responses) throws Exception { |
| | | return responses.map(new Function<Response, Response, Exception>() { |
| | | @Override |
| | | public Response apply(Response value) throws Exception { |
| | | return responseTransformer.apply(value); |
| | | } |
| | | }); |
| | | } |
| | | }); |
| | | } |
| | | } |
| | | return new TransformFilter(); |
| | | } |
| | | |
| | | /** |
| | | * Forward {@link Request} to the provided {@link LDAPConnectionFactory}. |
| | | * |
| | | * @param connectionFactory |
| | | * The {@link LDAPConnectionFactory} used to send the forwarded {@link Request} |
| | | * @return a {@link ReactiveHandler} Forwarding {@link Request} to the {@link LDAPConnectionFactory} |
| | | */ |
| | | public static ReactiveHandler<LDAPClientConnection2, Request, Stream<Response>> proxyTo( |
| | | LDAPConnectionFactory connectionFactory) { |
| | | return new ProxyToHandler(connectionFactory); |
| | | } |
| | | |
| | | /** |
| | | * {@link ReactiveHandler} responding with the provided Response for all incoming requests. |
| | | * |
| | | * @param <CTX> |
| | | * Context type in which request are processed |
| | | * @param response |
| | | * The {@link Response} to send as response for all {@link Request} |
| | | * @return a {@link ReactiveHandler} replying {@link Response} for all {@link Request} |
| | | */ |
| | | public static <CTX> ReactiveHandler<CTX, Request, Stream<Response>> respondWith(final Response response) { |
| | | return new ReactiveHandler<CTX, Request, Stream<Response>>() { |
| | | @Override |
| | | public Single<Stream<Response>> handle(final CTX context, final Request request) { |
| | | if (request instanceof UnbindRequest) { |
| | | return singleFrom(RxJavaStreams.<Response>emptyStream()); |
| | | } |
| | | return singleFrom(streamFrom(response)); |
| | | } |
| | | }; |
| | | } |
| | | |
| | | /** |
| | | * Decodes incoming {@link LdapRawMessage} into a {@link Request}. |
| | | * |
| | | * @param decodeOptions |
| | | * {@link DecodeOptions} used during {@link Request} decoding |
| | | * @return a {@link ReactiveFilter} decoding {@link LdapRawMessage} into {@link Request} |
| | | */ |
| | | public static ReactiveFilter<LDAPClientConnection2, LdapRawMessage, Stream<Response>, |
| | | Request, Stream<Response>> |
| | | decodeRequest(final DecodeOptions decodeOptions) { |
| | | return new ReactiveFilter<LDAPClientConnection2, LdapRawMessage, Stream<Response>, |
| | | Request, Stream<Response>>() { |
| | | @Override |
| | | public Single<Stream<Response>> filter(final LDAPClientConnection2 context, |
| | | final LdapRawMessage encodedRequestMessage, |
| | | final ReactiveHandler<LDAPClientConnection2, Request, Stream<Response>> next) throws Exception { |
| | | return newSingle(new Single.OnSubscribe<Request>() { |
| | | @Override |
| | | public void onSubscribe(final Single.Emitter<Request> emitter) throws Exception { |
| | | LDAP.getReader(encodedRequestMessage.getContent(), decodeOptions) |
| | | .readMessage(new AbstractLDAPMessageHandler() { |
| | | @Override |
| | | public void abandonRequest(final int messageID, final AbandonRequest request) |
| | | throws DecodeException, IOException { |
| | | emitter.onSuccess(request); |
| | | } |
| | | |
| | | @Override |
| | | public void addRequest(int messageID, AddRequest request) |
| | | throws DecodeException, IOException { |
| | | emitter.onSuccess(request); |
| | | } |
| | | |
| | | @Override |
| | | public void bindRequest(int messageID, int version, GenericBindRequest request) |
| | | throws DecodeException, IOException { |
| | | emitter.onSuccess(request); |
| | | } |
| | | |
| | | @Override |
| | | public void modifyDNRequest(int messageID, ModifyDNRequest request) |
| | | throws DecodeException, IOException { |
| | | emitter.onSuccess(request); |
| | | } |
| | | |
| | | @Override |
| | | public void modifyRequest(int messageID, ModifyRequest request) |
| | | throws DecodeException, IOException { |
| | | emitter.onSuccess(request); |
| | | } |
| | | |
| | | @Override |
| | | public void searchRequest(int messageID, SearchRequest request) |
| | | throws DecodeException, IOException { |
| | | emitter.onSuccess(request); |
| | | } |
| | | |
| | | @Override |
| | | public void unbindRequest(int messageID, UnbindRequest request) |
| | | throws DecodeException, IOException { |
| | | emitter.onSuccess(request); |
| | | } |
| | | }); |
| | | } |
| | | }).flatMap(new Function<Request, Publisher<Stream<Response>>, Exception>() { |
| | | @Override |
| | | public Publisher<Stream<Response>> apply(Request t) throws Exception { |
| | | return next.handle(context, t); |
| | | } |
| | | }); |
| | | } |
| | | }; |
| | | } |
| | | |
| | | /** |
| | | * Invoke the following {@link ReactiveFilter} from the given {@link Executor}. |
| | | * |
| | | * @param <CTX> |
| | | * Context type in which request are processed |
| | | * @param <REQ> |
| | | * Type of dispatched request |
| | | * @param <REP> |
| | | * Type of dispatched response |
| | | * @param executor |
| | | * The {@link Executor} used to forward the request |
| | | * @return A {@link ReactiveFilter} fowarding {@link Request} through the {@link Executor} |
| | | */ |
| | | public static <CTX, REQ, REP> ReactiveFilter<CTX, REQ, REP, REQ, REP> dispatch(final Executor executor) { |
| | | /** Dispatches request into an {@link Executor}. */ |
| | | final class DispatchFilter extends SimpleReactiveFilter<CTX, REQ, REP> { |
| | | private final Scheduler executor; |
| | | |
| | | DispatchFilter(final Executor executor) { |
| | | this.executor = Schedulers.from(checkNotNull(executor, "executor must not be null")); |
| | | } |
| | | |
| | | @Override |
| | | public Single<REP> filter(final CTX context, final REQ request, |
| | | final ReactiveHandler<CTX, REQ, REP> next) { |
| | | return singleFromPublisher(Flowable.defer(new Callable<Publisher<REP>>() { |
| | | @Override |
| | | public Publisher<REP> call() throws Exception { |
| | | return next.handle(context, request); |
| | | } |
| | | }).subscribeOn(executor)); |
| | | } |
| | | } |
| | | return new DispatchFilter(executor); |
| | | } |
| | | |
| | | private Components() { |
| | | // Prevent instantiation |
| | | } |
| | | } |
| New file |
| | |
| | | /* |
| | | * 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 2006-2010 Sun Microsystems, Inc. |
| | | * Portions Copyright 2010-2016 ForgeRock AS. |
| | | */ |
| | | package org.forgerock.opendj.reactive; |
| | | |
| | | import static com.forgerock.reactive.RxJavaStreams.*; |
| | | import static org.opends.messages.CoreMessages.*; |
| | | import static org.opends.messages.ProtocolMessages.*; |
| | | import static org.opends.server.loggers.AccessLogger.logDisconnect; |
| | | import static org.opends.server.protocols.ldap.LDAPConstants.*; |
| | | import static org.opends.server.util.ServerConstants.OID_START_TLS_REQUEST; |
| | | import static org.opends.server.util.StaticUtils.getExceptionMessage; |
| | | |
| | | import java.net.InetAddress; |
| | | import java.nio.channels.Selector; |
| | | import java.nio.channels.SocketChannel; |
| | | import java.security.cert.Certificate; |
| | | import java.util.Collection; |
| | | import java.util.Iterator; |
| | | import java.util.List; |
| | | import java.util.concurrent.ConcurrentHashMap; |
| | | import java.util.concurrent.atomic.AtomicLong; |
| | | |
| | | import org.forgerock.i18n.LocalizableMessage; |
| | | import org.forgerock.i18n.LocalizableMessageBuilder; |
| | | import org.forgerock.i18n.slf4j.LocalizedLogger; |
| | | import org.forgerock.opendj.adapter.server3x.Converters; |
| | | import org.forgerock.opendj.ldap.ByteString; |
| | | import org.forgerock.opendj.ldap.DN; |
| | | import org.forgerock.opendj.ldap.LDAPClientContext; |
| | | import org.forgerock.opendj.ldap.ResultCode; |
| | | import org.forgerock.opendj.ldap.responses.CompareResult; |
| | | import org.forgerock.opendj.ldap.responses.Response; |
| | | import org.forgerock.opendj.ldap.responses.Responses; |
| | | import org.forgerock.opendj.ldap.responses.Result; |
| | | import org.forgerock.opendj.ldap.spi.LdapMessages.LdapRawMessage; |
| | | import org.opends.server.api.ClientConnection; |
| | | import org.opends.server.api.ConnectionHandler; |
| | | import org.opends.server.core.AbandonOperationBasis; |
| | | import org.opends.server.core.AddOperationBasis; |
| | | import org.opends.server.core.BindOperationBasis; |
| | | import org.opends.server.core.CompareOperationBasis; |
| | | import org.opends.server.core.DeleteOperationBasis; |
| | | import org.opends.server.core.DirectoryServer; |
| | | import org.opends.server.core.ExtendedOperationBasis; |
| | | import org.opends.server.core.ModifyDNOperationBasis; |
| | | import org.opends.server.core.ModifyOperationBasis; |
| | | import org.opends.server.core.PersistentSearch; |
| | | import org.opends.server.core.PluginConfigManager; |
| | | import org.opends.server.core.QueueingStrategy; |
| | | import org.opends.server.core.SearchOperation; |
| | | import org.opends.server.core.SearchOperationBasis; |
| | | import org.opends.server.core.UnbindOperationBasis; |
| | | import org.opends.server.extensions.TLSCapableConnection; |
| | | import org.opends.server.protocols.ldap.AbandonRequestProtocolOp; |
| | | import org.opends.server.protocols.ldap.AddRequestProtocolOp; |
| | | import org.opends.server.protocols.ldap.BindRequestProtocolOp; |
| | | import org.opends.server.protocols.ldap.CompareRequestProtocolOp; |
| | | import org.opends.server.protocols.ldap.DeleteRequestProtocolOp; |
| | | import org.opends.server.protocols.ldap.ExtendedRequestProtocolOp; |
| | | import org.opends.server.protocols.ldap.LDAPMessage; |
| | | import org.opends.server.protocols.ldap.LDAPReader; |
| | | import org.opends.server.protocols.ldap.LDAPResultCode; |
| | | import org.opends.server.protocols.ldap.LDAPStatistics; |
| | | import org.opends.server.protocols.ldap.ModifyDNRequestProtocolOp; |
| | | import org.opends.server.protocols.ldap.ModifyRequestProtocolOp; |
| | | import org.opends.server.protocols.ldap.SearchRequestProtocolOp; |
| | | import org.opends.server.types.AuthenticationType; |
| | | import org.opends.server.types.CancelRequest; |
| | | import org.opends.server.types.CancelResult; |
| | | import org.opends.server.types.Control; |
| | | import org.opends.server.types.DirectoryException; |
| | | import org.opends.server.types.DisconnectReason; |
| | | import org.opends.server.types.IntermediateResponse; |
| | | import org.opends.server.types.Operation; |
| | | import org.opends.server.types.OperationType; |
| | | import org.opends.server.types.SearchResultEntry; |
| | | import org.opends.server.types.SearchResultReference; |
| | | import org.opends.server.util.TimeThread; |
| | | |
| | | import com.forgerock.reactive.ReactiveHandler; |
| | | import com.forgerock.reactive.Single; |
| | | import com.forgerock.reactive.Stream; |
| | | |
| | | import io.reactivex.BackpressureOverflowStrategy; |
| | | import io.reactivex.Flowable; |
| | | import io.reactivex.FlowableEmitter; |
| | | import io.reactivex.FlowableEmitter.BackpressureMode; |
| | | import io.reactivex.FlowableOnSubscribe; |
| | | |
| | | /** |
| | | * This class defines an LDAP client connection, which is a type of |
| | | * client connection that will be accepted by an instance of the LDAP |
| | | * connection handler and have its requests decoded by an LDAP request |
| | | * handler. |
| | | */ |
| | | public final class LDAPClientConnection2 extends ClientConnection implements |
| | | TLSCapableConnection, ReactiveHandler<QueueingStrategy, LdapRawMessage, Stream<Response>> |
| | | { |
| | | private static final String REACTIVE_OUT = "reactive.out"; |
| | | |
| | | /** The tracer object for the debug logger. */ |
| | | private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass(); |
| | | |
| | | /** The time that the last operation was completed. */ |
| | | private final AtomicLong lastCompletionTime; |
| | | /** The next operation ID that should be used for this connection. */ |
| | | private final AtomicLong nextOperationID; |
| | | |
| | | /** |
| | | * Indicates whether the Directory Server believes this connection to be valid |
| | | * and available for communication. |
| | | */ |
| | | private volatile boolean connectionValid; |
| | | |
| | | /** |
| | | * Indicates whether this connection is about to be closed. This will be used |
| | | * to prevent accepting new requests while a disconnect is in progress. |
| | | */ |
| | | private boolean disconnectRequested; |
| | | |
| | | /** |
| | | * Indicates whether the connection should keep statistics regarding the |
| | | * operations that it is performing. |
| | | */ |
| | | private final boolean keepStats; |
| | | |
| | | /** The set of all operations currently in progress on this connection. */ |
| | | private final ConcurrentHashMap<Integer, Operation> operationsInProgress; |
| | | |
| | | /** |
| | | * The number of operations performed on this connection. Used to compare with |
| | | * the resource limits of the network group. |
| | | */ |
| | | private final AtomicLong operationsPerformed; |
| | | |
| | | /** The port on the client from which this connection originated. */ |
| | | private final int clientPort; |
| | | /** The LDAP version that the client is using to communicate with the server. */ |
| | | private int ldapVersion; |
| | | /** The port on the server to which this client has connected. */ |
| | | private final int serverPort; |
| | | |
| | | /** The reference to the connection handler that accepted this connection. */ |
| | | private final LDAPConnectionHandler2 connectionHandler; |
| | | /** The statistics tracker associated with this client connection. */ |
| | | private final LDAPStatistics statTracker; |
| | | private final boolean useNanoTime; |
| | | |
| | | /** The connection ID assigned to this connection. */ |
| | | private final long connectionID; |
| | | |
| | | /** The lock used to provide threadsafe access to the set of operations in progress. */ |
| | | private final Object opsInProgressLock; |
| | | |
| | | /** The socket channel with which this client connection is associated. */ |
| | | private final LDAPClientContext clientContext; |
| | | |
| | | /** The string representation of the address of the client. */ |
| | | private final String clientAddress; |
| | | /** The name of the protocol that the client is using to communicate with the server. */ |
| | | private final String protocol; |
| | | /** The string representation of the address of the server to which the client has connected. */ |
| | | private final String serverAddress; |
| | | |
| | | /** |
| | | * Creates a new LDAP client connection with the provided information. |
| | | * |
| | | * @param connectionHandler |
| | | * The connection handler that accepted this connection. |
| | | * @param clientContext |
| | | * The socket channel that may be used to communicate with |
| | | * the client. |
| | | * @param protocol String representing the protocol (LDAP or LDAP+SSL). |
| | | * @throws LdapException |
| | | * @throws DirectoryException If SSL initialisation fails. |
| | | */ |
| | | LDAPClientConnection2(LDAPConnectionHandler2 connectionHandler, LDAPClientContext clientContext, String protocol, |
| | | boolean keepStats) |
| | | { |
| | | this.connectionHandler = connectionHandler; |
| | | this.clientContext = clientContext; |
| | | opsInProgressLock = new Object(); |
| | | ldapVersion = 3; |
| | | lastCompletionTime = new AtomicLong(TimeThread.getTime()); |
| | | nextOperationID = new AtomicLong(0); |
| | | connectionValid = true; |
| | | disconnectRequested = false; |
| | | operationsInProgress = new ConcurrentHashMap<>(); |
| | | operationsPerformed = new AtomicLong(0); |
| | | this.keepStats = keepStats; |
| | | this.protocol = protocol; |
| | | |
| | | clientAddress = clientContext.getPeerAddress().getAddress().getHostAddress(); |
| | | clientPort = clientContext.getPeerAddress().getPort(); |
| | | serverAddress = clientContext.getLocalAddress().getAddress().getHostAddress(); |
| | | serverPort = clientContext.getLocalAddress().getPort(); |
| | | |
| | | statTracker = this.connectionHandler.getStatTracker(); |
| | | if (keepStats) |
| | | { |
| | | statTracker.updateConnect(); |
| | | this.useNanoTime = DirectoryServer.getUseNanoTime(); |
| | | } |
| | | else |
| | | { |
| | | this.useNanoTime = false; |
| | | } |
| | | |
| | | connectionID = DirectoryServer.newConnectionAccepted(this); |
| | | } |
| | | |
| | | /** |
| | | * Retrieves the connection ID assigned to this connection. |
| | | * |
| | | * @return The connection ID assigned to this connection. |
| | | */ |
| | | @Override |
| | | public long getConnectionID() |
| | | { |
| | | return connectionID; |
| | | } |
| | | |
| | | /** |
| | | * Retrieves the connection handler that accepted this client |
| | | * connection. |
| | | * |
| | | * @return The connection handler that accepted this client |
| | | * connection. |
| | | */ |
| | | @Override |
| | | public ConnectionHandler<?> getConnectionHandler() |
| | | { |
| | | return connectionHandler; |
| | | } |
| | | |
| | | /** |
| | | * Retrieves the socket channel that can be used to communicate with |
| | | * the client. |
| | | * |
| | | * @return The socket channel that can be used to communicate with the |
| | | * client. |
| | | */ |
| | | @Override |
| | | public SocketChannel getSocketChannel() |
| | | { |
| | | throw new UnsupportedOperationException(); |
| | | } |
| | | |
| | | /** |
| | | * Retrieves the protocol that the client is using to communicate with |
| | | * the Directory Server. |
| | | * |
| | | * @return The protocol that the client is using to communicate with |
| | | * the Directory Server. |
| | | */ |
| | | @Override |
| | | public String getProtocol() |
| | | { |
| | | return protocol; |
| | | } |
| | | |
| | | /** |
| | | * Retrieves a string representation of the address of the client. |
| | | * |
| | | * @return A string representation of the address of the client. |
| | | */ |
| | | @Override |
| | | public String getClientAddress() |
| | | { |
| | | return clientAddress; |
| | | } |
| | | |
| | | /** |
| | | * Retrieves the port number for this connection on the client system. |
| | | * |
| | | * @return The port number for this connection on the client system. |
| | | */ |
| | | @Override |
| | | public int getClientPort() |
| | | { |
| | | return clientPort; |
| | | } |
| | | |
| | | /** |
| | | * Retrieves a string representation of the address on the server to |
| | | * which the client connected. |
| | | * |
| | | * @return A string representation of the address on the server to |
| | | * which the client connected. |
| | | */ |
| | | @Override |
| | | public String getServerAddress() |
| | | { |
| | | return serverAddress; |
| | | } |
| | | |
| | | /** |
| | | * Retrieves the port number for this connection on the server system. |
| | | * |
| | | * @return The port number for this connection on the server system. |
| | | */ |
| | | @Override |
| | | public int getServerPort() |
| | | { |
| | | return serverPort; |
| | | } |
| | | |
| | | /** |
| | | * Retrieves the <CODE>java.net.InetAddress</CODE> associated with the |
| | | * remote client system. |
| | | * |
| | | * @return The <CODE>java.net.InetAddress</CODE> associated with the |
| | | * remote client system. It may be <CODE>null</CODE> if the |
| | | * client is not connected over an IP-based connection. |
| | | */ |
| | | @Override |
| | | public InetAddress getRemoteAddress() |
| | | { |
| | | return clientContext.getPeerAddress().getAddress(); |
| | | } |
| | | |
| | | /** |
| | | * Retrieves the <CODE>java.net.InetAddress</CODE> for the Directory |
| | | * Server system to which the client has established the connection. |
| | | * |
| | | * @return The <CODE>java.net.InetAddress</CODE> for the Directory |
| | | * Server system to which the client has established the |
| | | * connection. It may be <CODE>null</CODE> if the client is |
| | | * not connected over an IP-based connection. |
| | | */ |
| | | @Override |
| | | public InetAddress getLocalAddress() |
| | | { |
| | | return clientContext.getLocalAddress().getAddress(); |
| | | } |
| | | |
| | | @Override |
| | | public boolean isConnectionValid() |
| | | { |
| | | return this.connectionValid; |
| | | } |
| | | |
| | | /** |
| | | * Indicates whether this client connection is currently using a |
| | | * secure mechanism to communicate with the server. Note that this may |
| | | * change over time based on operations performed by the client or |
| | | * server (e.g., it may go from <CODE>false</CODE> to |
| | | * <CODE>true</CODE> if the client uses the StartTLS extended |
| | | * operation). |
| | | * |
| | | * @return <CODE>true</CODE> if the client connection is currently |
| | | * using a secure mechanism to communicate with the server, or |
| | | * <CODE>false</CODE> if not. |
| | | */ |
| | | @Override |
| | | public boolean isSecure() |
| | | { |
| | | return false; |
| | | } |
| | | |
| | | /** |
| | | * Sends a response to the client based on the information in the |
| | | * provided operation. |
| | | * |
| | | * @param operation |
| | | * The operation for which to send the response. |
| | | */ |
| | | @Override |
| | | public void sendResponse(Operation operation) |
| | | { |
| | | // Since this is the final response for this operation, we can go |
| | | // ahead and remove it from the "operations in progress" list. It |
| | | // can't be canceled after this point, and this will avoid potential |
| | | // race conditions in which the client immediately sends another |
| | | // request with the same message ID as was used for this operation. |
| | | |
| | | if (keepStats) { |
| | | long time; |
| | | if (useNanoTime) { |
| | | time = operation.getProcessingNanoTime(); |
| | | } else { |
| | | time = operation.getProcessingTime(); |
| | | } |
| | | this.statTracker.updateOperationMonitoringData( |
| | | operation.getOperationType(), |
| | | time); |
| | | } |
| | | |
| | | // Avoid sending the response if one has already been sent. This may happen |
| | | // if operation processing encounters a run-time exception after sending the |
| | | // response: the worker thread exception handling code will attempt to send |
| | | // an error result to the client indicating that a problem occurred. |
| | | if (removeOperationInProgress(operation.getMessageID())) |
| | | { |
| | | final Response response = operationToResponse(operation); |
| | | final FlowableEmitter<Response> out = getOut(operation); |
| | | if (response != null) |
| | | { |
| | | out.onNext(response); |
| | | } |
| | | out.onComplete(); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * Retrieves an LDAPMessage containing a response generated from the |
| | | * provided operation. |
| | | * |
| | | * @param operation |
| | | * The operation to use to generate the response LDAPMessage. |
| | | * @return An LDAPMessage containing a response generated from the |
| | | * provided operation. |
| | | */ |
| | | private Response operationToResponse(Operation operation) |
| | | { |
| | | ResultCode resultCode = operation.getResultCode(); |
| | | if (resultCode == null) |
| | | { |
| | | // This must mean that the operation has either not yet completed |
| | | // or that it completed without a result for some reason. In any |
| | | // case, log a message and set the response to "operations error". |
| | | logger.error(ERR_LDAP_CLIENT_SEND_RESPONSE_NO_RESULT_CODE, operation.getOperationType(), |
| | | operation.getConnectionID(), operation.getOperationID()); |
| | | resultCode = DirectoryServer.getServerErrorResultCode(); |
| | | } |
| | | |
| | | LocalizableMessageBuilder errorMessage = operation.getErrorMessage(); |
| | | String matchedDN = operation.getMatchedDN() != null ? operation.getMatchedDN().toString() : null; |
| | | |
| | | // Referrals are not allowed for LDAPv2 clients. |
| | | List<String> referralURLs; |
| | | if (ldapVersion == 2) |
| | | { |
| | | referralURLs = null; |
| | | |
| | | if (resultCode == ResultCode.REFERRAL) |
| | | { |
| | | resultCode = ResultCode.CONSTRAINT_VIOLATION; |
| | | errorMessage.append(ERR_LDAPV2_REFERRAL_RESULT_CHANGED.get()); |
| | | } |
| | | |
| | | List<String> opReferrals = operation.getReferralURLs(); |
| | | if (opReferrals != null && !opReferrals.isEmpty()) |
| | | { |
| | | StringBuilder referralsStr = new StringBuilder(); |
| | | Iterator<String> iterator = opReferrals.iterator(); |
| | | referralsStr.append(iterator.next()); |
| | | |
| | | while (iterator.hasNext()) |
| | | { |
| | | referralsStr.append(", "); |
| | | referralsStr.append(iterator.next()); |
| | | } |
| | | |
| | | errorMessage.append(ERR_LDAPV2_REFERRALS_OMITTED.get(referralsStr)); |
| | | } |
| | | } |
| | | else |
| | | { |
| | | referralURLs = operation.getReferralURLs(); |
| | | } |
| | | |
| | | final Result result; |
| | | switch (operation.getOperationType()) |
| | | { |
| | | case ADD: |
| | | result = Responses.newResult(resultCode).setDiagnosticMessage(errorMessage.toString()).setMatchedDN(matchedDN); |
| | | break; |
| | | case BIND: |
| | | result = Responses.newBindResult(resultCode).setDiagnosticMessage(errorMessage.toString()).setMatchedDN(matchedDN) |
| | | .setServerSASLCredentials(((BindOperationBasis) operation).getServerSASLCredentials()); |
| | | break; |
| | | case COMPARE: |
| | | result = Responses.newCompareResult(resultCode) |
| | | .setDiagnosticMessage(errorMessage.toString()) |
| | | .setMatchedDN(matchedDN); |
| | | break; |
| | | case DELETE: |
| | | result = Responses.newResult(resultCode).setDiagnosticMessage(errorMessage.toString()).setMatchedDN(matchedDN); |
| | | break; |
| | | case EXTENDED: |
| | | // If this an LDAPv2 client, then we can't send this. |
| | | if (ldapVersion == 2) |
| | | { |
| | | logger.error(ERR_LDAPV2_SKIPPING_EXTENDED_RESPONSE, |
| | | getConnectionID(), operation.getOperationID(), operation); |
| | | return null; |
| | | } |
| | | |
| | | ExtendedOperationBasis extOp = (ExtendedOperationBasis) operation; |
| | | result = Responses.newGenericExtendedResult(resultCode) |
| | | .setDiagnosticMessage(errorMessage.toString()) |
| | | .setMatchedDN(matchedDN) |
| | | .setOID(extOp.getResponseOID()).setValue(extOp.getResponseValue()); |
| | | break; |
| | | case MODIFY: |
| | | result = Responses.newResult(resultCode).setDiagnosticMessage(errorMessage.toString()).setMatchedDN(matchedDN); |
| | | break; |
| | | case MODIFY_DN: |
| | | result = Responses.newResult(resultCode).setDiagnosticMessage(errorMessage.toString()).setMatchedDN(matchedDN); |
| | | break; |
| | | case SEARCH: |
| | | result = Responses.newResult(resultCode).setDiagnosticMessage(errorMessage.toString()).setMatchedDN(matchedDN); |
| | | break; |
| | | default: |
| | | // This must be a type of operation that doesn't have a response. |
| | | // This shouldn't happen, so log a message and return. |
| | | logger.error(ERR_LDAP_CLIENT_SEND_RESPONSE_INVALID_OP, operation.getOperationType(), getConnectionID(), |
| | | operation.getOperationID(), operation); |
| | | return null; |
| | | } |
| | | if (referralURLs != null) |
| | | { |
| | | result.getReferralURIs().addAll(referralURLs); |
| | | } |
| | | |
| | | // Controls are not allowed for LDAPv2 clients. |
| | | if (ldapVersion != 2) |
| | | { |
| | | for(Control control : operation.getResponseControls()) { |
| | | result.addControl(Converters.from(control)); |
| | | } |
| | | } |
| | | |
| | | return result; |
| | | } |
| | | |
| | | /** |
| | | * Sends the provided search result entry to the client. |
| | | * |
| | | * @param searchOperation |
| | | * The search operation with which the entry is associated |
| | | * @param searchEntry |
| | | * The search result entry to be sent to the client |
| | | */ |
| | | @Override |
| | | public void sendSearchEntry(SearchOperation searchOperation, SearchResultEntry searchEntry) |
| | | { |
| | | getEmitter(searchOperation).onNext(toResponse(searchEntry)); |
| | | } |
| | | |
| | | private FlowableEmitter<Response> getEmitter(SearchOperation searchOperation) |
| | | { |
| | | return getOut(searchOperation); |
| | | } |
| | | |
| | | private Response toResponse(SearchResultEntry searchEntry) |
| | | { |
| | | return Responses.newSearchResultEntry(Converters.partiallyWrap(searchEntry)); |
| | | } |
| | | |
| | | private FlowableEmitter<Response> getOut(Operation operation) |
| | | { |
| | | return ((FlowableEmitter<Response>) operation.getAttachment(REACTIVE_OUT)); |
| | | } |
| | | |
| | | /** |
| | | * Sends the provided search result reference to the client. |
| | | * |
| | | * @param searchOperation |
| | | * The search operation with which the reference is |
| | | * associated. |
| | | * @param searchReference |
| | | * The search result reference to be sent to the client. |
| | | * @return <CODE>true</CODE> if the client is able to accept |
| | | * referrals, or <CODE>false</CODE> if the client cannot |
| | | * handle referrals and no more attempts should be made to |
| | | * send them for the associated search operation. |
| | | */ |
| | | @Override |
| | | public boolean sendSearchReference(SearchOperation searchOperation, SearchResultReference searchReference) |
| | | { |
| | | // Make sure this is not an LDAPv2 client. If it is, then they can't |
| | | // see referrals so we'll not send anything. Also, throw an |
| | | // exception so that the core server will know not to try sending |
| | | // any more referrals to this client for the rest of the operation. |
| | | if (ldapVersion == 2) |
| | | { |
| | | logger.error(ERR_LDAPV2_SKIPPING_SEARCH_REFERENCE, getConnectionID(), |
| | | searchOperation.getOperationID(), searchReference); |
| | | return false; |
| | | } |
| | | |
| | | final FlowableEmitter<Response> out = getOut(searchOperation); |
| | | out.onNext(Converters.from(searchReference)); |
| | | |
| | | return true; |
| | | } |
| | | |
| | | /** |
| | | * Sends the provided intermediate response message to the client. |
| | | * |
| | | * @param intermediateResponse |
| | | * The intermediate response message to be sent. |
| | | * @return <CODE>true</CODE> if processing on the associated operation |
| | | * should continue, or <CODE>false</CODE> if not. |
| | | */ |
| | | @Override |
| | | protected boolean sendIntermediateResponseMessage(IntermediateResponse intermediateResponse) |
| | | { |
| | | final Operation operation = intermediateResponse.getOperation(); |
| | | final FlowableEmitter<Response> out = getOut(operation); |
| | | |
| | | final Response response = |
| | | Responses.newGenericIntermediateResponse(intermediateResponse.getOID(), intermediateResponse.getValue()); |
| | | for (Control control : intermediateResponse.getControls()) |
| | | { |
| | | response.addControl(Converters.from(control)); |
| | | } |
| | | |
| | | out.onNext(response); |
| | | |
| | | // The only reason we shouldn't continue processing is if the |
| | | // connection is closed. |
| | | return connectionValid; |
| | | } |
| | | |
| | | /** |
| | | * Closes the connection to the client, optionally sending it a |
| | | * message indicating the reason for the closure. Note that the |
| | | * ability to send a notice of disconnection may not be available for |
| | | * all protocols or under all circumstances. |
| | | * |
| | | * @param disconnectReason |
| | | * The disconnect reason that provides the generic cause for |
| | | * the disconnect. |
| | | * @param sendNotification |
| | | * Indicates whether to try to provide notification to the |
| | | * client that the connection will be closed. |
| | | * @param message |
| | | * The message to include in the disconnect notification |
| | | * response. It may be <CODE>null</CODE> if no message is to |
| | | * be sent. |
| | | */ |
| | | @Override |
| | | public void disconnect(DisconnectReason disconnectReason, |
| | | boolean sendNotification, LocalizableMessage message) |
| | | { |
| | | // Set a flag indicating that the connection is being terminated so |
| | | // that no new requests will be accepted. Also cancel all operations |
| | | // in progress. |
| | | synchronized (opsInProgressLock) |
| | | { |
| | | // If we are already in the middle of a disconnect, then don't |
| | | // do anything. |
| | | if (disconnectRequested) |
| | | { |
| | | return; |
| | | } |
| | | |
| | | disconnectRequested = true; |
| | | } |
| | | |
| | | if (keepStats) |
| | | { |
| | | statTracker.updateDisconnect(); |
| | | } |
| | | |
| | | if (connectionID >= 0) |
| | | { |
| | | DirectoryServer.connectionClosed(this); |
| | | } |
| | | |
| | | // Indicate that this connection is no longer valid. |
| | | connectionValid = false; |
| | | |
| | | final LocalizableMessage cancelMessage; |
| | | if (message != null) |
| | | { |
| | | cancelMessage = new LocalizableMessageBuilder() |
| | | .append(disconnectReason.getClosureMessage()) |
| | | .append(": ") |
| | | .append(message) |
| | | .toMessage(); |
| | | } |
| | | else |
| | | { |
| | | cancelMessage = disconnectReason.getClosureMessage(); |
| | | } |
| | | cancelAllOperations(new CancelRequest(true, cancelMessage)); |
| | | finalizeConnectionInternal(); |
| | | |
| | | // See if we should send a notification to the client. If so, then |
| | | // construct and send a notice of disconnection unsolicited |
| | | // response. Note that we cannot send this notification to an LDAPv2 client. |
| | | if (sendNotification && ldapVersion != 2) |
| | | { |
| | | try |
| | | { |
| | | LocalizableMessage errMsg = message != null ? message : INFO_LDAP_CLIENT_GENERIC_NOTICE_OF_DISCONNECTION.get(); |
| | | clientContext.disconnect(ResultCode.valueOf(toResultCode(disconnectReason)), errMsg.toString()); |
| | | } |
| | | catch (Exception e) |
| | | { |
| | | // NYI -- Log a message indicating that we couldn't send the |
| | | // notice of disconnection. |
| | | logger.traceException(e); |
| | | } |
| | | } |
| | | |
| | | // NYI -- Deregister the client connection from any server components that |
| | | // might know about it. |
| | | |
| | | logDisconnect(this, disconnectReason, message); |
| | | |
| | | try |
| | | { |
| | | PluginConfigManager pluginManager = DirectoryServer.getPluginConfigManager(); |
| | | pluginManager.invokePostDisconnectPlugins(this, disconnectReason, message); |
| | | } |
| | | catch (Exception e) |
| | | { |
| | | logger.traceException(e); |
| | | } |
| | | } |
| | | |
| | | private int toResultCode(DisconnectReason disconnectReason) |
| | | { |
| | | switch (disconnectReason) |
| | | { |
| | | case PROTOCOL_ERROR: |
| | | return LDAPResultCode.PROTOCOL_ERROR; |
| | | case SERVER_SHUTDOWN: |
| | | return LDAPResultCode.UNAVAILABLE; |
| | | case SERVER_ERROR: |
| | | return DirectoryServer.getServerErrorResultCode().intValue(); |
| | | case ADMIN_LIMIT_EXCEEDED: |
| | | case IDLE_TIME_LIMIT_EXCEEDED: |
| | | case MAX_REQUEST_SIZE_EXCEEDED: |
| | | case IO_TIMEOUT: |
| | | return LDAPResultCode.ADMIN_LIMIT_EXCEEDED; |
| | | case CONNECTION_REJECTED: |
| | | return LDAPResultCode.CONSTRAINT_VIOLATION; |
| | | case INVALID_CREDENTIALS: |
| | | return LDAPResultCode.INVALID_CREDENTIALS; |
| | | default: |
| | | return LDAPResultCode.OTHER; |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * Retrieves the set of operations in progress for this client |
| | | * connection. This list must not be altered by any caller. |
| | | * |
| | | * @return The set of operations in progress for this client |
| | | * connection. |
| | | */ |
| | | @Override |
| | | public Collection<Operation> getOperationsInProgress() |
| | | { |
| | | return operationsInProgress.values(); |
| | | } |
| | | |
| | | /** |
| | | * Retrieves the operation in progress with the specified message ID. |
| | | * |
| | | * @param messageID |
| | | * The message ID for the operation to retrieve. |
| | | * @return The operation in progress with the specified message ID, or |
| | | * <CODE>null</CODE> if no such operation could be found. |
| | | */ |
| | | @Override |
| | | public Operation getOperationInProgress(int messageID) |
| | | { |
| | | return operationsInProgress.get(messageID); |
| | | } |
| | | |
| | | /** |
| | | * Adds the provided operation to the set of operations in progress |
| | | * for this client connection. |
| | | * |
| | | * @param operation |
| | | * The operation to add to the set of operations in progress |
| | | * for this client connection. |
| | | * @throws DirectoryException |
| | | * If the operation is not added for some reason (e.g., the |
| | | * client already has reached the maximum allowed concurrent |
| | | * requests). |
| | | */ |
| | | private void addOperationInProgress(final QueueingStrategy queueingStrategy, Operation operation) |
| | | throws DirectoryException |
| | | { |
| | | int messageID = operation.getMessageID(); |
| | | |
| | | // We need to grab a lock to ensure that no one else can add |
| | | // operations to the queue while we are performing some preliminary |
| | | // checks. |
| | | try |
| | | { |
| | | synchronized (opsInProgressLock) |
| | | { |
| | | // If we're already in the process of disconnecting the client, |
| | | // then reject the operation. |
| | | if (disconnectRequested) |
| | | { |
| | | LocalizableMessage message = WARN_CLIENT_DISCONNECT_IN_PROGRESS.get(); |
| | | throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, |
| | | message); |
| | | } |
| | | |
| | | // Add the operation to the list of operations in progress for |
| | | // this connection. |
| | | Operation op = operationsInProgress.putIfAbsent(messageID, operation); |
| | | |
| | | // See if there is already an operation in progress with the |
| | | // same message ID. If so, then we can't allow it. |
| | | if (op != null) |
| | | { |
| | | LocalizableMessage message = |
| | | WARN_LDAP_CLIENT_DUPLICATE_MESSAGE_ID.get(messageID); |
| | | throw new DirectoryException(ResultCode.PROTOCOL_ERROR, |
| | | message); |
| | | } |
| | | } |
| | | |
| | | // Try to add the operation to the work queue, |
| | | // or run it synchronously (typically for the administration |
| | | // connector) |
| | | queueingStrategy.enqueueRequest(operation); |
| | | } |
| | | catch (DirectoryException de) |
| | | { |
| | | logger.traceException(de); |
| | | |
| | | operationsInProgress.remove(messageID); |
| | | lastCompletionTime.set(TimeThread.getTime()); |
| | | |
| | | throw de; |
| | | } |
| | | catch (Exception e) |
| | | { |
| | | logger.traceException(e); |
| | | |
| | | LocalizableMessage message = |
| | | WARN_LDAP_CLIENT_CANNOT_ENQUEUE.get(getExceptionMessage(e)); |
| | | throw new DirectoryException(DirectoryServer |
| | | .getServerErrorResultCode(), message, e); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * Removes the provided operation from the set of operations in |
| | | * progress for this client connection. Note that this does not make |
| | | * any attempt to cancel any processing that may already be in |
| | | * progress for the operation. |
| | | * |
| | | * @param messageID |
| | | * The message ID of the operation to remove from the set of |
| | | * operations in progress. |
| | | * @return <CODE>true</CODE> if the operation was found and removed |
| | | * from the set of operations in progress, or |
| | | * <CODE>false</CODE> if not. |
| | | */ |
| | | @Override |
| | | public boolean removeOperationInProgress(int messageID) |
| | | { |
| | | Operation operation = operationsInProgress.remove(messageID); |
| | | if (operation == null) |
| | | { |
| | | return false; |
| | | } |
| | | |
| | | if (operation.getOperationType() == OperationType.ABANDON |
| | | && keepStats |
| | | && operation.getResultCode() == ResultCode.CANCELLED) |
| | | { |
| | | statTracker.updateAbandonedOperation(); |
| | | } |
| | | |
| | | lastCompletionTime.set(TimeThread.getTime()); |
| | | return true; |
| | | } |
| | | |
| | | /** |
| | | * Attempts to cancel the specified operation. |
| | | * |
| | | * @param messageID |
| | | * The message ID of the operation to cancel. |
| | | * @param cancelRequest |
| | | * An object providing additional information about how the |
| | | * cancel should be processed. |
| | | * @return A cancel result that either indicates that the cancel was |
| | | * successful or provides a reason that it was not. |
| | | */ |
| | | @Override |
| | | public CancelResult cancelOperation(int messageID, |
| | | CancelRequest cancelRequest) |
| | | { |
| | | Operation op = operationsInProgress.get(messageID); |
| | | if (op != null) |
| | | { |
| | | return op.cancel(cancelRequest); |
| | | } |
| | | |
| | | // See if the operation is in the list of persistent searches. |
| | | for (PersistentSearch ps : getPersistentSearches()) |
| | | { |
| | | if (ps.getMessageID() == messageID) |
| | | { |
| | | // We only need to find the first persistent search |
| | | // associated with the provided message ID. The persistent search |
| | | // will ensure that all other related persistent searches are cancelled. |
| | | return ps.cancel(); |
| | | } |
| | | } |
| | | return new CancelResult(ResultCode.NO_SUCH_OPERATION, null); |
| | | } |
| | | |
| | | /** |
| | | * Attempts to cancel all operations in progress on this connection. |
| | | * |
| | | * @param cancelRequest |
| | | * An object providing additional information about how the |
| | | * cancel should be processed. |
| | | */ |
| | | @Override |
| | | public void cancelAllOperations(CancelRequest cancelRequest) |
| | | { |
| | | // Make sure that no one can add any new operations. |
| | | synchronized (opsInProgressLock) |
| | | { |
| | | try |
| | | { |
| | | for (Operation o : operationsInProgress.values()) |
| | | { |
| | | try |
| | | { |
| | | o.abort(cancelRequest); |
| | | |
| | | // TODO: Assume its cancelled? |
| | | if (keepStats) |
| | | { |
| | | statTracker.updateAbandonedOperation(); |
| | | } |
| | | } |
| | | catch (Exception e) |
| | | { |
| | | logger.traceException(e); |
| | | } |
| | | } |
| | | |
| | | if (!operationsInProgress.isEmpty() |
| | | || !getPersistentSearches().isEmpty()) |
| | | { |
| | | lastCompletionTime.set(TimeThread.getTime()); |
| | | } |
| | | |
| | | operationsInProgress.clear(); |
| | | |
| | | for (PersistentSearch persistentSearch : getPersistentSearches()) |
| | | { |
| | | persistentSearch.cancel(); |
| | | } |
| | | } |
| | | catch (Exception e) |
| | | { |
| | | logger.traceException(e); |
| | | } |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * Attempts to cancel all operations in progress on this connection |
| | | * except the operation with the specified message ID. |
| | | * |
| | | * @param cancelRequest |
| | | * An object providing additional information about how the |
| | | * cancel should be processed. |
| | | * @param messageID |
| | | * The message ID of the operation that should not be |
| | | * canceled. |
| | | */ |
| | | @Override |
| | | public void cancelAllOperationsExcept(CancelRequest cancelRequest, |
| | | int messageID) |
| | | { |
| | | // Make sure that no one can add any new operations. |
| | | synchronized (opsInProgressLock) |
| | | { |
| | | try |
| | | { |
| | | for (int msgID : operationsInProgress.keySet()) |
| | | { |
| | | if (msgID == messageID) |
| | | { |
| | | continue; |
| | | } |
| | | |
| | | Operation o = operationsInProgress.get(msgID); |
| | | if (o != null) |
| | | { |
| | | try |
| | | { |
| | | o.abort(cancelRequest); |
| | | |
| | | // TODO: Assume its cancelled? |
| | | if (keepStats) |
| | | { |
| | | statTracker.updateAbandonedOperation(); |
| | | } |
| | | } |
| | | catch (Exception e) |
| | | { |
| | | logger.traceException(e); |
| | | } |
| | | } |
| | | |
| | | operationsInProgress.remove(msgID); |
| | | lastCompletionTime.set(TimeThread.getTime()); |
| | | } |
| | | |
| | | for (PersistentSearch persistentSearch : getPersistentSearches()) |
| | | { |
| | | if (persistentSearch.getMessageID() == messageID) |
| | | { |
| | | continue; |
| | | } |
| | | |
| | | persistentSearch.cancel(); |
| | | lastCompletionTime.set(TimeThread.getTime()); |
| | | } |
| | | } |
| | | catch (Exception e) |
| | | { |
| | | logger.traceException(e); |
| | | } |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | public Selector getWriteSelector() |
| | | { |
| | | throw new UnsupportedOperationException(); |
| | | } |
| | | |
| | | @Override |
| | | public long getMaxBlockedWriteTimeLimit() |
| | | { |
| | | return connectionHandler.getMaxBlockedWriteTimeLimit(); |
| | | } |
| | | |
| | | /** |
| | | * Returns the total number of operations initiated on this |
| | | * connection. |
| | | * |
| | | * @return the total number of operations on this connection |
| | | */ |
| | | @Override |
| | | public long getNumberOfOperations() |
| | | { |
| | | return operationsPerformed.get(); |
| | | } |
| | | |
| | | /** |
| | | * Processes the provided LDAP message read from the client and takes |
| | | * whatever action is appropriate. For most requests, this will |
| | | * include placing the operation in the work queue. Certain requests |
| | | * (in particular, abandons and unbinds) will be processed directly. |
| | | * |
| | | * @param queueingStrategy |
| | | * The {@link QueueingStrategy} to use for operation |
| | | * @param message |
| | | * The LDAP message to process. |
| | | * @return <CODE>true</CODE> if the appropriate action was taken for |
| | | * the request, or <CODE>false</CODE> if there was a fatal |
| | | * error and the client has been disconnected as a result, or |
| | | * if the client unbound from the server. |
| | | */ |
| | | @Override |
| | | public Single<Stream<Response>> handle(final QueueingStrategy queueingStrategy, final LdapRawMessage message) { |
| | | return singleFrom(streamFromPublisher(Flowable.create(new FlowableOnSubscribe<Response>() { |
| | | @Override |
| | | public void subscribe(FlowableEmitter<Response> emitter) throws Exception { |
| | | processLDAPMessage(queueingStrategy, LDAPReader.readMessage(message.getContent()), emitter); |
| | | } |
| | | }, BackpressureMode.NONE).onBackpressureBuffer(64, null, BackpressureOverflowStrategy.ERROR))); |
| | | } |
| | | |
| | | private boolean processLDAPMessage(final QueueingStrategy queueingStrategy, final LDAPMessage message, |
| | | final FlowableEmitter<Response> out) |
| | | { |
| | | if (keepStats) |
| | | { |
| | | statTracker.updateMessageRead(message); |
| | | } |
| | | operationsPerformed.getAndIncrement(); |
| | | |
| | | List<Control> opControls = message.getControls(); |
| | | |
| | | // FIXME -- See if there is a bind in progress. If so, then deny |
| | | // most kinds of operations. |
| | | |
| | | // Figure out what type of operation we're dealing with based on the |
| | | // LDAP message. Abandon and unbind requests will be processed here. |
| | | // All other types of requests will be encapsulated into operations |
| | | // and append into the work queue to be picked up by a worker |
| | | // thread. Any other kinds of LDAP messages (e.g., response |
| | | // messages) are illegal and will result in the connection being |
| | | // terminated. |
| | | try |
| | | { |
| | | if (bindInProgress.get()) |
| | | { |
| | | throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, ERR_ENQUEUE_BIND_IN_PROGRESS.get()); |
| | | } |
| | | else if (startTLSInProgress.get()) |
| | | { |
| | | throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, ERR_ENQUEUE_STARTTLS_IN_PROGRESS.get()); |
| | | } |
| | | else if (saslBindInProgress.get() && message.getProtocolOpType() != OP_TYPE_BIND_REQUEST) |
| | | { |
| | | throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, ERR_ENQUEUE_SASLBIND_IN_PROGRESS.get()); |
| | | } |
| | | |
| | | boolean result; |
| | | switch (message.getProtocolOpType()) |
| | | { |
| | | case OP_TYPE_ABANDON_REQUEST: |
| | | return processAbandonRequest(queueingStrategy, message, opControls, out); |
| | | case OP_TYPE_ADD_REQUEST: |
| | | return processAddRequest(queueingStrategy, message, opControls, out); |
| | | case OP_TYPE_BIND_REQUEST: |
| | | boolean isSaslBind = message.getBindRequestProtocolOp().getAuthenticationType() == AuthenticationType.SASL; |
| | | bindInProgress.set(true); |
| | | if (isSaslBind) |
| | | { |
| | | saslBindInProgress.set(true); |
| | | } |
| | | result = processBindRequest(queueingStrategy, message, opControls, out); |
| | | if(!result) |
| | | { |
| | | bindInProgress.set(false); |
| | | if (isSaslBind) |
| | | { |
| | | saslBindInProgress.set(false); |
| | | } |
| | | } |
| | | return result; |
| | | case OP_TYPE_COMPARE_REQUEST: |
| | | return processCompareRequest(queueingStrategy, message, opControls, out); |
| | | case OP_TYPE_DELETE_REQUEST: |
| | | return processDeleteRequest(queueingStrategy, message, opControls, out); |
| | | case OP_TYPE_EXTENDED_REQUEST: |
| | | boolean isStartTlsRequest = OID_START_TLS_REQUEST.equals(message.getExtendedRequestProtocolOp().getOID()); |
| | | if (isStartTlsRequest) |
| | | { |
| | | startTLSInProgress.set(true); |
| | | } |
| | | result = processExtendedRequest(queueingStrategy, message, opControls, out); |
| | | if (!result && isStartTlsRequest) |
| | | { |
| | | startTLSInProgress.set(false); |
| | | } |
| | | return result; |
| | | case OP_TYPE_MODIFY_REQUEST: |
| | | return processModifyRequest(queueingStrategy, message, opControls, out); |
| | | case OP_TYPE_MODIFY_DN_REQUEST: |
| | | return processModifyDNRequest(queueingStrategy, message, opControls, out); |
| | | case OP_TYPE_SEARCH_REQUEST: |
| | | return processSearchRequest(queueingStrategy, message, opControls, out); |
| | | case OP_TYPE_UNBIND_REQUEST: |
| | | return processUnbindRequest(message, opControls); |
| | | default: |
| | | LocalizableMessage msg = |
| | | ERR_LDAP_DISCONNECT_DUE_TO_INVALID_REQUEST_TYPE.get(message |
| | | .getProtocolOpName(), message.getMessageID()); |
| | | disconnect(DisconnectReason.PROTOCOL_ERROR, true, msg); |
| | | return false; |
| | | } |
| | | } |
| | | catch (Exception e) |
| | | { |
| | | logger.traceException(e); |
| | | |
| | | LocalizableMessage msg = |
| | | ERR_LDAP_DISCONNECT_DUE_TO_PROCESSING_FAILURE.get(message |
| | | .getProtocolOpName(), message.getMessageID(), e); |
| | | disconnect(DisconnectReason.SERVER_ERROR, true, msg); |
| | | return false; |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * Processes the provided LDAP message as an abandon request. |
| | | * |
| | | * @param queueingStrategy |
| | | * The {@link QueueingStrategy} to use for operation |
| | | * @param message |
| | | * The LDAP message containing the abandon request to |
| | | * process. |
| | | * @param controls |
| | | * The set of pre-decoded request controls contained in the |
| | | * message. |
| | | * @return <CODE>true</CODE> if the request was processed |
| | | * successfully, or <CODE>false</CODE> if not and the |
| | | * connection has been closed as a result (it is the |
| | | * responsibility of this method to close the connection). |
| | | */ |
| | | private boolean processAbandonRequest(final QueueingStrategy queueingStrategy, final LDAPMessage message, |
| | | final List<Control> controls, final FlowableEmitter<Response> out) |
| | | { |
| | | if (ldapVersion == 2 && !controls.isEmpty()) |
| | | { |
| | | disconnectControlsNotAllowed(); |
| | | return false; |
| | | } |
| | | |
| | | // Create the abandon operation and add it into the work queue. |
| | | AbandonRequestProtocolOp protocolOp = |
| | | message.getAbandonRequestProtocolOp(); |
| | | AbandonOperationBasis abandonOp = |
| | | new AbandonOperationBasis(this, nextOperationID |
| | | .getAndIncrement(), message.getMessageID(), controls, |
| | | protocolOp.getIDToAbandon()); |
| | | abandonOp.setAttachment(REACTIVE_OUT, out); |
| | | |
| | | try |
| | | { |
| | | addOperationInProgress(queueingStrategy, abandonOp); |
| | | } |
| | | catch (DirectoryException de) |
| | | { |
| | | logger.traceException(de); |
| | | |
| | | // Don't send an error response since abandon operations |
| | | // don't have a response. |
| | | } |
| | | |
| | | return connectionValid; |
| | | } |
| | | |
| | | /** |
| | | * Processes the provided LDAP message as an add request. |
| | | * |
| | | * @param queueingStrategy |
| | | * The {@link QueueingStrategy} to use for operation |
| | | * @param message |
| | | * The LDAP message containing the add request to process. |
| | | * @param controls |
| | | * The set of pre-decoded request controls contained in the |
| | | * message. |
| | | * @return <CODE>true</CODE> if the request was processed |
| | | * successfully, or <CODE>false</CODE> if not and the |
| | | * connection has been closed as a result (it is the |
| | | * responsibility of this method to close the connection). |
| | | */ |
| | | private boolean processAddRequest(final QueueingStrategy queueingStrategy, final LDAPMessage message, |
| | | final List<Control> controls, final FlowableEmitter<Response> out) |
| | | { |
| | | if (ldapVersion == 2 && !controls.isEmpty()) |
| | | { |
| | | // LDAPv2 clients aren't allowed to send controls. |
| | | out.onNext(Responses.newResult(ResultCode.PROTOCOL_ERROR) |
| | | .setDiagnosticMessage(ERR_LDAPV2_CLIENTS_NOT_ALLOWED.get().toString())); |
| | | out.onComplete(); |
| | | disconnectControlsNotAllowed(); |
| | | return false; |
| | | } |
| | | |
| | | // Create the add operation and add it into the work queue. |
| | | AddRequestProtocolOp protocolOp = message.getAddRequestProtocolOp(); |
| | | AddOperationBasis addOp = |
| | | new AddOperationBasis(this, nextOperationID.getAndIncrement(), |
| | | message.getMessageID(), controls, protocolOp.getDN(), |
| | | protocolOp.getAttributes()); |
| | | addOp.setAttachment(REACTIVE_OUT, out); |
| | | |
| | | try |
| | | { |
| | | addOperationInProgress(queueingStrategy, addOp); |
| | | } |
| | | catch (DirectoryException de) |
| | | { |
| | | logger.traceException(de); |
| | | |
| | | final Result result = Responses.newResult(de.getResultCode()) |
| | | .setDiagnosticMessage(de.getLocalizedMessage()) |
| | | .setMatchedDN(de.getMatchedDN().toString()); |
| | | for(String referral : de.getReferralURLs()) { |
| | | result.addReferralURI(referral); |
| | | } |
| | | |
| | | out.onNext(result); |
| | | out.onComplete(); |
| | | } |
| | | |
| | | return connectionValid; |
| | | } |
| | | |
| | | private void disconnectControlsNotAllowed() |
| | | { |
| | | disconnect(DisconnectReason.PROTOCOL_ERROR, false, ERR_LDAPV2_CONTROLS_NOT_ALLOWED.get()); |
| | | } |
| | | |
| | | /** |
| | | * Processes the provided LDAP message as a bind request. |
| | | * |
| | | * @param queueingStrategy |
| | | * The {@link QueueingStrategy} to use for operation |
| | | * @param message |
| | | * The LDAP message containing the bind request to process. |
| | | * @param controls |
| | | * The set of pre-decoded request controls contained in the |
| | | * message. |
| | | * @return <CODE>true</CODE> if the request was processed |
| | | * successfully, or <CODE>false</CODE> if not and the |
| | | * connection has been closed as a result (it is the |
| | | * responsibility of this method to close the connection). |
| | | */ |
| | | private boolean processBindRequest(final QueueingStrategy queueingStrategy, final LDAPMessage message, |
| | | final List<Control> controls, final FlowableEmitter<Response> out) |
| | | { |
| | | BindRequestProtocolOp protocolOp = |
| | | message.getBindRequestProtocolOp(); |
| | | |
| | | // See if this is an LDAPv2 bind request, and if so whether that |
| | | // should be allowed. |
| | | String versionString; |
| | | switch (ldapVersion = protocolOp.getProtocolVersion()) |
| | | { |
| | | case 2: |
| | | versionString = "2"; |
| | | |
| | | if (!connectionHandler.allowLDAPv2()) |
| | | { |
| | | out.onNext(Responses.newBindResult(ResultCode.PROTOCOL_ERROR) |
| | | .setDiagnosticMessage(ERR_LDAPV2_CLIENTS_NOT_ALLOWED.get().toString())); |
| | | out.onComplete(); |
| | | disconnect(DisconnectReason.PROTOCOL_ERROR, false, ERR_LDAPV2_CLIENTS_NOT_ALLOWED.get()); |
| | | return false; |
| | | } |
| | | |
| | | if (!controls.isEmpty()) |
| | | { |
| | | // LDAPv2 clients aren't allowed to send controls. |
| | | out.onNext(Responses.newBindResult(ResultCode.PROTOCOL_ERROR) |
| | | .setDiagnosticMessage(ERR_LDAPV2_CONTROLS_NOT_ALLOWED.get().toString())); |
| | | out.onComplete(); |
| | | disconnectControlsNotAllowed(); |
| | | return false; |
| | | } |
| | | |
| | | break; |
| | | case 3: |
| | | versionString = "3"; |
| | | break; |
| | | default: |
| | | // Unsupported protocol version. RFC4511 states that we MUST send |
| | | // a protocol error back to the client. |
| | | out.onNext(Responses.newBindResult(ResultCode.PROTOCOL_ERROR) |
| | | .setDiagnosticMessage(ERR_LDAP_UNSUPPORTED_PROTOCOL_VERSION.get(ldapVersion).toString())); |
| | | out.onComplete(); |
| | | disconnect(DisconnectReason.PROTOCOL_ERROR, false, |
| | | ERR_LDAP_UNSUPPORTED_PROTOCOL_VERSION.get(ldapVersion)); |
| | | return false; |
| | | } |
| | | |
| | | ByteString bindDN = protocolOp.getDN(); |
| | | |
| | | BindOperationBasis bindOp; |
| | | switch (protocolOp.getAuthenticationType()) |
| | | { |
| | | case SIMPLE: |
| | | bindOp = |
| | | new BindOperationBasis(this, nextOperationID |
| | | .getAndIncrement(), message.getMessageID(), controls, |
| | | versionString, bindDN, protocolOp.getSimplePassword()); |
| | | break; |
| | | case SASL: |
| | | bindOp = |
| | | new BindOperationBasis(this, nextOperationID |
| | | .getAndIncrement(), message.getMessageID(), controls, |
| | | versionString, bindDN, protocolOp.getSASLMechanism(), |
| | | protocolOp.getSASLCredentials()); |
| | | break; |
| | | default: |
| | | // This is an invalid authentication type, and therefore a |
| | | // protocol error. As per RFC 2251, a protocol error in a bind |
| | | // request must result in terminating the connection. |
| | | LocalizableMessage msg = |
| | | ERR_LDAP_INVALID_BIND_AUTH_TYPE.get(message.getMessageID(), |
| | | protocolOp.getAuthenticationType()); |
| | | disconnect(DisconnectReason.PROTOCOL_ERROR, true, msg); |
| | | return false; |
| | | } |
| | | |
| | | // Add the operation into the work queue. |
| | | bindOp.setAttachment(REACTIVE_OUT, out); |
| | | try |
| | | { |
| | | addOperationInProgress(queueingStrategy, bindOp); |
| | | } |
| | | catch (DirectoryException de) |
| | | { |
| | | logger.traceException(de); |
| | | |
| | | final Result result = Responses.newBindResult(de.getResultCode()) |
| | | .setDiagnosticMessage(de.getLocalizedMessage()) |
| | | .setMatchedDN(de.getMatchedDN().toString()); |
| | | for(String referral : de.getReferralURLs()) |
| | | { |
| | | result.addReferralURI(referral); |
| | | } |
| | | |
| | | out.onNext(result); |
| | | out.onComplete(); |
| | | |
| | | // If it was a protocol error, then terminate the connection. |
| | | if (de.getResultCode() == ResultCode.PROTOCOL_ERROR) |
| | | { |
| | | LocalizableMessage msg = |
| | | ERR_LDAP_DISCONNECT_DUE_TO_BIND_PROTOCOL_ERROR.get(message |
| | | .getMessageID(), de.getMessageObject()); |
| | | disconnect(DisconnectReason.PROTOCOL_ERROR, true, msg); |
| | | } |
| | | } |
| | | |
| | | return connectionValid; |
| | | } |
| | | |
| | | /** |
| | | * Processes the provided LDAP message as a compare request. |
| | | * |
| | | * @param queueingStrategy |
| | | * The {@link QueueingStrategy} to use for operation |
| | | * @param message |
| | | * The LDAP message containing the compare request to |
| | | * process. |
| | | * @param controls |
| | | * The set of pre-decoded request controls contained in the |
| | | * message. |
| | | * @return <CODE>true</CODE> if the request was processed |
| | | * successfully, or <CODE>false</CODE> if not and the |
| | | * connection has been closed as a result (it is the |
| | | * responsibility of this method to close the connection). |
| | | */ |
| | | private boolean processCompareRequest(final QueueingStrategy queueingStrategy, final LDAPMessage message, |
| | | final List<Control> controls, final FlowableEmitter<Response> out) |
| | | { |
| | | if (ldapVersion == 2 && !controls.isEmpty()) |
| | | { |
| | | // LDAPv2 clients aren't allowed to send controls. |
| | | out.onNext(Responses.newResult(ResultCode.PROTOCOL_ERROR) |
| | | .setDiagnosticMessage(ERR_LDAPV2_CONTROLS_NOT_ALLOWED.get().toString())); |
| | | out.onComplete(); |
| | | disconnectControlsNotAllowed(); |
| | | return false; |
| | | } |
| | | |
| | | CompareRequestProtocolOp protocolOp = |
| | | message.getCompareRequestProtocolOp(); |
| | | CompareOperationBasis compareOp = |
| | | new CompareOperationBasis(this, nextOperationID |
| | | .getAndIncrement(), message.getMessageID(), controls, |
| | | protocolOp.getDN(), protocolOp.getAttributeType(), |
| | | protocolOp.getAssertionValue()); |
| | | |
| | | // Add the operation into the work queue. |
| | | compareOp.setAttachment(REACTIVE_OUT, out); |
| | | try |
| | | { |
| | | addOperationInProgress(queueingStrategy, compareOp); |
| | | } |
| | | catch (DirectoryException de) |
| | | { |
| | | logger.traceException(de); |
| | | |
| | | final CompareResult result = Responses.newCompareResult(de.getResultCode()) |
| | | .setDiagnosticMessage(de.getLocalizedMessage()) |
| | | .setMatchedDN(de.getMatchedDN().toString()); |
| | | result.getReferralURIs().addAll(de.getReferralURLs()); |
| | | out.onNext(result); |
| | | out.onComplete(); |
| | | } |
| | | |
| | | return connectionValid; |
| | | } |
| | | |
| | | /** |
| | | * Processes the provided LDAP message as a delete request. |
| | | * @param queueingStrategy |
| | | * The {@link QueueingStrategy} to use for operation |
| | | * @param message |
| | | * The LDAP message containing the delete request to process. |
| | | * @param controls |
| | | * The set of pre-decoded request controls contained in the |
| | | * message. |
| | | * @return <CODE>true</CODE> if the request was processed |
| | | * successfully, or <CODE>false</CODE> if not and the |
| | | * connection has been closed as a result (it is the |
| | | * responsibility of this method to close the connection). |
| | | */ |
| | | private boolean processDeleteRequest(final QueueingStrategy queueingStrategy, final LDAPMessage message, |
| | | final List<Control> controls, final FlowableEmitter<Response> out) |
| | | { |
| | | if (ldapVersion == 2 && !controls.isEmpty()) |
| | | { |
| | | // LDAPv2 clients aren't allowed to send controls. |
| | | out.onNext(Responses.newResult(ResultCode.PROTOCOL_ERROR) |
| | | .setDiagnosticMessage( ERR_LDAPV2_CONTROLS_NOT_ALLOWED.get().toString())); |
| | | out.onComplete(); |
| | | disconnectControlsNotAllowed(); |
| | | return false; |
| | | } |
| | | |
| | | DeleteRequestProtocolOp protocolOp = |
| | | message.getDeleteRequestProtocolOp(); |
| | | DeleteOperationBasis deleteOp = |
| | | new DeleteOperationBasis(this, nextOperationID |
| | | .getAndIncrement(), message.getMessageID(), controls, |
| | | protocolOp.getDN()); |
| | | |
| | | // Add the operation into the work queue. |
| | | deleteOp.setAttachment(REACTIVE_OUT, out); |
| | | try |
| | | { |
| | | addOperationInProgress(queueingStrategy, deleteOp); |
| | | } |
| | | catch (DirectoryException de) |
| | | { |
| | | logger.traceException(de); |
| | | |
| | | final Result result = Responses.newResult(de.getResultCode()) |
| | | .setDiagnosticMessage(de.getLocalizedMessage()) |
| | | .setMatchedDN(de.getMatchedDN().toString()); |
| | | result.getReferralURIs().addAll(de.getReferralURLs()); |
| | | |
| | | out.onNext(result); |
| | | out.onComplete(); |
| | | } |
| | | |
| | | return connectionValid; |
| | | } |
| | | |
| | | /** |
| | | * Processes the provided LDAP message as an extended request. |
| | | * @param queueingStrategy |
| | | * The {@link QueueingStrategy} to use for operation |
| | | * @param message |
| | | * The LDAP message containing the extended request to |
| | | * process. |
| | | * @param controls |
| | | * The set of pre-decoded request controls contained in the |
| | | * message. |
| | | * @return <CODE>true</CODE> if the request was processed |
| | | * successfully, or <CODE>false</CODE> if not and the |
| | | * connection has been closed as a result (it is the |
| | | * responsibility of this method to close the connection). |
| | | */ |
| | | private boolean processExtendedRequest(final QueueingStrategy queueingStrategy, final LDAPMessage message, |
| | | final List<Control> controls, final FlowableEmitter<Response> out) |
| | | { |
| | | // See if this is an LDAPv2 client. If it is, then they should not |
| | | // be issuing extended requests. We can't send a response that we |
| | | // can be sure they can understand, so we have no choice but to |
| | | // close the connection. |
| | | if (ldapVersion == 2) |
| | | { |
| | | // LDAPv2 clients aren't allowed to send controls. |
| | | LocalizableMessage msg = ERR_LDAPV2_EXTENDED_REQUEST_NOT_ALLOWED.get(getConnectionID(), message.getMessageID()); |
| | | |
| | | out.onNext(Responses.newResult(ResultCode.PROTOCOL_ERROR) |
| | | .setDiagnosticMessage(msg.toString())); |
| | | out.onComplete(); |
| | | |
| | | logger.error(msg); |
| | | disconnect(DisconnectReason.PROTOCOL_ERROR, false, msg); |
| | | return false; |
| | | } |
| | | |
| | | // FIXME -- Do we need to handle certain types of request here? |
| | | // -- StartTLS requests |
| | | // -- Cancel requests |
| | | |
| | | ExtendedRequestProtocolOp protocolOp = |
| | | message.getExtendedRequestProtocolOp(); |
| | | ExtendedOperationBasis extendedOp = |
| | | new ExtendedOperationBasis(this, nextOperationID |
| | | .getAndIncrement(), message.getMessageID(), controls, |
| | | protocolOp.getOID(), protocolOp.getValue()); |
| | | |
| | | // Add the operation into the work queue. |
| | | extendedOp.setAttachment(REACTIVE_OUT, out); |
| | | try |
| | | { |
| | | addOperationInProgress(queueingStrategy, extendedOp); |
| | | } |
| | | catch (DirectoryException de) |
| | | { |
| | | logger.traceException(de); |
| | | final Result result = Responses.newResult(de.getResultCode()) |
| | | .setDiagnosticMessage(de.getMessage()) |
| | | .setMatchedDN(de.getMatchedDN().toString()); |
| | | result.getReferralURIs().addAll(de.getReferralURLs()); |
| | | |
| | | out.onNext(result); |
| | | out.onComplete(); |
| | | } |
| | | |
| | | return connectionValid; |
| | | } |
| | | |
| | | /** |
| | | * Processes the provided LDAP message as a modify request. |
| | | * |
| | | * @param queueingStrategy |
| | | * The {@link QueueingStrategy} to use for operation |
| | | * @param message |
| | | * The LDAP message containing the modify request to process. |
| | | * @param controls |
| | | * The set of pre-decoded request controls contained in the |
| | | * message. |
| | | * @return <CODE>true</CODE> if the request was processed |
| | | * successfully, or <CODE>false</CODE> if not and the |
| | | * connection has been closed as a result (it is the |
| | | * responsibility of this method to close the connection). |
| | | */ |
| | | private boolean processModifyRequest(final QueueingStrategy queueingStrategy, final LDAPMessage message, |
| | | final List<Control> controls, final FlowableEmitter<Response> out) |
| | | { |
| | | if (ldapVersion == 2 && !controls.isEmpty()) |
| | | { |
| | | // LDAPv2 clients aren't allowed to send controls. |
| | | out.onNext(Responses.newResult(ResultCode.PROTOCOL_ERROR) |
| | | .setDiagnosticMessage(ERR_LDAPV2_CONTROLS_NOT_ALLOWED.get().toString())); |
| | | out.onComplete(); |
| | | disconnectControlsNotAllowed(); |
| | | return false; |
| | | } |
| | | |
| | | ModifyRequestProtocolOp protocolOp = |
| | | message.getModifyRequestProtocolOp(); |
| | | ModifyOperationBasis modifyOp = |
| | | new ModifyOperationBasis(this, nextOperationID |
| | | .getAndIncrement(), message.getMessageID(), controls, |
| | | protocolOp.getDN(), protocolOp.getModifications()); |
| | | |
| | | // Add the operation into the work queue. |
| | | modifyOp.setAttachment(REACTIVE_OUT, out); |
| | | try |
| | | { |
| | | addOperationInProgress(queueingStrategy, modifyOp); |
| | | } |
| | | catch (DirectoryException de) |
| | | { |
| | | logger.traceException(de); |
| | | final Result result = |
| | | Responses.newResult(de.getResultCode()).setDiagnosticMessage(de.getMessage()).setMatchedDN(de.getMatchedDN() |
| | | .toString()); |
| | | result.getReferralURIs().addAll(de.getReferralURLs()); |
| | | |
| | | out.onNext(result); |
| | | out.onComplete(); |
| | | } |
| | | |
| | | return connectionValid; |
| | | } |
| | | |
| | | /** |
| | | * Processes the provided LDAP message as a modify DN request. |
| | | * |
| | | * @param queueingStrategy |
| | | * The {@link QueueingStrategy} to use for operation |
| | | * @param message |
| | | * The LDAP message containing the modify DN request to |
| | | * process. |
| | | * @param controls |
| | | * The set of pre-decoded request controls contained in the |
| | | * message. |
| | | * @return <CODE>true</CODE> if the request was processed |
| | | * successfully, or <CODE>false</CODE> if not and the |
| | | * connection has been closed as a result (it is the |
| | | * responsibility of this method to close the connection). |
| | | */ |
| | | private boolean processModifyDNRequest(final QueueingStrategy queueingStrategy, final LDAPMessage message, |
| | | final List<Control> controls, final FlowableEmitter<Response> out) |
| | | { |
| | | if (ldapVersion == 2 && !controls.isEmpty()) |
| | | { |
| | | // LDAPv2 clients aren't allowed to send controls. |
| | | out.onNext(Responses.newResult(ResultCode.PROTOCOL_ERROR) |
| | | .setDiagnosticMessage(ERR_LDAPV2_CONTROLS_NOT_ALLOWED.get().toString())); |
| | | out.onComplete(); |
| | | disconnectControlsNotAllowed(); |
| | | return false; |
| | | } |
| | | |
| | | ModifyDNRequestProtocolOp protocolOp = |
| | | message.getModifyDNRequestProtocolOp(); |
| | | ModifyDNOperationBasis modifyDNOp = |
| | | new ModifyDNOperationBasis(this, nextOperationID |
| | | .getAndIncrement(), message.getMessageID(), controls, |
| | | protocolOp.getEntryDN(), protocolOp.getNewRDN(), protocolOp |
| | | .deleteOldRDN(), protocolOp.getNewSuperior()); |
| | | |
| | | // Add the operation into the work queue. |
| | | modifyDNOp.setAttachment(REACTIVE_OUT, out); |
| | | try |
| | | { |
| | | addOperationInProgress(queueingStrategy, modifyDNOp); |
| | | } |
| | | catch (DirectoryException de) |
| | | { |
| | | logger.traceException(de); |
| | | |
| | | final Result result = |
| | | Responses.newResult(de.getResultCode()).setDiagnosticMessage(de.getMessage()).setMatchedDN(de.getMatchedDN() |
| | | .toString()); |
| | | result.getReferralURIs().addAll(de.getReferralURLs()); |
| | | for(Control control : modifyDNOp.getResponseControls()) { |
| | | result.addControl(Converters.from(control)); |
| | | } |
| | | out.onNext(result); |
| | | out.onComplete(); |
| | | } |
| | | |
| | | return connectionValid; |
| | | } |
| | | |
| | | /** |
| | | * Processes the provided LDAP message as a search request. |
| | | * |
| | | * @param queueingStrategy |
| | | * The {@link QueueingStrategy} to use for operation |
| | | * @param message |
| | | * The LDAP message containing the search request to process. |
| | | * @param controls |
| | | * The set of pre-decoded request controls contained in the |
| | | * message. |
| | | * @return <CODE>true</CODE> if the request was processed |
| | | * successfully, or <CODE>false</CODE> if not and the |
| | | * connection has been closed as a result (it is the |
| | | * responsibility of this method to close the connection). |
| | | */ |
| | | private boolean processSearchRequest(final QueueingStrategy queueingStrategy, final LDAPMessage message, |
| | | final List<Control> controls, final FlowableEmitter<Response> out) |
| | | { |
| | | if (ldapVersion == 2 && !controls.isEmpty()) |
| | | { |
| | | // LDAPv2 clients aren't allowed to send controls. |
| | | out.onNext(Responses.newResult(ResultCode.PROTOCOL_ERROR) |
| | | .setDiagnosticMessage(ERR_LDAPV2_CONTROLS_NOT_ALLOWED.get().toString())); |
| | | out.onComplete(); |
| | | disconnectControlsNotAllowed(); |
| | | return false; |
| | | } |
| | | |
| | | SearchRequestProtocolOp protocolOp = |
| | | message.getSearchRequestProtocolOp(); |
| | | SearchOperationBasis searchOp = |
| | | new SearchOperationBasis(this, nextOperationID |
| | | .getAndIncrement(), message.getMessageID(), controls, |
| | | protocolOp.getBaseDN(), protocolOp.getScope(), protocolOp |
| | | .getDereferencePolicy(), protocolOp.getSizeLimit(), |
| | | protocolOp.getTimeLimit(), protocolOp.getTypesOnly(), |
| | | protocolOp.getFilter(), protocolOp.getAttributes()); |
| | | |
| | | // Add the operation into the work queue. |
| | | searchOp.setAttachment(REACTIVE_OUT, out); |
| | | try |
| | | { |
| | | addOperationInProgress(queueingStrategy, searchOp); |
| | | } |
| | | catch (DirectoryException de) |
| | | { |
| | | logger.traceException(de); |
| | | |
| | | final Result result = Responses.newResult(de.getResultCode()); |
| | | if (de.getMessage() != null) |
| | | { |
| | | result.setDiagnosticMessage(de.getMessage()); |
| | | } |
| | | if (de.getMatchedDN() != null) |
| | | { |
| | | result.setMatchedDN(de.getMatchedDN().toString()); |
| | | } |
| | | if (de.getReferralURLs() != null) |
| | | { |
| | | result.getReferralURIs().addAll(de.getReferralURLs()); |
| | | } |
| | | if (searchOp.getResponseControls() != null) |
| | | { |
| | | for (Control control : searchOp.getResponseControls()) |
| | | { |
| | | result.addControl(Converters.from(control)); |
| | | } |
| | | } |
| | | out.onNext(result); |
| | | out.onComplete(); |
| | | } |
| | | |
| | | return connectionValid; |
| | | } |
| | | |
| | | /** |
| | | * Processes the provided LDAP message as an unbind request. |
| | | * |
| | | * @param message |
| | | * The LDAP message containing the unbind request to process. |
| | | * @param controls |
| | | * The set of pre-decoded request controls contained in the |
| | | * message. |
| | | * @return <CODE>true</CODE> if the request was processed |
| | | * successfully, or <CODE>false</CODE> if not and the |
| | | * connection has been closed as a result (it is the |
| | | * responsibility of this method to close the connection). |
| | | */ |
| | | private boolean processUnbindRequest(final LDAPMessage message, final List<Control> controls) |
| | | { |
| | | UnbindOperationBasis unbindOp = |
| | | new UnbindOperationBasis(this, nextOperationID |
| | | .getAndIncrement(), message.getMessageID(), controls); |
| | | |
| | | unbindOp.run(); |
| | | |
| | | // The client connection will never be valid after an unbind. |
| | | return false; |
| | | } |
| | | |
| | | @Override |
| | | public String getMonitorSummary() |
| | | { |
| | | StringBuilder buffer = new StringBuilder(); |
| | | buffer.append("connID=\""); |
| | | buffer.append(connectionID); |
| | | buffer.append("\" connectTime=\""); |
| | | buffer.append(getConnectTimeString()); |
| | | buffer.append("\" source=\""); |
| | | buffer.append(clientAddress); |
| | | buffer.append(":"); |
| | | buffer.append(clientPort); |
| | | buffer.append("\" destination=\""); |
| | | buffer.append(serverAddress); |
| | | buffer.append(":"); |
| | | buffer.append(connectionHandler.getListeners().iterator().next().getPort()); |
| | | buffer.append("\" ldapVersion=\""); |
| | | buffer.append(ldapVersion); |
| | | buffer.append("\" authDN=\""); |
| | | |
| | | DN authDN = getAuthenticationInfo().getAuthenticationDN(); |
| | | if (authDN != null) |
| | | { |
| | | buffer.append(authDN); |
| | | } |
| | | |
| | | buffer.append("\" security=\""); |
| | | buffer.append("none"); |
| | | |
| | | buffer.append("\" opsInProgress=\""); |
| | | buffer.append(operationsInProgress.size()); |
| | | buffer.append("\""); |
| | | |
| | | int countPSearch = getPersistentSearches().size(); |
| | | if (countPSearch > 0) |
| | | { |
| | | buffer.append(" persistentSearches=\""); |
| | | buffer.append(countPSearch); |
| | | buffer.append("\""); |
| | | } |
| | | return buffer.toString(); |
| | | } |
| | | |
| | | /** |
| | | * Appends a string representation of this client connection to the |
| | | * provided buffer. |
| | | * |
| | | * @param buffer |
| | | * The buffer to which the information should be appended. |
| | | */ |
| | | @Override |
| | | public void toString(StringBuilder buffer) |
| | | { |
| | | buffer.append("LDAP client connection from "); |
| | | buffer.append(clientAddress); |
| | | buffer.append(":"); |
| | | buffer.append(clientPort); |
| | | buffer.append(" to "); |
| | | buffer.append(serverAddress); |
| | | buffer.append(":"); |
| | | buffer.append(serverPort); |
| | | } |
| | | |
| | | @Override |
| | | public boolean prepareTLS(LocalizableMessageBuilder unavailableReason) |
| | | { |
| | | throw new UnsupportedOperationException(); |
| | | } |
| | | |
| | | /** |
| | | * Retrieves the length of time in milliseconds that this client |
| | | * connection has been idle. <BR> |
| | | * <BR> |
| | | * Note that the default implementation will always return zero. |
| | | * Subclasses associated with connection handlers should override this |
| | | * method if they wish to provided idle time limit functionality. |
| | | * |
| | | * @return The length of time in milliseconds that this client |
| | | * connection has been idle. |
| | | */ |
| | | @Override |
| | | public long getIdleTime() |
| | | { |
| | | if (operationsInProgress.isEmpty() |
| | | && getPersistentSearches().isEmpty()) |
| | | { |
| | | return TimeThread.getTime() - lastCompletionTime.get(); |
| | | } |
| | | else |
| | | { |
| | | // There's at least one operation in progress, so it's not idle. |
| | | return 0L; |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * Return the certificate chain array associated with a connection. |
| | | * |
| | | * @return The array of certificates associated with a connection. |
| | | */ |
| | | public Certificate[] getClientCertificateChain() |
| | | { |
| | | return new Certificate[0]; |
| | | } |
| | | |
| | | @Override |
| | | public int getSSF() |
| | | { |
| | | return 0; |
| | | } |
| | | } |
| New file |
| | |
| | | /* |
| | | * 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 2006-2010 Sun Microsystems, Inc. |
| | | * Portions Copyright 2011-2016 ForgeRock AS. |
| | | */ |
| | | package org.forgerock.opendj.reactive; |
| | | |
| | | import static org.opends.messages.ProtocolMessages.*; |
| | | import static org.opends.server.util.ServerConstants.*; |
| | | import static org.opends.server.util.StaticUtils.*; |
| | | |
| | | import java.io.IOException; |
| | | import java.net.InetAddress; |
| | | import java.net.InetSocketAddress; |
| | | import java.util.ArrayList; |
| | | import java.util.Arrays; |
| | | import java.util.Collection; |
| | | import java.util.Collections; |
| | | import java.util.HashSet; |
| | | import java.util.Iterator; |
| | | import java.util.LinkedHashMap; |
| | | import java.util.LinkedList; |
| | | import java.util.List; |
| | | import java.util.Map; |
| | | import java.util.Set; |
| | | import java.util.SortedSet; |
| | | import java.util.TreeSet; |
| | | import java.util.concurrent.Executors; |
| | | import java.util.concurrent.ScheduledExecutorService; |
| | | import java.util.concurrent.TimeUnit; |
| | | |
| | | import javax.net.ssl.KeyManager; |
| | | import javax.net.ssl.SSLContext; |
| | | import javax.net.ssl.SSLEngine; |
| | | |
| | | import org.forgerock.i18n.LocalizableMessage; |
| | | import org.forgerock.i18n.slf4j.LocalizedLogger; |
| | | import org.forgerock.opendj.config.server.ConfigChangeResult; |
| | | import org.forgerock.opendj.config.server.ConfigException; |
| | | import org.forgerock.opendj.config.server.ConfigurationChangeListener; |
| | | import org.forgerock.opendj.grizzly.GrizzlyLDAPListener; |
| | | import org.forgerock.opendj.ldap.AddressMask; |
| | | import org.forgerock.opendj.ldap.DN; |
| | | import org.forgerock.opendj.ldap.LDAPClientContext; |
| | | import org.forgerock.opendj.ldap.LDAPListener; |
| | | import org.forgerock.opendj.ldap.LdapException; |
| | | import org.forgerock.opendj.ldap.ResultCode; |
| | | import org.forgerock.opendj.ldap.responses.Response; |
| | | import org.forgerock.opendj.ldap.spi.LdapMessages.LdapRawMessage; |
| | | import org.forgerock.opendj.server.config.server.ConnectionHandlerCfg; |
| | | import org.forgerock.opendj.server.config.server.LDAPConnectionHandlerCfg; |
| | | import org.forgerock.util.Function; |
| | | import org.forgerock.util.Options; |
| | | import org.opends.server.api.AlertGenerator; |
| | | import org.opends.server.api.ClientConnection; |
| | | import org.opends.server.api.ConnectionHandler; |
| | | import org.opends.server.api.DirectoryThread; |
| | | import org.opends.server.api.KeyManagerProvider; |
| | | import org.opends.server.api.ServerShutdownListener; |
| | | import org.opends.server.api.TrustManagerProvider; |
| | | import org.opends.server.api.plugin.PluginResult; |
| | | import org.opends.server.core.DirectoryServer; |
| | | import org.opends.server.core.PluginConfigManager; |
| | | import org.opends.server.core.QueueingStrategy; |
| | | import org.opends.server.core.ServerContext; |
| | | import org.opends.server.core.WorkQueueStrategy; |
| | | import org.opends.server.extensions.NullKeyManagerProvider; |
| | | import org.opends.server.extensions.NullTrustManagerProvider; |
| | | import org.opends.server.monitors.ClientConnectionMonitorProvider; |
| | | import org.opends.server.protocols.ldap.LDAPStatistics; |
| | | import org.opends.server.types.DirectoryException; |
| | | import org.opends.server.types.DisconnectReason; |
| | | import org.opends.server.types.HostPort; |
| | | import org.opends.server.types.InitializationException; |
| | | import org.opends.server.types.SSLClientAuthPolicy; |
| | | import org.opends.server.util.SelectableCertificateKeyManager; |
| | | import org.opends.server.util.StaticUtils; |
| | | |
| | | import com.forgerock.reactive.ReactiveHandler; |
| | | import com.forgerock.reactive.Single; |
| | | import com.forgerock.reactive.Stream; |
| | | |
| | | /** |
| | | * This class defines a connection handler that will be used for communicating |
| | | * with clients over LDAP. It is actually implemented in two parts: as a |
| | | * connection handler and one or more request handlers. The connection handler |
| | | * is responsible for accepting new connections and registering each of them |
| | | * with a request handler. The request handlers then are responsible for reading |
| | | * requests from the clients and parsing them as operations. A single request |
| | | * handler may be used, but having multiple handlers might provide better |
| | | * performance in a multi-CPU system. |
| | | */ |
| | | public final class LDAPConnectionHandler2 extends |
| | | ConnectionHandler<LDAPConnectionHandlerCfg> implements |
| | | ConfigurationChangeListener<LDAPConnectionHandlerCfg>, |
| | | ServerShutdownListener, AlertGenerator |
| | | { |
| | | /** Task run periodically by the connection finalizer. */ |
| | | private final class ConnectionFinalizerRunnable implements Runnable |
| | | { |
| | | @Override |
| | | public void run() |
| | | { |
| | | if (!connectionFinalizerActiveJobQueue.isEmpty()) |
| | | { |
| | | for (Runnable r : connectionFinalizerActiveJobQueue) |
| | | { |
| | | r.run(); |
| | | } |
| | | connectionFinalizerActiveJobQueue.clear(); |
| | | } |
| | | |
| | | // Switch the lists. |
| | | synchronized (connectionFinalizerLock) |
| | | { |
| | | List<Runnable> tmp = connectionFinalizerActiveJobQueue; |
| | | connectionFinalizerActiveJobQueue = connectionFinalizerPendingJobQueue; |
| | | connectionFinalizerPendingJobQueue = tmp; |
| | | } |
| | | } |
| | | } |
| | | private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass(); |
| | | |
| | | /** Default friendly name for the LDAP connection handler. */ |
| | | private static final String DEFAULT_FRIENDLY_NAME = "LDAP Connection Handler"; |
| | | |
| | | /** SSL instance name used in context creation. */ |
| | | private static final String SSL_CONTEXT_INSTANCE_NAME = "TLS"; |
| | | |
| | | private GrizzlyLDAPListener listener; |
| | | |
| | | /** The current configuration state. */ |
| | | private LDAPConnectionHandlerCfg currentConfig; |
| | | |
| | | /* Properties that cannot be modified dynamically */ |
| | | |
| | | /** The set of addresses on which to listen for new connections. */ |
| | | private Set<InetSocketAddress> listenAddresses; |
| | | |
| | | /** The SSL client auth policy used by this connection handler. */ |
| | | private SSLClientAuthPolicy sslClientAuthPolicy; |
| | | |
| | | /** The backlog that will be used for the accept queue. */ |
| | | private int backlog; |
| | | |
| | | /** Indicates whether to allow the reuse address socket option. */ |
| | | private boolean allowReuseAddress; |
| | | |
| | | /** Indicates whether the Directory Server is in the process of shutting down. */ |
| | | private volatile boolean shutdownRequested; |
| | | |
| | | /* Internal LDAP connection handler state */ |
| | | |
| | | /** Indicates whether this connection handler is enabled. */ |
| | | private boolean enabled; |
| | | |
| | | /** The set of clients that are explicitly allowed access to the server. */ |
| | | private Collection<AddressMask> allowedClients; |
| | | |
| | | /** The set of clients that have been explicitly denied access to the server. */ |
| | | private Collection<AddressMask> deniedClients; |
| | | |
| | | /** The set of listeners for this connection handler. */ |
| | | private List<HostPort> listeners; |
| | | |
| | | /** The set of statistics collected for this connection handler. */ |
| | | private LDAPStatistics statTracker; |
| | | |
| | | /** The client connection monitor provider associated with this connection handler. */ |
| | | private ClientConnectionMonitorProvider connMonitor; |
| | | |
| | | /** The unique name assigned to this connection handler. */ |
| | | private String handlerName; |
| | | |
| | | /** The protocol used by this connection handler. */ |
| | | private String protocol; |
| | | |
| | | /** Queueing strategy. */ |
| | | private final QueueingStrategy queueingStrategy; |
| | | |
| | | /** |
| | | * The condition variable that will be used by the start method to wait for |
| | | * the socket port to be opened and ready to process requests before |
| | | * returning. |
| | | */ |
| | | private final Object waitListen = new Object(); |
| | | |
| | | /** The friendly name of this connection handler. */ |
| | | private String friendlyName; |
| | | |
| | | /** |
| | | * SSL context. |
| | | * |
| | | * @see LDAPConnectionHandler2#sslEngine |
| | | */ |
| | | private SSLContext sslContext; |
| | | |
| | | /** The SSL engine is used for obtaining default SSL parameters. */ |
| | | private SSLEngine sslEngine; |
| | | |
| | | /** |
| | | * Connection finalizer thread. |
| | | * <p> |
| | | * This thread is defers closing clients for approximately 100ms. This gives |
| | | * the client a chance to close the connection themselves before the server |
| | | * thus avoiding leaving the server side in the TIME WAIT state. |
| | | */ |
| | | private final Object connectionFinalizerLock = new Object(); |
| | | private ScheduledExecutorService connectionFinalizer; |
| | | private List<Runnable> connectionFinalizerActiveJobQueue; |
| | | private List<Runnable> connectionFinalizerPendingJobQueue; |
| | | |
| | | private final List<ClientConnection> connectionList = Collections.synchronizedList(new ArrayList<ClientConnection>()); |
| | | |
| | | /** |
| | | * Creates a new instance of this LDAP connection handler. It must be |
| | | * initialized before it may be used. |
| | | */ |
| | | public LDAPConnectionHandler2() |
| | | { |
| | | this(new WorkQueueStrategy(), null); // Use name from configuration. |
| | | } |
| | | |
| | | /** |
| | | * Creates a new instance of this LDAP connection handler, using a queueing |
| | | * strategy. It must be initialized before it may be used. |
| | | * |
| | | * @param strategy |
| | | * Request handling strategy. |
| | | * @param friendlyName |
| | | * The name of of this connection handler, or {@code null} if the |
| | | * name should be taken from the configuration. |
| | | */ |
| | | public LDAPConnectionHandler2(QueueingStrategy strategy, String friendlyName) |
| | | { |
| | | super(friendlyName != null ? friendlyName : DEFAULT_FRIENDLY_NAME + " Thread"); |
| | | |
| | | this.friendlyName = friendlyName; |
| | | this.queueingStrategy = strategy; |
| | | } |
| | | |
| | | /** |
| | | * Indicates whether this connection handler should allow interaction with |
| | | * LDAPv2 clients. |
| | | * |
| | | * @return <CODE>true</CODE> if LDAPv2 is allowed, or <CODE>false</CODE> if |
| | | * not. |
| | | */ |
| | | public boolean allowLDAPv2() |
| | | { |
| | | return currentConfig.isAllowLDAPV2(); |
| | | } |
| | | |
| | | /** |
| | | * Indicates whether this connection handler should allow the use of the |
| | | * StartTLS extended operation. |
| | | * |
| | | * @return <CODE>true</CODE> if StartTLS is allowed, or <CODE>false</CODE> if |
| | | * not. |
| | | */ |
| | | public boolean allowStartTLS() |
| | | { |
| | | return currentConfig.isAllowStartTLS() && !currentConfig.isUseSSL(); |
| | | } |
| | | |
| | | @Override |
| | | public ConfigChangeResult applyConfigurationChange( |
| | | LDAPConnectionHandlerCfg config) |
| | | { |
| | | final ConfigChangeResult ccr = new ConfigChangeResult(); |
| | | |
| | | // Note that the following properties cannot be modified: |
| | | // * listen port and addresses |
| | | // * use ssl |
| | | // * ssl policy |
| | | // * ssl cert nickname |
| | | // * accept backlog |
| | | // * tcp reuse address |
| | | // * num request handler |
| | | |
| | | // Clear the stat tracker if LDAPv2 is being enabled. |
| | | if (currentConfig.isAllowLDAPV2() != config.isAllowLDAPV2() |
| | | && config.isAllowLDAPV2()) |
| | | { |
| | | statTracker.clearStatistics(); |
| | | } |
| | | |
| | | // Apply the changes. |
| | | currentConfig = config; |
| | | enabled = config.isEnabled(); |
| | | allowedClients = config.getAllowedClient(); |
| | | deniedClients = config.getDeniedClient(); |
| | | |
| | | // Reconfigure SSL if needed. |
| | | try |
| | | { |
| | | configureSSL(config); |
| | | } |
| | | catch (DirectoryException e) |
| | | { |
| | | logger.traceException(e); |
| | | ccr.setResultCode(e.getResultCode()); |
| | | ccr.addMessage(e.getMessageObject()); |
| | | return ccr; |
| | | } |
| | | |
| | | if (config.isAllowLDAPV2()) |
| | | { |
| | | DirectoryServer.registerSupportedLDAPVersion(2, this); |
| | | } |
| | | else |
| | | { |
| | | DirectoryServer.deregisterSupportedLDAPVersion(2, this); |
| | | } |
| | | |
| | | return ccr; |
| | | } |
| | | |
| | | private void configureSSL(LDAPConnectionHandlerCfg config) |
| | | throws DirectoryException |
| | | { |
| | | protocol = config.isUseSSL() ? "LDAPS" : "LDAP"; |
| | | if (config.isUseSSL() || config.isAllowStartTLS()) |
| | | { |
| | | sslContext = createSSLContext(config); |
| | | sslEngine = createSSLEngine(config, sslContext); |
| | | } |
| | | else |
| | | { |
| | | sslContext = null; |
| | | sslEngine = null; |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | public void finalizeConnectionHandler(LocalizableMessage finalizeReason) |
| | | { |
| | | shutdownRequested = true; |
| | | currentConfig.removeLDAPChangeListener(this); |
| | | |
| | | if (connMonitor != null) |
| | | { |
| | | DirectoryServer.deregisterMonitorProvider(connMonitor); |
| | | } |
| | | |
| | | if (statTracker != null) |
| | | { |
| | | DirectoryServer.deregisterMonitorProvider(statTracker); |
| | | } |
| | | |
| | | DirectoryServer.deregisterSupportedLDAPVersion(2, this); |
| | | DirectoryServer.deregisterSupportedLDAPVersion(3, this); |
| | | |
| | | // Shutdown the connection finalizer and ensure that any pending |
| | | // unclosed connections are closed. |
| | | synchronized (connectionFinalizerLock) |
| | | { |
| | | connectionFinalizer.shutdown(); |
| | | connectionFinalizer = null; |
| | | |
| | | Runnable r = new ConnectionFinalizerRunnable(); |
| | | r.run(); // Flush active queue. |
| | | r.run(); // Flush pending queue. |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * Retrieves information about the set of alerts that this generator may |
| | | * produce. The map returned should be between the notification type for a |
| | | * particular notification and the human-readable description for that |
| | | * notification. This alert generator must not generate any alerts with types |
| | | * that are not contained in this list. |
| | | * |
| | | * @return Information about the set of alerts that this generator may |
| | | * produce. |
| | | */ |
| | | @Override |
| | | public Map<String, String> getAlerts() |
| | | { |
| | | Map<String, String> alerts = new LinkedHashMap<>(); |
| | | |
| | | alerts.put(ALERT_TYPE_LDAP_CONNECTION_HANDLER_CONSECUTIVE_FAILURES, |
| | | ALERT_DESCRIPTION_LDAP_CONNECTION_HANDLER_CONSECUTIVE_FAILURES); |
| | | alerts.put(ALERT_TYPE_LDAP_CONNECTION_HANDLER_UNCAUGHT_ERROR, |
| | | ALERT_DESCRIPTION_LDAP_CONNECTION_HANDLER_UNCAUGHT_ERROR); |
| | | |
| | | return alerts; |
| | | } |
| | | |
| | | /** |
| | | * Retrieves the fully-qualified name of the Java class for this alert |
| | | * generator implementation. |
| | | * |
| | | * @return The fully-qualified name of the Java class for this alert generator |
| | | * implementation. |
| | | */ |
| | | @Override |
| | | public String getClassName() |
| | | { |
| | | return LDAPConnectionHandler2.class.getName(); |
| | | } |
| | | |
| | | /** |
| | | * Retrieves the set of active client connections that have been established |
| | | * through this connection handler. |
| | | * |
| | | * @return The set of active client connections that have been established |
| | | * through this connection handler. |
| | | */ |
| | | @Override |
| | | public Collection<ClientConnection> getClientConnections() |
| | | { |
| | | return connectionList; |
| | | } |
| | | |
| | | /** |
| | | * Retrieves the DN of the configuration entry with which this alert generator |
| | | * is associated. |
| | | * |
| | | * @return The DN of the configuration entry with which this alert generator |
| | | * is associated. |
| | | */ |
| | | @Override |
| | | public DN getComponentEntryDN() |
| | | { |
| | | return currentConfig.dn(); |
| | | } |
| | | |
| | | @Override |
| | | public String getConnectionHandlerName() |
| | | { |
| | | return handlerName; |
| | | } |
| | | |
| | | @Override |
| | | public Collection<String> getEnabledSSLCipherSuites() |
| | | { |
| | | final SSLEngine engine = sslEngine; |
| | | if (engine != null) |
| | | { |
| | | return Arrays.asList(engine.getEnabledCipherSuites()); |
| | | } |
| | | return super.getEnabledSSLCipherSuites(); |
| | | } |
| | | |
| | | @Override |
| | | public Collection<String> getEnabledSSLProtocols() |
| | | { |
| | | final SSLEngine engine = sslEngine; |
| | | if (engine != null) |
| | | { |
| | | return Arrays.asList(engine.getEnabledProtocols()); |
| | | } |
| | | return super.getEnabledSSLProtocols(); |
| | | } |
| | | |
| | | @Override |
| | | public Collection<HostPort> getListeners() |
| | | { |
| | | return listeners; |
| | | } |
| | | |
| | | /** |
| | | * Retrieves the maximum length of time in milliseconds that attempts to write |
| | | * to LDAP client connections should be allowed to block. |
| | | * |
| | | * @return The maximum length of time in milliseconds that attempts to write |
| | | * to LDAP client connections should be allowed to block, or zero if |
| | | * there should not be any limit imposed. |
| | | */ |
| | | public long getMaxBlockedWriteTimeLimit() |
| | | { |
| | | return currentConfig.getMaxBlockedWriteTimeLimit(); |
| | | } |
| | | |
| | | /** |
| | | * Retrieves the maximum ASN.1 element value length that will be allowed by |
| | | * this connection handler. |
| | | * |
| | | * @return The maximum ASN.1 element value length that will be allowed by this |
| | | * connection handler. |
| | | */ |
| | | public int getMaxRequestSize() |
| | | { |
| | | return (int) currentConfig.getMaxRequestSize(); |
| | | } |
| | | |
| | | /** |
| | | * Retrieves the size in bytes of the LDAP response message write buffer |
| | | * defined for this connection handler. |
| | | * |
| | | * @return The size in bytes of the LDAP response message write buffer. |
| | | */ |
| | | public int getBufferSize() |
| | | { |
| | | return (int) currentConfig.getBufferSize(); |
| | | } |
| | | |
| | | @Override |
| | | public String getProtocol() |
| | | { |
| | | return protocol; |
| | | } |
| | | |
| | | @Override |
| | | public String getShutdownListenerName() |
| | | { |
| | | return handlerName; |
| | | } |
| | | |
| | | /** |
| | | * Retrieves the SSL client authentication policy for this connection handler. |
| | | * |
| | | * @return The SSL client authentication policy for this connection handler. |
| | | */ |
| | | public SSLClientAuthPolicy getSSLClientAuthPolicy() |
| | | { |
| | | return sslClientAuthPolicy; |
| | | } |
| | | |
| | | /** |
| | | * Retrieves the set of statistics maintained by this connection handler. |
| | | * |
| | | * @return The set of statistics maintained by this connection handler. |
| | | */ |
| | | public LDAPStatistics getStatTracker() |
| | | { |
| | | return statTracker; |
| | | } |
| | | |
| | | @Override |
| | | public void initializeConnectionHandler(ServerContext serverContext, LDAPConnectionHandlerCfg config) |
| | | throws ConfigException, InitializationException |
| | | { |
| | | if (friendlyName == null) |
| | | { |
| | | friendlyName = config.dn().rdn().getFirstAVA().getAttributeValue().toString(); |
| | | } |
| | | |
| | | // Save this configuration for future reference. |
| | | currentConfig = config; |
| | | enabled = config.isEnabled(); |
| | | allowedClients = config.getAllowedClient(); |
| | | deniedClients = config.getDeniedClient(); |
| | | |
| | | // Configure SSL if needed. |
| | | try |
| | | { |
| | | // This call may disable the connector if wrong SSL settings |
| | | configureSSL(config); |
| | | } |
| | | catch (DirectoryException e) |
| | | { |
| | | logger.traceException(e); |
| | | throw new InitializationException(e.getMessageObject()); |
| | | } |
| | | |
| | | // Save properties that cannot be dynamically modified. |
| | | allowReuseAddress = config.isAllowTCPReuseAddress(); |
| | | backlog = config.getAcceptBacklog(); |
| | | listenAddresses = new HashSet<>(); |
| | | for(InetAddress addr :config.getListenAddress()) { |
| | | listenAddresses.add(new InetSocketAddress(addr, config.getListenPort())); |
| | | } |
| | | |
| | | // Construct a unique name for this connection handler, and put |
| | | // together the set of listeners. |
| | | listeners = new LinkedList<>(); |
| | | StringBuilder nameBuffer = new StringBuilder(); |
| | | nameBuffer.append(friendlyName); |
| | | for (InetSocketAddress a : listenAddresses) |
| | | { |
| | | listeners.add(new HostPort(a.getHostName(), a.getPort())); |
| | | nameBuffer.append(" "); |
| | | nameBuffer.append(a.getHostName()); |
| | | } |
| | | nameBuffer.append(" port "); |
| | | nameBuffer.append(config.getListenPort()); |
| | | handlerName = nameBuffer.toString(); |
| | | |
| | | // Attempt to bind to the listen port on all configured addresses to |
| | | // verify whether the connection handler will be able to start. |
| | | LocalizableMessage errorMessage = |
| | | checkAnyListenAddressInUse(config.getListenAddress(), config.getListenPort(), |
| | | allowReuseAddress, config.dn()); |
| | | if (errorMessage != null) |
| | | { |
| | | logger.error(errorMessage); |
| | | throw new InitializationException(errorMessage); |
| | | } |
| | | |
| | | // Create a system property to store the LDAP(S) port the server is |
| | | // listening to. This information can be displayed with jinfo. |
| | | System.setProperty(protocol + "_port", String.valueOf(config.getListenPort())); |
| | | |
| | | // Create and start a connection finalizer thread for this |
| | | // connection handler. |
| | | connectionFinalizer = Executors |
| | | .newSingleThreadScheduledExecutor(new DirectoryThread.Factory( |
| | | "LDAP Connection Finalizer for connection handler " + toString())); |
| | | |
| | | connectionFinalizerActiveJobQueue = new ArrayList<>(); |
| | | connectionFinalizerPendingJobQueue = new ArrayList<>(); |
| | | |
| | | connectionFinalizer.scheduleWithFixedDelay( |
| | | new ConnectionFinalizerRunnable(), 100, 100, TimeUnit.MILLISECONDS); |
| | | |
| | | // Register the set of supported LDAP versions. |
| | | DirectoryServer.registerSupportedLDAPVersion(3, this); |
| | | if (config.isAllowLDAPV2()) |
| | | { |
| | | DirectoryServer.registerSupportedLDAPVersion(2, this); |
| | | } |
| | | |
| | | // Create and register monitors. |
| | | statTracker = new LDAPStatistics(handlerName + " Statistics"); |
| | | DirectoryServer.registerMonitorProvider(statTracker); |
| | | |
| | | connMonitor = new ClientConnectionMonitorProvider(this); |
| | | DirectoryServer.registerMonitorProvider(connMonitor); |
| | | |
| | | // Register this as a change listener. |
| | | config.addLDAPChangeListener(this); |
| | | } |
| | | |
| | | @Override |
| | | public boolean isConfigurationAcceptable(ConnectionHandlerCfg configuration, |
| | | List<LocalizableMessage> unacceptableReasons) |
| | | { |
| | | LDAPConnectionHandlerCfg config = (LDAPConnectionHandlerCfg) configuration; |
| | | |
| | | if (currentConfig == null |
| | | || (!currentConfig.isEnabled() && config.isEnabled())) |
| | | { |
| | | // Attempt to bind to the listen port on all configured addresses to |
| | | // verify whether the connection handler will be able to start. |
| | | LocalizableMessage errorMessage = |
| | | checkAnyListenAddressInUse(config.getListenAddress(), config |
| | | .getListenPort(), config.isAllowTCPReuseAddress(), config.dn()); |
| | | if (errorMessage != null) |
| | | { |
| | | unacceptableReasons.add(errorMessage); |
| | | return false; |
| | | } |
| | | } |
| | | |
| | | if (config.isEnabled() |
| | | // Check that the SSL configuration is valid. |
| | | && (config.isUseSSL() || config.isAllowStartTLS())) |
| | | { |
| | | try |
| | | { |
| | | createSSLEngine(config, createSSLContext(config)); |
| | | } |
| | | catch (DirectoryException e) |
| | | { |
| | | logger.traceException(e); |
| | | |
| | | unacceptableReasons.add(e.getMessageObject()); |
| | | return false; |
| | | } |
| | | } |
| | | |
| | | return true; |
| | | } |
| | | |
| | | /** |
| | | * Checks whether any listen address is in use for the given port. The check |
| | | * is performed by binding to each address and port. |
| | | * |
| | | * @param listenAddresses |
| | | * the listen {@link InetAddress} to test |
| | | * @param listenPort |
| | | * the listen port to test |
| | | * @param allowReuseAddress |
| | | * whether addresses can be reused |
| | | * @param configEntryDN |
| | | * the configuration entry DN |
| | | * @return an error message if at least one of the address is already in use, |
| | | * null otherwise. |
| | | */ |
| | | private LocalizableMessage checkAnyListenAddressInUse( |
| | | Collection<InetAddress> listenAddresses, int listenPort, |
| | | boolean allowReuseAddress, DN configEntryDN) |
| | | { |
| | | for (InetAddress a : listenAddresses) |
| | | { |
| | | try |
| | | { |
| | | if (StaticUtils.isAddressInUse(a, listenPort, allowReuseAddress)) |
| | | { |
| | | throw new IOException(ERR_CONNHANDLER_ADDRESS_INUSE.get().toString()); |
| | | } |
| | | } |
| | | catch (IOException e) |
| | | { |
| | | logger.traceException(e); |
| | | return ERR_CONNHANDLER_CANNOT_BIND.get("LDAP", configEntryDN, a.getHostAddress(), listenPort, |
| | | getExceptionMessage(e)); |
| | | } |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | @Override |
| | | public boolean isConfigurationChangeAcceptable( |
| | | LDAPConnectionHandlerCfg config, List<LocalizableMessage> unacceptableReasons) |
| | | { |
| | | return isConfigurationAcceptable(config, unacceptableReasons); |
| | | } |
| | | |
| | | @Override |
| | | public void processServerShutdown(LocalizableMessage reason) |
| | | { |
| | | shutdownRequested = true; |
| | | } |
| | | |
| | | void stopListener() |
| | | { |
| | | if (listener != null) |
| | | { |
| | | listener.close(); |
| | | listener = null; |
| | | } |
| | | } |
| | | |
| | | private void startListener() throws IOException |
| | | { |
| | | listener = new GrizzlyLDAPListener( |
| | | listenAddresses, |
| | | Options.defaultOptions() |
| | | .set(LDAPListener.CONNECT_MAX_BACKLOG, backlog) |
| | | .set(LDAPListener.REQUEST_MAX_SIZE_IN_BYTES, (int) currentConfig.getMaxRequestSize()), |
| | | new Function<LDAPClientContext, |
| | | ReactiveHandler<LDAPClientContext,LdapRawMessage, Stream<Response>>, |
| | | LdapException>() { |
| | | @Override |
| | | public ReactiveHandler<LDAPClientContext, LdapRawMessage, Stream<Response>> |
| | | apply(LDAPClientContext value) throws LdapException { |
| | | final LDAPClientConnection2 conn = canAccept(value); |
| | | return new ReactiveHandler<LDAPClientContext, LdapRawMessage, Stream<Response>>() { |
| | | @Override |
| | | public Single<Stream<Response>> handle(LDAPClientContext context, |
| | | LdapRawMessage request) throws Exception { |
| | | return conn.handle(queueingStrategy, request); |
| | | } |
| | | }; |
| | | } |
| | | }); |
| | | } |
| | | |
| | | /** |
| | | * Operates in a loop, accepting new connections and ensuring that requests on |
| | | * those connections are handled properly. |
| | | */ |
| | | @Override |
| | | public void run() |
| | | { |
| | | setName(handlerName); |
| | | boolean starting = true; |
| | | setName(handlerName); |
| | | |
| | | boolean lastIterationFailed = false; |
| | | |
| | | while (!shutdownRequested) |
| | | { |
| | | // If this connection handler is not enabled, then just sleep for a bit and check again. |
| | | if (!this.enabled) |
| | | { |
| | | if (listener != null) |
| | | { |
| | | stopListener(); |
| | | } |
| | | |
| | | if (starting) |
| | | { |
| | | // This may happen if there was an initialisation error which led to disable the connector. |
| | | // The main thread is waiting for the connector to listen on its port, which will not occur yet, |
| | | // so notify here to allow the server startup to complete. |
| | | synchronized (waitListen) |
| | | { |
| | | starting = false; |
| | | waitListen.notify(); |
| | | } |
| | | } |
| | | |
| | | StaticUtils.sleep(1000); |
| | | continue; |
| | | } |
| | | |
| | | if (listener != null) |
| | | { |
| | | // If already listening, then sleep for a bit and check again. |
| | | StaticUtils.sleep(1000); |
| | | continue; |
| | | } |
| | | |
| | | try |
| | | { |
| | | // At this point, the connection Handler either started correctly or failed |
| | | // to start but the start process should be notified and resume its work in any cases. |
| | | synchronized (waitListen) |
| | | { |
| | | waitListen.notify(); |
| | | } |
| | | |
| | | // If we have gotten here, then we are about to start listening |
| | | // for the first time since startup or since we were previously disabled. |
| | | // Start the embedded HTTP server |
| | | startListener(); |
| | | lastIterationFailed = false; |
| | | } |
| | | catch (Exception e) |
| | | { |
| | | // Clean up the messed up HTTP server |
| | | stopListener(); |
| | | |
| | | // Error + alert about the horked config |
| | | logger.traceException(e); |
| | | logger.error( |
| | | ERR_CONNHANDLER_CANNOT_ACCEPT_CONNECTION, friendlyName, currentConfig.dn(), getExceptionMessage(e)); |
| | | |
| | | if (lastIterationFailed) |
| | | { |
| | | // The last time through the accept loop we also encountered a failure. |
| | | // Rather than enter a potential infinite loop of failures, |
| | | // disable this acceptor and log an error. |
| | | LocalizableMessage message = ERR_CONNHANDLER_CONSECUTIVE_ACCEPT_FAILURES.get( |
| | | friendlyName, currentConfig.dn(), stackTraceToSingleLineString(e)); |
| | | logger.error(message); |
| | | |
| | | DirectoryServer.sendAlertNotification(this, ALERT_TYPE_HTTP_CONNECTION_HANDLER_CONSECUTIVE_FAILURES, message); |
| | | this.enabled = false; |
| | | } |
| | | else |
| | | { |
| | | lastIterationFailed = true; |
| | | } |
| | | } |
| | | } |
| | | |
| | | // Initiate shutdown |
| | | stopListener(); |
| | | } |
| | | |
| | | private LDAPClientConnection2 canAccept(LDAPClientContext clientContext) throws LdapException |
| | | { |
| | | // Check to see if the core server rejected the |
| | | // connection (e.g., already too many connections |
| | | // established). |
| | | final LDAPClientConnection2 clientConnection = |
| | | new LDAPClientConnection2(this, clientContext, getProtocol(), currentConfig.isKeepStats()); |
| | | if (clientConnection.getConnectionID() < 0) |
| | | { |
| | | clientConnection.disconnect( |
| | | DisconnectReason.ADMIN_LIMIT_EXCEEDED, true, ERR_CONNHANDLER_REJECTED_BY_SERVER.get()); |
| | | throw LdapException.newLdapException(ResultCode.ADMIN_LIMIT_EXCEEDED); |
| | | } |
| | | |
| | | InetAddress clientAddr = clientConnection.getRemoteAddress(); |
| | | // Check to see if the client is on the denied list. |
| | | // If so, then reject it immediately. |
| | | if (!deniedClients.isEmpty() |
| | | && AddressMask.matchesAny(deniedClients, clientAddr)) |
| | | { |
| | | clientConnection.disconnect(DisconnectReason.CONNECTION_REJECTED, |
| | | currentConfig.isSendRejectionNotice(), ERR_CONNHANDLER_DENIED_CLIENT |
| | | .get(clientConnection.getClientHostPort(), clientConnection |
| | | .getServerHostPort())); |
| | | throw LdapException.newLdapException(ResultCode.CONSTRAINT_VIOLATION); |
| | | } |
| | | // Check to see if there is an allowed list and if |
| | | // there is whether the client is on that list. If |
| | | // not, then reject the connection. |
| | | if (!allowedClients.isEmpty() |
| | | && !AddressMask.matchesAny(allowedClients, clientAddr)) |
| | | { |
| | | clientConnection.disconnect(DisconnectReason.CONNECTION_REJECTED, |
| | | currentConfig.isSendRejectionNotice(), |
| | | ERR_CONNHANDLER_DISALLOWED_CLIENT.get(clientConnection |
| | | .getClientHostPort(), clientConnection.getServerHostPort())); |
| | | throw LdapException.newLdapException(ResultCode.CONSTRAINT_VIOLATION); |
| | | } |
| | | |
| | | // If we've gotten here, then we'll take the |
| | | // connection so invoke the post-connect plugins and |
| | | // register the client connection with a request |
| | | // handler. |
| | | try |
| | | { |
| | | PluginConfigManager pluginManager = DirectoryServer |
| | | .getPluginConfigManager(); |
| | | PluginResult.PostConnect pluginResult = pluginManager |
| | | .invokePostConnectPlugins(clientConnection); |
| | | if (!pluginResult.continueProcessing()) |
| | | { |
| | | clientConnection.disconnect(pluginResult.getDisconnectReason(), |
| | | pluginResult.sendDisconnectNotification(), |
| | | pluginResult.getErrorMessage()); |
| | | throw LdapException.newLdapException(ResultCode.CONSTRAINT_VIOLATION); |
| | | } |
| | | } |
| | | catch (Exception e) |
| | | { |
| | | logger.traceException(e); |
| | | |
| | | LocalizableMessage message = |
| | | INFO_CONNHANDLER_UNABLE_TO_REGISTER_CLIENT.get(clientConnection |
| | | .getClientHostPort(), clientConnection.getServerHostPort(), |
| | | getExceptionMessage(e)); |
| | | logger.debug(message); |
| | | |
| | | clientConnection.disconnect(DisconnectReason.SERVER_ERROR, |
| | | currentConfig.isSendRejectionNotice(), message); |
| | | throw LdapException.newLdapException(ResultCode.OPERATIONS_ERROR); |
| | | } |
| | | |
| | | if (useSSL()) { |
| | | try { |
| | | clientContext.enableTLS(createSSLEngine()); |
| | | } catch (DirectoryException e) { |
| | | throw LdapException.newLdapException(e.getResultCode(), e); |
| | | } |
| | | } |
| | | |
| | | return clientConnection; |
| | | } |
| | | |
| | | /** |
| | | * Appends a string representation of this connection handler to the provided |
| | | * buffer. |
| | | * |
| | | * @param buffer |
| | | * The buffer to which the information should be appended. |
| | | */ |
| | | @Override |
| | | public void toString(StringBuilder buffer) |
| | | { |
| | | buffer.append(handlerName); |
| | | } |
| | | |
| | | /** |
| | | * Indicates whether this connection handler should use SSL to communicate |
| | | * with clients. |
| | | * |
| | | * @return {@code true} if this connection handler should use SSL to |
| | | * communicate with clients, or {@code false} if not. |
| | | */ |
| | | public boolean useSSL() |
| | | { |
| | | return currentConfig.isUseSSL(); |
| | | } |
| | | |
| | | SSLEngine createSSLEngine() throws DirectoryException { |
| | | return createSSLEngine(currentConfig, sslContext); |
| | | } |
| | | |
| | | private SSLEngine createSSLEngine(LDAPConnectionHandlerCfg config, SSLContext sslContext) throws DirectoryException |
| | | { |
| | | try |
| | | { |
| | | SSLEngine sslEngine = sslContext.createSSLEngine(); |
| | | sslEngine.setUseClientMode(false); |
| | | |
| | | final Set<String> protocols = config.getSSLProtocol(); |
| | | if (!protocols.isEmpty()) |
| | | { |
| | | sslEngine.setEnabledProtocols(protocols.toArray(new String[0])); |
| | | } |
| | | |
| | | final Set<String> ciphers = config.getSSLCipherSuite(); |
| | | if (!ciphers.isEmpty()) |
| | | { |
| | | sslEngine.setEnabledCipherSuites(ciphers.toArray(new String[0])); |
| | | } |
| | | |
| | | switch (config.getSSLClientAuthPolicy()) |
| | | { |
| | | case DISABLED: |
| | | sslEngine.setNeedClientAuth(false); |
| | | sslEngine.setWantClientAuth(false); |
| | | break; |
| | | case REQUIRED: |
| | | sslEngine.setWantClientAuth(true); |
| | | sslEngine.setNeedClientAuth(true); |
| | | break; |
| | | case OPTIONAL: |
| | | default: |
| | | sslEngine.setNeedClientAuth(false); |
| | | sslEngine.setWantClientAuth(true); |
| | | break; |
| | | } |
| | | |
| | | return sslEngine; |
| | | } |
| | | catch (Exception e) |
| | | { |
| | | logger.traceException(e); |
| | | ResultCode resCode = DirectoryServer.getServerErrorResultCode(); |
| | | LocalizableMessage message = ERR_CONNHANDLER_SSL_CANNOT_INITIALIZE |
| | | .get(getExceptionMessage(e)); |
| | | throw new DirectoryException(resCode, message, e); |
| | | } |
| | | } |
| | | |
| | | private void disableAndWarnIfUseSSL(LDAPConnectionHandlerCfg config) |
| | | { |
| | | if (config.isUseSSL()) |
| | | { |
| | | logger.warn(INFO_DISABLE_CONNECTION, friendlyName); |
| | | enabled = false; |
| | | } |
| | | } |
| | | |
| | | private SSLContext createSSLContext(LDAPConnectionHandlerCfg config) |
| | | throws DirectoryException |
| | | { |
| | | try |
| | | { |
| | | DN keyMgrDN = config.getKeyManagerProviderDN(); |
| | | KeyManagerProvider<?> keyManagerProvider = DirectoryServer |
| | | .getKeyManagerProvider(keyMgrDN); |
| | | if (keyManagerProvider == null) |
| | | { |
| | | logger.error(ERR_NULL_KEY_PROVIDER_MANAGER, keyMgrDN, friendlyName); |
| | | disableAndWarnIfUseSSL(config); |
| | | keyManagerProvider = new NullKeyManagerProvider(); |
| | | // The SSL connection is unusable without a key manager provider |
| | | } |
| | | else if (! keyManagerProvider.containsAtLeastOneKey()) |
| | | { |
| | | logger.error(ERR_INVALID_KEYSTORE, friendlyName); |
| | | disableAndWarnIfUseSSL(config); |
| | | } |
| | | |
| | | final SortedSet<String> aliases = new TreeSet<>(config.getSSLCertNickname()); |
| | | final KeyManager[] keyManagers; |
| | | if (aliases.isEmpty()) |
| | | { |
| | | keyManagers = keyManagerProvider.getKeyManagers(); |
| | | } |
| | | else |
| | | { |
| | | final Iterator<String> it = aliases.iterator(); |
| | | while (it.hasNext()) |
| | | { |
| | | if (!keyManagerProvider.containsKeyWithAlias(it.next())) |
| | | { |
| | | logger.error(ERR_KEYSTORE_DOES_NOT_CONTAIN_ALIAS, aliases, friendlyName); |
| | | it.remove(); |
| | | } |
| | | } |
| | | |
| | | if (aliases.isEmpty()) |
| | | { |
| | | disableAndWarnIfUseSSL(config); |
| | | } |
| | | keyManagers = SelectableCertificateKeyManager.wrap(keyManagerProvider.getKeyManagers(), aliases, friendlyName); |
| | | } |
| | | |
| | | DN trustMgrDN = config.getTrustManagerProviderDN(); |
| | | TrustManagerProvider<?> trustManagerProvider = DirectoryServer |
| | | .getTrustManagerProvider(trustMgrDN); |
| | | if (trustManagerProvider == null) |
| | | { |
| | | trustManagerProvider = new NullTrustManagerProvider(); |
| | | } |
| | | |
| | | SSLContext sslContext = SSLContext.getInstance(SSL_CONTEXT_INSTANCE_NAME); |
| | | sslContext.init(keyManagers, trustManagerProvider.getTrustManagers(), |
| | | null); |
| | | return sslContext; |
| | | } |
| | | catch (Exception e) |
| | | { |
| | | logger.traceException(e); |
| | | ResultCode resCode = DirectoryServer.getServerErrorResultCode(); |
| | | LocalizableMessage message = ERR_CONNHANDLER_SSL_CANNOT_INITIALIZE |
| | | .get(getExceptionMessage(e)); |
| | | throw new DirectoryException(resCode, message, e); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * Enqueue a connection finalizer which will be invoked after a short delay. |
| | | * |
| | | * @param r |
| | | * The connection finalizer runnable. |
| | | */ |
| | | void registerConnectionFinalizer(Runnable r) |
| | | { |
| | | synchronized (connectionFinalizerLock) |
| | | { |
| | | if (connectionFinalizer != null) |
| | | { |
| | | connectionFinalizerPendingJobQueue.add(r); |
| | | } |
| | | else |
| | | { |
| | | // Already finalized - invoked immediately. |
| | | r.run(); |
| | | } |
| | | } |
| | | } |
| | | } |
| New file |
| | |
| | | /* |
| | | * 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 2016 ForgeRock AS. |
| | | */ |
| | | package org.forgerock.opendj.reactive; |
| | | |
| | | import static com.forgerock.reactive.RxJavaStreams.*; |
| | | import static org.forgerock.util.Utils.closeSilently; |
| | | |
| | | import java.util.concurrent.atomic.AtomicReference; |
| | | |
| | | import org.forgerock.opendj.ldap.Connection; |
| | | import org.forgerock.opendj.ldap.IntermediateResponseHandler; |
| | | import org.forgerock.opendj.ldap.LDAPConnectionFactory; |
| | | import org.forgerock.opendj.ldap.LdapException; |
| | | import org.forgerock.opendj.ldap.SearchResultHandler; |
| | | import org.forgerock.opendj.ldap.requests.CompareRequest; |
| | | import org.forgerock.opendj.ldap.requests.ExtendedRequest; |
| | | import org.forgerock.opendj.ldap.requests.Request; |
| | | import org.forgerock.opendj.ldap.requests.SearchRequest; |
| | | import org.forgerock.opendj.ldap.responses.IntermediateResponse; |
| | | import org.forgerock.opendj.ldap.responses.Response; |
| | | import org.forgerock.opendj.ldap.responses.Result; |
| | | import org.forgerock.opendj.ldap.responses.SearchResultEntry; |
| | | import org.forgerock.opendj.ldap.responses.SearchResultReference; |
| | | import org.forgerock.opendj.ldif.ChangeRecord; |
| | | import org.forgerock.util.promise.ExceptionHandler; |
| | | import org.forgerock.util.promise.ResultHandler; |
| | | import org.forgerock.util.promise.RuntimeExceptionHandler; |
| | | |
| | | import com.forgerock.reactive.ReactiveHandler; |
| | | import com.forgerock.reactive.Single; |
| | | import com.forgerock.reactive.Stream; |
| | | |
| | | import io.reactivex.Flowable; |
| | | import io.reactivex.FlowableEmitter; |
| | | import io.reactivex.FlowableEmitter.BackpressureMode; |
| | | import io.reactivex.FlowableOnSubscribe; |
| | | import io.reactivex.functions.Action; |
| | | |
| | | /** Forward {@link Request} to another server reached through the {@link LDAPConnectionFactory}. */ |
| | | final class ProxyToHandler implements ReactiveHandler<LDAPClientConnection2, Request, Stream<Response>> { |
| | | |
| | | private final LDAPConnectionFactory connectionFactory; |
| | | |
| | | ProxyToHandler(final LDAPConnectionFactory connectionFactory) { |
| | | this.connectionFactory = connectionFactory; |
| | | } |
| | | |
| | | @Override |
| | | public Single<Stream<Response>> handle(final LDAPClientConnection2 context, final Request request) { |
| | | return singleFromPublisher(Flowable.create(new FlowableOnSubscribe<Stream<Response>>() { |
| | | @Override |
| | | public void subscribe(final FlowableEmitter<Stream<Response>> emitter) throws Exception { |
| | | final AtomicReference<Connection> connectionHolder = new AtomicReference<Connection>(); |
| | | |
| | | connectionFactory.getConnectionAsync().thenOnResult(new ResultHandler<Connection>() { |
| | | @Override |
| | | public void handleResult(final Connection connection) { |
| | | connectionHolder.set(connection); |
| | | emitter.onNext(executeRequest(connection, request)); |
| | | emitter.onComplete(); |
| | | } |
| | | }).thenOnException(new ExceptionHandler<LdapException>() { |
| | | @Override |
| | | public void handleException(LdapException exception) { |
| | | emitter.onError(exception); |
| | | } |
| | | }).thenOnRuntimeException(new RuntimeExceptionHandler() { |
| | | @Override |
| | | public void handleRuntimeException(RuntimeException exception) { |
| | | emitter.onError(exception); |
| | | } |
| | | }); |
| | | } |
| | | }, BackpressureMode.ERROR)); |
| | | } |
| | | |
| | | private Stream<Response> executeRequest(final Connection connection, final Request request) { |
| | | return streamFromPublisher(Flowable.create(new FlowableOnSubscribe<Response>() { |
| | | @Override |
| | | public void subscribe(FlowableEmitter<Response> emitter) throws Exception { |
| | | // add/delete/modifyDN/modifyReq |
| | | final PublisherAdaptor adapter = new PublisherAdaptor(emitter); |
| | | if (request instanceof ChangeRecord) { |
| | | connection.applyChangeAsync((ChangeRecord) request, adapter).thenOnResult(adapter) |
| | | .thenOnException(adapter).thenOnRuntimeException(adapter); |
| | | } else if (request instanceof CompareRequest) { |
| | | connection.compareAsync((CompareRequest) request, adapter).thenOnResult(adapter) |
| | | .thenOnException(adapter).thenOnRuntimeException(adapter); |
| | | } else if (request instanceof ExtendedRequest) { |
| | | connection.compareAsync((CompareRequest) request, adapter).thenOnResult(adapter) |
| | | .thenOnException(adapter).thenOnRuntimeException(adapter); |
| | | } else if (request instanceof SearchRequest) { |
| | | connection.searchAsync((SearchRequest) request, adapter).thenOnResult(adapter) |
| | | .thenOnException(adapter).thenOnRuntimeException(adapter); |
| | | } else { |
| | | emitter.onError(new IllegalArgumentException("Unsupported request type")); |
| | | } |
| | | } |
| | | }, BackpressureMode.ERROR).doAfterTerminate(new Action() { |
| | | @Override |
| | | public void run() throws Exception { |
| | | closeSilently(connection); |
| | | } |
| | | })); |
| | | } |
| | | |
| | | /** Adaptor forwarding events from the SDK handler to the emitter. */ |
| | | private final class PublisherAdaptor implements IntermediateResponseHandler, SearchResultHandler, |
| | | ResultHandler<Result>, ExceptionHandler<Exception>, RuntimeExceptionHandler { |
| | | |
| | | private final FlowableEmitter<Response> emitter; |
| | | |
| | | PublisherAdaptor(final FlowableEmitter<Response> emitter) { |
| | | this.emitter = emitter; |
| | | } |
| | | |
| | | private boolean handle(final Response response) { |
| | | emitter.onNext(response); |
| | | return true; |
| | | } |
| | | |
| | | @Override |
| | | public boolean handleEntry(SearchResultEntry entry) { |
| | | return handle(entry); |
| | | } |
| | | |
| | | @Override |
| | | public boolean handleReference(SearchResultReference reference) { |
| | | return handle(reference); |
| | | } |
| | | |
| | | @Override |
| | | public boolean handleIntermediateResponse(IntermediateResponse response) { |
| | | return handle(response); |
| | | } |
| | | |
| | | @Override |
| | | public void handleResult(Result result) { |
| | | if (result != null) { |
| | | handle(result); |
| | | } |
| | | emitter.onComplete(); |
| | | } |
| | | |
| | | @Override |
| | | public void handleRuntimeException(RuntimeException exception) { |
| | | emitter.onError(exception); |
| | | } |
| | | |
| | | @Override |
| | | public void handleException(Exception exception) { |
| | | emitter.onError(exception); |
| | | } |
| | | } |
| | | } |
| New file |
| | |
| | | /* |
| | | * 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 2016 ForgeRock AS. |
| | | */ |
| | | package org.forgerock.opendj.reactive; |
| | | |
| | | import static org.forgerock.util.Reject.checkNotNull; |
| | | |
| | | import com.forgerock.opendj.util.Predicate; |
| | | import com.forgerock.reactive.ReactiveHandler; |
| | | |
| | | /** |
| | | * Contains a routing predicate and a handler. If the predicate matches, the handler will be invoked. |
| | | * |
| | | * @param <CTX> |
| | | * Type of context in which request are processed |
| | | * @param <REQ> |
| | | * Type of routed request |
| | | * @param <REP> |
| | | * Type of routed response |
| | | */ |
| | | public final class Route<CTX, REQ, REP> { |
| | | final Predicate<REQ, CTX> predicate; |
| | | final ReactiveHandler<CTX, REQ, REP> handler; |
| | | |
| | | /** |
| | | * Creates a new route. |
| | | * |
| | | * @param <CTX> |
| | | * Type of context in which request are processed |
| | | * @param <REQ> |
| | | * Type of routed request |
| | | * @param <REP> |
| | | * Type of routed response |
| | | * @param predicate |
| | | * The {@link Predicate} which must be fulfilled to apply this route |
| | | * @param target |
| | | * The {@link ReactiveHandler} to invoke when this route is applied |
| | | * @return a new {@link Route} |
| | | */ |
| | | public static <CTX, REQ, REP> Route<CTX, REQ, REP> newRoute(final Predicate<REQ, CTX> predicate, |
| | | final ReactiveHandler<CTX, REQ, REP> target) { |
| | | return new Route<>(predicate, target); |
| | | } |
| | | |
| | | private Route(final Predicate<REQ, CTX> predicate, final ReactiveHandler<CTX, REQ, REP> target) { |
| | | this.predicate = checkNotNull(predicate, "predicate must not be null"); |
| | | this.handler = checkNotNull(target, "handler must not be null"); |
| | | } |
| | | } |
| New file |
| | |
| | | /* |
| | | * 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 2016 ForgeRock AS. |
| | | */ |
| | | |
| | | /** Classes implementing a Grizzly based {@link org.opends.server.api.ConnectionHandler} using reactive api. */ |
| | | package org.forgerock.opendj.reactive; |
| | | |
| New file |
| | |
| | | /* |
| | | * 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 2006-2008 Sun Microsystems, Inc. |
| | | * Portions Copyright 2014-2016 ForgeRock AS. |
| | | */ |
| | | package org.opends.server.core; |
| | | |
| | | import static org.forgerock.opendj.ldap.ResultCode.*; |
| | | import static org.opends.messages.ConfigMessages.*; |
| | | import static org.opends.server.core.DirectoryServer.*; |
| | | import static org.opends.server.util.StaticUtils.*; |
| | | |
| | | import java.util.Collection; |
| | | import java.util.Iterator; |
| | | import java.util.LinkedHashSet; |
| | | import java.util.List; |
| | | import java.util.Set; |
| | | import java.util.concurrent.ConcurrentHashMap; |
| | | |
| | | import org.forgerock.i18n.LocalizableMessage; |
| | | import org.forgerock.i18n.slf4j.LocalizedLogger; |
| | | import org.forgerock.opendj.config.server.ConfigChangeResult; |
| | | import org.forgerock.opendj.config.server.ConfigException; |
| | | import org.forgerock.opendj.config.server.ConfigurationAddListener; |
| | | import org.forgerock.opendj.config.server.ConfigurationChangeListener; |
| | | import org.forgerock.opendj.config.server.ConfigurationDeleteListener; |
| | | import org.forgerock.opendj.ldap.DN; |
| | | import org.forgerock.opendj.ldap.ResultCode; |
| | | import org.forgerock.opendj.server.config.meta.BackendCfgDefn; |
| | | import org.forgerock.opendj.server.config.server.BackendCfg; |
| | | import org.forgerock.opendj.server.config.server.RootCfg; |
| | | import org.opends.server.api.Backend; |
| | | import org.opends.server.api.BackendInitializationListener; |
| | | import org.opends.server.backends.ConfigurationBackend; |
| | | import org.opends.server.config.ConfigConstants; |
| | | import org.opends.server.types.DirectoryException; |
| | | import org.opends.server.types.Entry; |
| | | import org.opends.server.types.InitializationException; |
| | | import org.opends.server.types.WritabilityMode; |
| | | |
| | | /** |
| | | * This class defines a utility that will be used to manage the configuration |
| | | * for the set of backends defined in the Directory Server. It will perform |
| | | * the necessary initialization of those backends when the server is first |
| | | * started, and then will manage any changes to them while the server is |
| | | * running. |
| | | */ |
| | | public class LdapEndpointConfigManager implements |
| | | ConfigurationChangeListener<BackendCfg>, |
| | | ConfigurationAddListener<BackendCfg>, |
| | | ConfigurationDeleteListener<BackendCfg> |
| | | { |
| | | private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass(); |
| | | |
| | | /** The mapping between configuration entry DNs and their corresponding backend implementations. */ |
| | | private final ConcurrentHashMap<DN, Backend<? extends BackendCfg>> registeredBackends = new ConcurrentHashMap<>(); |
| | | private final ServerContext serverContext; |
| | | |
| | | /** |
| | | * Creates a new instance of this backend config manager. |
| | | * |
| | | * @param serverContext |
| | | * The server context. |
| | | */ |
| | | public LdapEndpointConfigManager(ServerContext serverContext) |
| | | { |
| | | this.serverContext = serverContext; |
| | | } |
| | | |
| | | /** |
| | | * Initializes the configuration associated with the Directory Server |
| | | * backends. This should only be called at Directory Server startup. |
| | | * |
| | | * @param backendIDsToStart |
| | | * The list of backendID to start. Everything will be started if empty. |
| | | * @throws ConfigException |
| | | * If a critical configuration problem prevents the backend |
| | | * initialization from succeeding. |
| | | * @throws InitializationException |
| | | * If a problem occurs while initializing the backends that is not |
| | | * related to the server configuration. |
| | | */ |
| | | public void initializeBackendConfig(Collection<String> backendIDsToStart) |
| | | throws ConfigException, InitializationException |
| | | { |
| | | initializeConfigurationBackend(); |
| | | |
| | | // Register add and delete listeners. |
| | | RootCfg root = serverContext.getRootConfig(); |
| | | root.addBackendAddListener(this); |
| | | root.addBackendDeleteListener(this); |
| | | |
| | | // Get the configuration entry that is at the root of all the backends in |
| | | // the server. |
| | | Entry backendRoot; |
| | | try |
| | | { |
| | | DN configEntryDN = DN.valueOf(ConfigConstants.DN_BACKEND_BASE); |
| | | backendRoot = DirectoryServer.getConfigEntry(configEntryDN); |
| | | } |
| | | catch (Exception e) |
| | | { |
| | | logger.traceException(e); |
| | | |
| | | LocalizableMessage message = |
| | | ERR_CONFIG_BACKEND_CANNOT_GET_CONFIG_BASE.get(getExceptionMessage(e)); |
| | | throw new ConfigException(message, e); |
| | | } |
| | | |
| | | |
| | | // If the configuration root entry is null, then assume it doesn't exist. |
| | | // In that case, then fail. At least that entry must exist in the |
| | | // configuration, even if there are no backends defined below it. |
| | | if (backendRoot == null) |
| | | { |
| | | throw new ConfigException(ERR_CONFIG_BACKEND_BASE_DOES_NOT_EXIST.get()); |
| | | } |
| | | initializeBackends(backendIDsToStart, root); |
| | | } |
| | | |
| | | /** |
| | | * Initializes specified backends. If a backend has been already initialized, do nothing. |
| | | * This should only be called at Directory Server startup, after #initializeBackendConfig() |
| | | * |
| | | * @param backendIDsToStart |
| | | * The list of backendID to start. Everything will be started if empty. |
| | | * @param root |
| | | * The configuration of the server's Root backend |
| | | * @throws ConfigException |
| | | * If a critical configuration problem prevents the backend |
| | | * initialization from succeeding. |
| | | */ |
| | | public void initializeBackends(Collection<String> backendIDsToStart, RootCfg root) throws ConfigException |
| | | { |
| | | // Initialize existing backends. |
| | | for (String name : root.listBackends()) |
| | | { |
| | | // Get the handler's configuration. |
| | | // This will decode and validate its properties. |
| | | final BackendCfg backendCfg = root.getBackend(name); |
| | | final String backendID = backendCfg.getBackendId(); |
| | | if (!backendIDsToStart.isEmpty() && !backendIDsToStart.contains(backendID)) |
| | | { |
| | | continue; |
| | | } |
| | | if (DirectoryServer.hasBackend(backendID)) |
| | | { |
| | | // Skip this backend if it is already initialized and registered as available. |
| | | continue; |
| | | } |
| | | |
| | | // Register as a change listener for this backend so that we can be |
| | | // notified when it is disabled or enabled. |
| | | backendCfg.addChangeListener(this); |
| | | |
| | | final DN backendDN = backendCfg.dn(); |
| | | if (!backendCfg.isEnabled()) |
| | | { |
| | | logger.debug(INFO_CONFIG_BACKEND_DISABLED, backendDN); |
| | | continue; |
| | | } |
| | | |
| | | // See if the entry contains an attribute that specifies the class name |
| | | // for the backend implementation. If it does, then load it and make |
| | | // sure that it's a valid backend implementation. There is no such |
| | | // attribute, the specified class cannot be loaded, or it does not |
| | | // contain a valid backend implementation, then log an error and skip it. |
| | | String className = backendCfg.getJavaClass(); |
| | | |
| | | Backend<? extends BackendCfg> backend; |
| | | try |
| | | { |
| | | backend = loadBackendClass(className).newInstance(); |
| | | } |
| | | catch (Exception e) |
| | | { |
| | | logger.traceException(e); |
| | | logger.error(ERR_CONFIG_BACKEND_CANNOT_INSTANTIATE, className, backendDN, stackTraceToSingleLineString(e)); |
| | | continue; |
| | | } |
| | | |
| | | initializeBackend(backend, backendCfg); |
| | | } |
| | | } |
| | | |
| | | private void initializeConfigurationBackend() throws InitializationException |
| | | { |
| | | final ConfigurationBackend configBackend = |
| | | new ConfigurationBackend(serverContext, DirectoryServer.getConfigurationHandler()); |
| | | initializeBackend(configBackend, configBackend.getBackendCfg()); |
| | | } |
| | | |
| | | private void initializeBackend(Backend<? extends BackendCfg> backend, BackendCfg backendCfg) |
| | | { |
| | | ConfigChangeResult ccr = new ConfigChangeResult(); |
| | | initializeBackend(backend, backendCfg, ccr); |
| | | for (LocalizableMessage msg : ccr.getMessages()) |
| | | { |
| | | logger.error(msg); |
| | | } |
| | | } |
| | | |
| | | private void initializeBackend(Backend<? extends BackendCfg> backend, BackendCfg backendCfg, ConfigChangeResult ccr) |
| | | { |
| | | backend.setBackendID(backendCfg.getBackendId()); |
| | | backend.setWritabilityMode(toWritabilityMode(backendCfg.getWritabilityMode())); |
| | | |
| | | if (acquireSharedLock(backend, backendCfg.getBackendId(), ccr) && configureAndOpenBackend(backend, backendCfg, ccr)) |
| | | { |
| | | registerBackend(backend, backendCfg, ccr); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * Acquire a shared lock on this backend. This will prevent operations like LDIF import or restore |
| | | * from occurring while the backend is active. |
| | | */ |
| | | private boolean acquireSharedLock(Backend<?> backend, String backendID, final ConfigChangeResult ccr) |
| | | { |
| | | try |
| | | { |
| | | String lockFile = LockFileManager.getBackendLockFileName(backend); |
| | | StringBuilder failureReason = new StringBuilder(); |
| | | if (!LockFileManager.acquireSharedLock(lockFile, failureReason)) |
| | | { |
| | | cannotAcquireLock(backendID, ccr, failureReason); |
| | | return false; |
| | | } |
| | | return true; |
| | | } |
| | | catch (Exception e) |
| | | { |
| | | logger.traceException(e); |
| | | |
| | | cannotAcquireLock(backendID, ccr, stackTraceToSingleLineString(e)); |
| | | return false; |
| | | } |
| | | } |
| | | |
| | | private void cannotAcquireLock(String backendID, final ConfigChangeResult ccr, CharSequence failureReason) |
| | | { |
| | | LocalizableMessage message = ERR_CONFIG_BACKEND_CANNOT_ACQUIRE_SHARED_LOCK.get(backendID, failureReason); |
| | | logger.error(message); |
| | | |
| | | // FIXME -- Do we need to send an admin alert? |
| | | ccr.setResultCode(ResultCode.CONSTRAINT_VIOLATION); |
| | | ccr.setAdminActionRequired(true); |
| | | ccr.addMessage(message); |
| | | } |
| | | |
| | | private void releaseSharedLock(Backend<?> backend, String backendID) |
| | | { |
| | | try |
| | | { |
| | | String lockFile = LockFileManager.getBackendLockFileName(backend); |
| | | StringBuilder failureReason = new StringBuilder(); |
| | | if (! LockFileManager.releaseLock(lockFile, failureReason)) |
| | | { |
| | | logger.warn(WARN_CONFIG_BACKEND_CANNOT_RELEASE_SHARED_LOCK, backendID, failureReason); |
| | | // FIXME -- Do we need to send an admin alert? |
| | | } |
| | | } |
| | | catch (Exception e2) |
| | | { |
| | | logger.traceException(e2); |
| | | |
| | | logger.warn(WARN_CONFIG_BACKEND_CANNOT_RELEASE_SHARED_LOCK, backendID, stackTraceToSingleLineString(e2)); |
| | | // FIXME -- Do we need to send an admin alert? |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | public boolean isConfigurationChangeAcceptable( |
| | | BackendCfg configEntry, |
| | | List<LocalizableMessage> unacceptableReason) |
| | | { |
| | | DN backendDN = configEntry.dn(); |
| | | |
| | | |
| | | Set<DN> baseDNs = configEntry.getBaseDN(); |
| | | |
| | | // See if the backend is registered with the server. If it is, then |
| | | // see what's changed and whether those changes are acceptable. |
| | | Backend<?> backend = registeredBackends.get(backendDN); |
| | | if (backend != null) |
| | | { |
| | | LinkedHashSet<DN> removedDNs = new LinkedHashSet<>(backend.getBaseDNs()); |
| | | LinkedHashSet<DN> addedDNs = new LinkedHashSet<>(baseDNs); |
| | | Iterator<DN> iterator = removedDNs.iterator(); |
| | | while (iterator.hasNext()) |
| | | { |
| | | DN dn = iterator.next(); |
| | | if (addedDNs.remove(dn)) |
| | | { |
| | | iterator.remove(); |
| | | } |
| | | } |
| | | |
| | | // Copy the directory server's base DN registry and make the |
| | | // requested changes to see if it complains. |
| | | BaseDnRegistry reg = DirectoryServer.copyBaseDnRegistry(); |
| | | for (DN dn : removedDNs) |
| | | { |
| | | try |
| | | { |
| | | reg.deregisterBaseDN(dn); |
| | | } |
| | | catch (DirectoryException de) |
| | | { |
| | | logger.traceException(de); |
| | | |
| | | unacceptableReason.add(de.getMessageObject()); |
| | | return false; |
| | | } |
| | | } |
| | | |
| | | for (DN dn : addedDNs) |
| | | { |
| | | try |
| | | { |
| | | reg.registerBaseDN(dn, backend, false); |
| | | } |
| | | catch (DirectoryException de) |
| | | { |
| | | logger.traceException(de); |
| | | |
| | | unacceptableReason.add(de.getMessageObject()); |
| | | return false; |
| | | } |
| | | } |
| | | } |
| | | else if (configEntry.isEnabled()) |
| | | { |
| | | /* |
| | | * If the backend was not enabled, it has not been registered with directory server, so |
| | | * no listeners will be registered at the lower layers. Verify as it was an add. |
| | | */ |
| | | String className = configEntry.getJavaClass(); |
| | | try |
| | | { |
| | | Class<Backend<BackendCfg>> backendClass = loadBackendClass(className); |
| | | if (! Backend.class.isAssignableFrom(backendClass)) |
| | | { |
| | | unacceptableReason.add(ERR_CONFIG_BACKEND_CLASS_NOT_BACKEND.get(className, backendDN)); |
| | | return false; |
| | | } |
| | | |
| | | Backend<BackendCfg> b = backendClass.newInstance(); |
| | | if (! b.isConfigurationAcceptable(configEntry, unacceptableReason, serverContext)) |
| | | { |
| | | return false; |
| | | } |
| | | } |
| | | catch (Exception e) |
| | | { |
| | | logger.traceException(e); |
| | | unacceptableReason.add( |
| | | ERR_CONFIG_BACKEND_CANNOT_INSTANTIATE.get(className, backendDN, stackTraceToSingleLineString(e))); |
| | | return false; |
| | | } |
| | | } |
| | | |
| | | // If we've gotten to this point, then it is acceptable as far as we are |
| | | // concerned. If it is unacceptable according to the configuration for that |
| | | // backend, then the backend itself will need to make that determination. |
| | | return true; |
| | | } |
| | | |
| | | @Override |
| | | public ConfigChangeResult applyConfigurationChange(BackendCfg cfg) |
| | | { |
| | | DN backendDN = cfg.dn(); |
| | | Backend<? extends BackendCfg> backend = registeredBackends.get(backendDN); |
| | | final ConfigChangeResult ccr = new ConfigChangeResult(); |
| | | |
| | | // See if the entry contains an attribute that indicates whether the |
| | | // backend should be enabled. |
| | | boolean needToEnable = false; |
| | | try |
| | | { |
| | | if (cfg.isEnabled()) |
| | | { |
| | | // The backend is marked as enabled. See if that is already true. |
| | | if (backend == null) |
| | | { |
| | | needToEnable = true; |
| | | } // else already enabled, no need to do anything. |
| | | } |
| | | else |
| | | { |
| | | // The backend is marked as disabled. See if that is already true. |
| | | if (backend != null) |
| | | { |
| | | // It isn't disabled, so we will do so now and deregister it from the |
| | | // Directory Server. |
| | | deregisterBackend(backendDN, backend); |
| | | |
| | | backend.finalizeBackend(); |
| | | |
| | | releaseSharedLock(backend, backend.getBackendID()); |
| | | |
| | | return ccr; |
| | | } // else already disabled, no need to do anything. |
| | | } |
| | | } |
| | | catch (Exception e) |
| | | { |
| | | logger.traceException(e); |
| | | |
| | | ccr.setResultCode(DirectoryServer.getServerErrorResultCode()); |
| | | ccr.addMessage(ERR_CONFIG_BACKEND_UNABLE_TO_DETERMINE_ENABLED_STATE.get(backendDN, |
| | | stackTraceToSingleLineString(e))); |
| | | return ccr; |
| | | } |
| | | |
| | | // See if the entry contains an attribute that specifies the class name |
| | | // for the backend implementation. If it does, then load it and make sure |
| | | // that it's a valid backend implementation. There is no such attribute, |
| | | // the specified class cannot be loaded, or it does not contain a valid |
| | | // backend implementation, then log an error and skip it. |
| | | String className = cfg.getJavaClass(); |
| | | |
| | | // See if this backend is currently active and if so if the name of the class is the same. |
| | | if (backend != null && !className.equals(backend.getClass().getName())) |
| | | { |
| | | // It is not the same. Try to load it and see if it is a valid backend implementation. |
| | | try |
| | | { |
| | | Class<?> backendClass = DirectoryServer.loadClass(className); |
| | | if (Backend.class.isAssignableFrom(backendClass)) |
| | | { |
| | | // It appears to be a valid backend class. We'll return that the |
| | | // change is successful, but indicate that some administrative |
| | | // action is required. |
| | | ccr.addMessage(NOTE_CONFIG_BACKEND_ACTION_REQUIRED_TO_CHANGE_CLASS.get( |
| | | backendDN, backend.getClass().getName(), className)); |
| | | ccr.setAdminActionRequired(true); |
| | | } |
| | | else |
| | | { |
| | | // It is not a valid backend class. This is an error. |
| | | ccr.setResultCode(ResultCode.CONSTRAINT_VIOLATION); |
| | | ccr.addMessage(ERR_CONFIG_BACKEND_CLASS_NOT_BACKEND.get(className, backendDN)); |
| | | } |
| | | return ccr; |
| | | } |
| | | catch (Exception e) |
| | | { |
| | | logger.traceException(e); |
| | | |
| | | ccr.setResultCode(DirectoryServer.getServerErrorResultCode()); |
| | | ccr.addMessage(ERR_CONFIG_BACKEND_CANNOT_INSTANTIATE.get( |
| | | className, backendDN, stackTraceToSingleLineString(e))); |
| | | return ccr; |
| | | } |
| | | } |
| | | |
| | | |
| | | // If we've gotten here, then that should mean that we need to enable the |
| | | // backend. Try to do so. |
| | | if (needToEnable) |
| | | { |
| | | try |
| | | { |
| | | backend = loadBackendClass(className).newInstance(); |
| | | } |
| | | catch (Exception e) |
| | | { |
| | | // It is not a valid backend class. This is an error. |
| | | ccr.setResultCode(ResultCode.CONSTRAINT_VIOLATION); |
| | | ccr.addMessage(ERR_CONFIG_BACKEND_CLASS_NOT_BACKEND.get(className, backendDN)); |
| | | return ccr; |
| | | } |
| | | |
| | | initializeBackend(backend, cfg, ccr); |
| | | return ccr; |
| | | } |
| | | else if (ccr.getResultCode() == ResultCode.SUCCESS && backend != null) |
| | | { |
| | | backend.setWritabilityMode(toWritabilityMode(cfg.getWritabilityMode())); |
| | | } |
| | | |
| | | return ccr; |
| | | } |
| | | |
| | | private boolean registerBackend(Backend<? extends BackendCfg> backend, BackendCfg backendCfg, ConfigChangeResult ccr) |
| | | { |
| | | for (BackendInitializationListener listener : getBackendInitializationListeners()) |
| | | { |
| | | listener.performBackendPreInitializationProcessing(backend); |
| | | } |
| | | |
| | | try |
| | | { |
| | | DirectoryServer.registerBackend(backend); |
| | | } |
| | | catch (Exception e) |
| | | { |
| | | logger.traceException(e); |
| | | |
| | | LocalizableMessage message = |
| | | WARN_CONFIG_BACKEND_CANNOT_REGISTER_BACKEND.get(backendCfg.getBackendId(), getExceptionMessage(e)); |
| | | logger.error(message); |
| | | |
| | | // FIXME -- Do we need to send an admin alert? |
| | | ccr.setResultCode(DirectoryServer.getServerErrorResultCode()); |
| | | ccr.addMessage(message); |
| | | return false; |
| | | } |
| | | |
| | | for (BackendInitializationListener listener : getBackendInitializationListeners()) |
| | | { |
| | | listener.performBackendPostInitializationProcessing(backend); |
| | | } |
| | | |
| | | registeredBackends.put(backendCfg.dn(), backend); |
| | | return true; |
| | | } |
| | | |
| | | @Override |
| | | public boolean isConfigurationAddAcceptable( |
| | | BackendCfg configEntry, |
| | | List<LocalizableMessage> unacceptableReason) |
| | | { |
| | | DN backendDN = configEntry.dn(); |
| | | |
| | | |
| | | // See if the entry contains an attribute that specifies the backend ID. If |
| | | // it does not, then skip it. |
| | | String backendID = configEntry.getBackendId(); |
| | | if (DirectoryServer.hasBackend(backendID)) |
| | | { |
| | | unacceptableReason.add(WARN_CONFIG_BACKEND_DUPLICATE_BACKEND_ID.get(backendDN, backendID)); |
| | | return false; |
| | | } |
| | | |
| | | |
| | | // See if the entry contains an attribute that specifies the set of base DNs |
| | | // for the backend. If it does not, then skip it. |
| | | Set<DN> baseList = configEntry.getBaseDN(); |
| | | DN[] baseDNs = new DN[baseList.size()]; |
| | | baseList.toArray(baseDNs); |
| | | |
| | | |
| | | // See if the entry contains an attribute that specifies the class name |
| | | // for the backend implementation. If it does, then load it and make sure |
| | | // that it's a valid backend implementation. There is no such attribute, |
| | | // the specified class cannot be loaded, or it does not contain a valid |
| | | // backend implementation, then log an error and skip it. |
| | | String className = configEntry.getJavaClass(); |
| | | |
| | | Backend<BackendCfg> backend; |
| | | try |
| | | { |
| | | backend = loadBackendClass(className).newInstance(); |
| | | } |
| | | catch (Exception e) |
| | | { |
| | | logger.traceException(e); |
| | | |
| | | unacceptableReason.add(ERR_CONFIG_BACKEND_CANNOT_INSTANTIATE.get( |
| | | className, backendDN, stackTraceToSingleLineString(e))); |
| | | return false; |
| | | } |
| | | |
| | | |
| | | // Make sure that all of the base DNs are acceptable for use in the server. |
| | | BaseDnRegistry reg = DirectoryServer.copyBaseDnRegistry(); |
| | | for (DN baseDN : baseDNs) |
| | | { |
| | | try |
| | | { |
| | | reg.registerBaseDN(baseDN, backend, false); |
| | | } |
| | | catch (DirectoryException de) |
| | | { |
| | | unacceptableReason.add(de.getMessageObject()); |
| | | return false; |
| | | } |
| | | catch (Exception e) |
| | | { |
| | | unacceptableReason.add(getExceptionMessage(e)); |
| | | return false; |
| | | } |
| | | } |
| | | |
| | | return backend.isConfigurationAcceptable(configEntry, unacceptableReason, serverContext); |
| | | } |
| | | |
| | | @Override |
| | | public ConfigChangeResult applyConfigurationAdd(BackendCfg cfg) |
| | | { |
| | | DN backendDN = cfg.dn(); |
| | | final ConfigChangeResult ccr = new ConfigChangeResult(); |
| | | |
| | | // Register as a change listener for this backend entry so that we will |
| | | // be notified of any changes that may be made to it. |
| | | cfg.addChangeListener(this); |
| | | |
| | | // See if the entry contains an attribute that indicates whether the backend should be enabled. |
| | | // If it does not, or if it is not set to "true", then skip it. |
| | | if (!cfg.isEnabled()) |
| | | { |
| | | // The backend is explicitly disabled. We will log a message to |
| | | // indicate that it won't be enabled and return. |
| | | LocalizableMessage message = INFO_CONFIG_BACKEND_DISABLED.get(backendDN); |
| | | logger.debug(message); |
| | | ccr.addMessage(message); |
| | | return ccr; |
| | | } |
| | | |
| | | |
| | | |
| | | // See if the entry contains an attribute that specifies the backend ID. If |
| | | // it does not, then skip it. |
| | | String backendID = cfg.getBackendId(); |
| | | if (DirectoryServer.hasBackend(backendID)) |
| | | { |
| | | LocalizableMessage message = WARN_CONFIG_BACKEND_DUPLICATE_BACKEND_ID.get(backendDN, backendID); |
| | | logger.warn(message); |
| | | ccr.addMessage(message); |
| | | return ccr; |
| | | } |
| | | |
| | | |
| | | // See if the entry contains an attribute that specifies the class name |
| | | // for the backend implementation. If it does, then load it and make sure |
| | | // that it's a valid backend implementation. There is no such attribute, |
| | | // the specified class cannot be loaded, or it does not contain a valid |
| | | // backend implementation, then log an error and skip it. |
| | | String className = cfg.getJavaClass(); |
| | | |
| | | Backend<? extends BackendCfg> backend; |
| | | try |
| | | { |
| | | backend = loadBackendClass(className).newInstance(); |
| | | } |
| | | catch (Exception e) |
| | | { |
| | | logger.traceException(e); |
| | | |
| | | ccr.setResultCode(DirectoryServer.getServerErrorResultCode()); |
| | | ccr.addMessage(ERR_CONFIG_BACKEND_CANNOT_INSTANTIATE.get( |
| | | className, backendDN, stackTraceToSingleLineString(e))); |
| | | return ccr; |
| | | } |
| | | |
| | | initializeBackend(backend, cfg, ccr); |
| | | return ccr; |
| | | } |
| | | |
| | | private boolean configureAndOpenBackend(Backend<?> backend, BackendCfg cfg, ConfigChangeResult ccr) |
| | | { |
| | | try |
| | | { |
| | | configureAndOpenBackend(backend, cfg); |
| | | return true; |
| | | } |
| | | catch (Exception e) |
| | | { |
| | | logger.traceException(e); |
| | | |
| | | ccr.setResultCode(DirectoryServer.getServerErrorResultCode()); |
| | | ccr.addMessage(ERR_CONFIG_BACKEND_CANNOT_INITIALIZE.get( |
| | | cfg.getJavaClass(), cfg.dn(), stackTraceToSingleLineString(e))); |
| | | |
| | | releaseSharedLock(backend, cfg.getBackendId()); |
| | | return false; |
| | | } |
| | | } |
| | | |
| | | @SuppressWarnings({ "unchecked", "rawtypes" }) |
| | | private void configureAndOpenBackend(Backend backend, BackendCfg cfg) throws ConfigException, InitializationException |
| | | { |
| | | backend.configureBackend(cfg, serverContext); |
| | | backend.openBackend(); |
| | | } |
| | | |
| | | @SuppressWarnings("unchecked") |
| | | private Class<Backend<BackendCfg>> loadBackendClass(String className) throws Exception |
| | | { |
| | | return (Class<Backend<BackendCfg>>) DirectoryServer.loadClass(className); |
| | | } |
| | | |
| | | private WritabilityMode toWritabilityMode(BackendCfgDefn.WritabilityMode writabilityMode) |
| | | { |
| | | switch (writabilityMode) |
| | | { |
| | | case DISABLED: |
| | | return WritabilityMode.DISABLED; |
| | | case ENABLED: |
| | | return WritabilityMode.ENABLED; |
| | | case INTERNAL_ONLY: |
| | | return WritabilityMode.INTERNAL_ONLY; |
| | | default: |
| | | return WritabilityMode.ENABLED; |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | public boolean isConfigurationDeleteAcceptable( |
| | | BackendCfg configEntry, |
| | | List<LocalizableMessage> unacceptableReason) |
| | | { |
| | | DN backendDN = configEntry.dn(); |
| | | |
| | | |
| | | // See if this backend config manager has a backend registered with the |
| | | // provided DN. If not, then we don't care if the entry is deleted. If we |
| | | // do know about it, then that means that it is enabled and we will not |
| | | // allow removing a backend that is enabled. |
| | | Backend<?> backend = registeredBackends.get(backendDN); |
| | | if (backend == null) |
| | | { |
| | | return true; |
| | | } |
| | | |
| | | |
| | | // See if the backend has any subordinate backends. If so, then it is not |
| | | // acceptable to remove it. Otherwise, it should be fine. |
| | | Backend<?>[] subBackends = backend.getSubordinateBackends(); |
| | | if (subBackends != null && subBackends.length != 0) |
| | | { |
| | | unacceptableReason.add(NOTE_CONFIG_BACKEND_CANNOT_REMOVE_BACKEND_WITH_SUBORDINATES.get(backendDN)); |
| | | return false; |
| | | } |
| | | return true; |
| | | } |
| | | |
| | | @Override |
| | | public ConfigChangeResult applyConfigurationDelete(BackendCfg configEntry) |
| | | { |
| | | DN backendDN = configEntry.dn(); |
| | | final ConfigChangeResult ccr = new ConfigChangeResult(); |
| | | |
| | | // See if this backend config manager has a backend registered with the |
| | | // provided DN. If not, then we don't care if the entry is deleted. |
| | | Backend<?> backend = registeredBackends.get(backendDN); |
| | | if (backend == null) |
| | | { |
| | | return ccr; |
| | | } |
| | | |
| | | // See if the backend has any subordinate backends. If so, then it is not |
| | | // acceptable to remove it. Otherwise, it should be fine. |
| | | Backend<?>[] subBackends = backend.getSubordinateBackends(); |
| | | if (subBackends != null && subBackends.length > 0) |
| | | { |
| | | ccr.setResultCode(UNWILLING_TO_PERFORM); |
| | | ccr.addMessage(NOTE_CONFIG_BACKEND_CANNOT_REMOVE_BACKEND_WITH_SUBORDINATES.get(backendDN)); |
| | | return ccr; |
| | | } |
| | | |
| | | deregisterBackend(backendDN, backend); |
| | | |
| | | try |
| | | { |
| | | backend.finalizeBackend(); |
| | | } |
| | | catch (Exception e) |
| | | { |
| | | logger.traceException(e); |
| | | } |
| | | |
| | | configEntry.removeChangeListener(this); |
| | | |
| | | releaseSharedLock(backend, backend.getBackendID()); |
| | | |
| | | return ccr; |
| | | } |
| | | |
| | | private void deregisterBackend(DN backendDN, Backend<?> backend) |
| | | { |
| | | for (BackendInitializationListener listener : getBackendInitializationListeners()) |
| | | { |
| | | listener.performBackendPreFinalizationProcessing(backend); |
| | | } |
| | | |
| | | registeredBackends.remove(backendDN); |
| | | DirectoryServer.deregisterBackend(backend); |
| | | |
| | | for (BackendInitializationListener listener : getBackendInitializationListeners()) |
| | | { |
| | | listener.performBackendPostFinalizationProcessing(backend); |
| | | } |
| | | } |
| | | } |