From f2b5fa18b58db09562d03a7d247e21c111e78056 Mon Sep 17 00:00:00 2001
From: Yannick Lecaillez <yannick.lecaillez@forgerock.com>
Date: Mon, 07 Nov 2016 13:59:40 +0000
Subject: [PATCH] OPENDJ-3179: Migrate LDAP Connection Handler to SDK Grizzly transport
---
opendj-grizzly/src/main/java/org/forgerock/opendj/grizzly/LdapCodec.java | 175 +
opendj-core/clirr-ignored-api-changes.xml | 80
opendj-core/src/main/java/com/forgerock/reactive/RxJavaStreams.java | 310 ++
opendj-grizzly/src/main/java/org/forgerock/opendj/grizzly/LdapResponseMessageWriter.java | 77
opendj-grizzly/src/test/java/org/forgerock/opendj/grizzly/GrizzlyLDAPReaderWriterTestCase.java | 17
opendj-grizzly/src/main/java/com/forgerock/opendj/grizzly/ServerConnectionAdaptor.java | 154 +
opendj-grizzly/src/test/java/org/forgerock/opendj/grizzly/GrizzlyLDAPConnectionFactoryTestCase.java | 39
opendj-core/src/main/java/org/forgerock/opendj/ldap/LDAPConnectionFactory.java | 4
opendj-server-legacy/pom.xml | 18
opendj-grizzly/src/test/java/org/forgerock/opendj/grizzly/ASN1BufferReaderTestCase.java | 9
opendj-server-legacy/src/main/java/org/forgerock/opendj/reactive/LDAPClientConnection2.java | 1944 +++++++++++++
opendj-grizzly/src/main/java/org/forgerock/opendj/grizzly/GrizzlyLDAPConnection.java | 20
opendj-server-legacy/src/main/java/org/forgerock/opendj/adapter/server3x/Adapters.java | 2
opendj-grizzly/src/test/java/org/forgerock/opendj/grizzly/GrizzlyLDAPListenerTestCase.java | 75
opendj-server-legacy/src/main/java/org/opends/server/core/LdapEndpointConfigManager.java | 798 +++++
opendj-core/src/main/java/com/forgerock/reactive/Action.java | 29
opendj-grizzly/src/main/java/org/forgerock/opendj/grizzly/LDAPServerFilter.java | 1183 ++-----
opendj-core/src/main/java/org/forgerock/opendj/ldap/CommonLDAPOptions.java | 2
opendj-core/src/test/java/org/forgerock/opendj/ldap/LDAPServer.java | 57
opendj-core/src/main/java/org/forgerock/opendj/ldap/spi/TransportProvider.java | 12
opendj-server-legacy/src/main/java/org/forgerock/opendj/reactive/Route.java | 61
opendj-core/src/main/java/com/forgerock/reactive/Consumer.java | 34
opendj-grizzly/src/main/java/org/forgerock/opendj/grizzly/ASN1BufferWriter.java | 170
opendj-server-legacy/src/main/java/org/forgerock/opendj/reactive/ProxyToHandler.java | 168 +
opendj-grizzly/src/main/java/org/forgerock/opendj/grizzly/LDAPClientFilter.java | 81
opendj-core/src/main/java/com/forgerock/reactive/ReactiveFilter.java | 185 +
opendj-core/src/main/java/com/forgerock/reactive/Single.java | 96
opendj-core/src/main/java/com/forgerock/reactive/Stream.java | 81
opendj-core/src/main/java/org/forgerock/opendj/ldap/LDAPListener.java | 61
opendj-server-legacy/src/main/java/org/forgerock/opendj/reactive/LDAPConnectionHandler2.java | 1116 +++++++
opendj-grizzly/src/main/java/org/forgerock/opendj/grizzly/LDAPBaseFilter.java | 72
opendj-grizzly/src/test/java/org/forgerock/opendj/grizzly/ASN1BufferWriterTestCase.java | 12
opendj-core/src/test/java/org/forgerock/opendj/ldap/spi/BasicTransportProvider.java | 10
opendj-server-legacy/src/main/java/org/forgerock/opendj/reactive/package-info.java | 19
opendj-grizzly/src/main/java/org/forgerock/opendj/grizzly/GrizzlyLDAPListener.java | 118
opendj-core/src/main/java/org/forgerock/opendj/ldap/spi/LDAPListenerImpl.java | 11
opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/Server.java | 4
opendj-grizzly/pom.xml | 4
opendj-core/src/test/java/org/forgerock/opendj/ldap/spi/BasicLDAPListener.java | 17
opendj-grizzly/src/main/java/org/forgerock/opendj/grizzly/DefaultTCPNIOTransport.java | 9
opendj-core/src/main/java/com/forgerock/reactive/package-info.java | 19
opendj-grizzly/src/main/java/org/forgerock/opendj/grizzly/SaslUtils.java | 44
opendj-core/src/test/java/org/forgerock/opendj/io/LDAPReaderWriterTestCase.java | 10
opendj-core/src/main/java/com/forgerock/reactive/ReactiveHandler.java | 41
opendj-grizzly/src/test/java/org/forgerock/opendj/grizzly/ConnectionFactoryTestCase.java | 37
opendj-core/src/main/java/com/forgerock/reactive/Completable.java | 58
opendj-core/src/main/java/org/forgerock/opendj/ldap/LDAPClientContext.java | 72
opendj-server-legacy/src/main/java/org/forgerock/opendj/reactive/Components.java | 271 +
opendj-core/src/main/java/org/forgerock/opendj/ldap/spi/LdapMessages.java | 157 +
opendj-grizzly/src/main/java/org/forgerock/opendj/grizzly/GrizzlyUtils.java | 34
opendj-server-legacy/src/main/java/org/forgerock/opendj/adapter/server3x/Converters.java | 301 ++
opendj-grizzly/src/main/java/com/forgerock/opendj/grizzly/GrizzlyTransportProvider.java | 144
opendj-grizzly/src/main/java/org/forgerock/opendj/grizzly/ASN1BufferReader.java | 44
opendj-grizzly/src/main/java/org/forgerock/opendj/grizzly/SaslFilter.java | 98
opendj-core/pom.xml | 15
55 files changed, 7,341 insertions(+), 1,338 deletions(-)
diff --git a/opendj-core/clirr-ignored-api-changes.xml b/opendj-core/clirr-ignored-api-changes.xml
index e5fa355..76c9630 100644
--- a/opendj-core/clirr-ignored-api-changes.xml
+++ b/opendj-core/clirr-ignored-api-changes.xml
@@ -139,4 +139,84 @@
<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>
diff --git a/opendj-core/pom.xml b/opendj-core/pom.xml
index d8e29ad..34cbab4 100644
--- a/opendj-core/pom.xml
+++ b/opendj-core/pom.xml
@@ -50,6 +50,18 @@
</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>
@@ -113,8 +125,7 @@
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>
diff --git a/opendj-core/src/main/java/com/forgerock/reactive/Action.java b/opendj-core/src/main/java/com/forgerock/reactive/Action.java
new file mode 100644
index 0000000..72c541c
--- /dev/null
+++ b/opendj-core/src/main/java/com/forgerock/reactive/Action.java
@@ -0,0 +1,29 @@
+/*
+ * 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;
+}
diff --git a/opendj-core/src/main/java/com/forgerock/reactive/Completable.java b/opendj-core/src/main/java/com/forgerock/reactive/Completable.java
new file mode 100644
index 0000000..a60df6b
--- /dev/null
+++ b/opendj-core/src/main/java/com/forgerock/reactive/Completable.java
@@ -0,0 +1,58 @@
+/*
+ * 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);
+}
diff --git a/opendj-core/src/main/java/com/forgerock/reactive/Consumer.java b/opendj-core/src/main/java/com/forgerock/reactive/Consumer.java
new file mode 100644
index 0000000..e3c3949
--- /dev/null
+++ b/opendj-core/src/main/java/com/forgerock/reactive/Consumer.java
@@ -0,0 +1,34 @@
+/*
+ * 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;
+}
diff --git a/opendj-core/src/main/java/com/forgerock/reactive/ReactiveFilter.java b/opendj-core/src/main/java/com/forgerock/reactive/ReactiveFilter.java
new file mode 100644
index 0000000..7a23201
--- /dev/null
+++ b/opendj-core/src/main/java/com/forgerock/reactive/ReactiveFilter.java
@@ -0,0 +1,185 @@
+/*
+ * 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);
+ }
+ }
+}
diff --git a/opendj-core/src/main/java/com/forgerock/reactive/ReactiveHandler.java b/opendj-core/src/main/java/com/forgerock/reactive/ReactiveHandler.java
new file mode 100644
index 0000000..849b511
--- /dev/null
+++ b/opendj-core/src/main/java/com/forgerock/reactive/ReactiveHandler.java
@@ -0,0 +1,41 @@
+/*
+ * 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;
+}
diff --git a/opendj-core/src/main/java/com/forgerock/reactive/RxJavaStreams.java b/opendj-core/src/main/java/com/forgerock/reactive/RxJavaStreams.java
new file mode 100644
index 0000000..7831df5
--- /dev/null
+++ b/opendj-core/src/main/java/com/forgerock/reactive/RxJavaStreams.java
@@ -0,0 +1,310 @@
+/*
+ * 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);
+ }
+ }
+}
diff --git a/opendj-core/src/main/java/com/forgerock/reactive/Single.java b/opendj-core/src/main/java/com/forgerock/reactive/Single.java
new file mode 100644
index 0000000..f5d7b25
--- /dev/null
+++ b/opendj-core/src/main/java/com/forgerock/reactive/Single.java
@@ -0,0 +1,96 @@
+/*
+ * 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);
+}
diff --git a/opendj-core/src/main/java/com/forgerock/reactive/Stream.java b/opendj-core/src/main/java/com/forgerock/reactive/Stream.java
new file mode 100644
index 0000000..b19ba37
--- /dev/null
+++ b/opendj-core/src/main/java/com/forgerock/reactive/Stream.java
@@ -0,0 +1,81 @@
+/*
+ * 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();
+}
diff --git a/opendj-core/src/main/java/com/forgerock/reactive/package-info.java b/opendj-core/src/main/java/com/forgerock/reactive/package-info.java
new file mode 100644
index 0000000..9ab2b62
--- /dev/null
+++ b/opendj-core/src/main/java/com/forgerock/reactive/package-info.java
@@ -0,0 +1,19 @@
+/*
+ * 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;
+
diff --git a/opendj-core/src/main/java/org/forgerock/opendj/ldap/CommonLDAPOptions.java b/opendj-core/src/main/java/org/forgerock/opendj/ldap/CommonLDAPOptions.java
index 2354990..1ea5b1d 100644
--- a/opendj-core/src/main/java/org/forgerock/opendj/ldap/CommonLDAPOptions.java
+++ b/opendj-core/src/main/java/org/forgerock/opendj/ldap/CommonLDAPOptions.java
@@ -24,7 +24,7 @@
/**
* 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}.
diff --git a/opendj-core/src/main/java/org/forgerock/opendj/ldap/LDAPClientContext.java b/opendj-core/src/main/java/org/forgerock/opendj/ldap/LDAPClientContext.java
index 7d5b69c..9bb905b 100644
--- a/opendj-core/src/main/java/org/forgerock/opendj/ldap/LDAPClientContext.java
+++ b/opendj-core/src/main/java/org/forgerock/opendj/ldap/LDAPClientContext.java
@@ -12,15 +12,16 @@
* 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;
@@ -32,6 +33,29 @@
*/
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>
@@ -56,7 +80,7 @@
* 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.
@@ -123,40 +147,20 @@
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);
}
diff --git a/opendj-core/src/main/java/org/forgerock/opendj/ldap/LDAPConnectionFactory.java b/opendj-core/src/main/java/org/forgerock/opendj/ldap/LDAPConnectionFactory.java
index dac0421..599069e 100644
--- a/opendj-core/src/main/java/org/forgerock/opendj/ldap/LDAPConnectionFactory.java
+++ b/opendj-core/src/main/java/org/forgerock/opendj/ldap/LDAPConnectionFactory.java
@@ -17,8 +17,8 @@
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.*;
@@ -502,7 +502,7 @@
connectException = newHeartBeatTimeoutError();
} else {
connectException = newLdapException(ResultCode.CLIENT_SIDE_SERVER_DOWN,
- ERR_CONNECTION_UNEXPECTED.get(e),
+ HBCF_HEARTBEAT_FAILED.get(),
e);
}
if (promise.tryHandleException(connectException)) {
diff --git a/opendj-core/src/main/java/org/forgerock/opendj/ldap/LDAPListener.java b/opendj-core/src/main/java/org/forgerock/opendj/ldap/LDAPListener.java
index 48ff6bf..013cd9d 100644
--- a/opendj-core/src/main/java/org/forgerock/opendj/ldap/LDAPListener.java
+++ b/opendj-core/src/main/java/org/forgerock/opendj/ldap/LDAPListener.java
@@ -12,14 +12,16 @@
* 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;
@@ -97,6 +99,12 @@
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.
*/
@@ -145,9 +153,7 @@
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);
}
/**
@@ -188,12 +194,14 @@
* 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);
}
/**
@@ -241,10 +249,7 @@
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. */
@@ -254,42 +259,12 @@
}
/**
- * 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();
}
/**
diff --git a/opendj-core/src/main/java/org/forgerock/opendj/ldap/spi/LDAPListenerImpl.java b/opendj-core/src/main/java/org/forgerock/opendj/ldap/spi/LDAPListenerImpl.java
index 673ec69..c1b326e 100644
--- a/opendj-core/src/main/java/org/forgerock/opendj/ldap/spi/LDAPListenerImpl.java
+++ b/opendj-core/src/main/java/org/forgerock/opendj/ldap/spi/LDAPListenerImpl.java
@@ -11,12 +11,13 @@
* 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}.
@@ -31,11 +32,11 @@
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
diff --git a/opendj-core/src/main/java/org/forgerock/opendj/ldap/spi/LdapMessages.java b/opendj-core/src/main/java/org/forgerock/opendj/ldap/spi/LdapMessages.java
new file mode 100644
index 0000000..bf79eed
--- /dev/null
+++ b/opendj-core/src/main/java/org/forgerock/opendj/ldap/spi/LdapMessages.java
@@ -0,0 +1,157 @@
+/*
+ * 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;
+ }
+ }
+}
diff --git a/opendj-core/src/main/java/org/forgerock/opendj/ldap/spi/TransportProvider.java b/opendj-core/src/main/java/org/forgerock/opendj/ldap/spi/TransportProvider.java
index ddfe3c5..90949eb 100644
--- a/opendj-core/src/main/java/org/forgerock/opendj/ldap/spi/TransportProvider.java
+++ b/opendj-core/src/main/java/org/forgerock/opendj/ldap/spi/TransportProvider.java
@@ -11,12 +11,13 @@
* 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;
@@ -51,8 +52,8 @@
/**
* 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.
@@ -63,8 +64,7 @@
* 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;
-
}
diff --git a/opendj-core/src/test/java/org/forgerock/opendj/io/LDAPReaderWriterTestCase.java b/opendj-core/src/test/java/org/forgerock/opendj/io/LDAPReaderWriterTestCase.java
index 5ba17fe..cc0fcc1 100644
--- a/opendj-core/src/test/java/org/forgerock/opendj/io/LDAPReaderWriterTestCase.java
+++ b/opendj-core/src/test/java/org/forgerock/opendj/io/LDAPReaderWriterTestCase.java
@@ -11,7 +11,7 @@
* 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;
@@ -487,8 +487,7 @@
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);
}
@@ -503,8 +502,7 @@
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);
}
diff --git a/opendj-core/src/test/java/org/forgerock/opendj/ldap/LDAPServer.java b/opendj-core/src/test/java/org/forgerock/opendj/ldap/LDAPServer.java
index be59f39..8edc028 100644
--- a/opendj-core/src/test/java/org/forgerock/opendj/ldap/LDAPServer.java
+++ b/opendj-core/src/test/java/org/forgerock/opendj/ldap/LDAPServer.java
@@ -17,6 +17,10 @@
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;
@@ -27,6 +31,7 @@
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;
@@ -62,14 +67,10 @@
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
@@ -225,7 +226,9 @@
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)
@@ -264,38 +267,7 @@
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 {
@@ -423,8 +395,11 @@
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);
}
}
@@ -568,6 +543,6 @@
if (!isRunning) {
throw new IllegalStateException("Server is not running");
}
- return listener.getSocketAddress();
+ return (InetSocketAddress) listener.getSocketAddresses().iterator().next();
}
}
diff --git a/opendj-core/src/test/java/org/forgerock/opendj/ldap/spi/BasicLDAPListener.java b/opendj-core/src/test/java/org/forgerock/opendj/ldap/spi/BasicLDAPListener.java
index 5c0c305..080cbd6 100644
--- a/opendj-core/src/test/java/org/forgerock/opendj/ldap/spi/BasicLDAPListener.java
+++ b/opendj-core/src/test/java/org/forgerock/opendj/ldap/spi/BasicLDAPListener.java
@@ -11,13 +11,14 @@
* 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;
@@ -28,7 +29,7 @@
*/
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.
@@ -43,11 +44,11 @@
* @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
@@ -56,15 +57,15 @@
}
@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();
}
diff --git a/opendj-core/src/test/java/org/forgerock/opendj/ldap/spi/BasicTransportProvider.java b/opendj-core/src/test/java/org/forgerock/opendj/ldap/spi/BasicTransportProvider.java
index 1e5518d..d9c03b3 100644
--- a/opendj-core/src/test/java/org/forgerock/opendj/ldap/spi/BasicTransportProvider.java
+++ b/opendj-core/src/test/java/org/forgerock/opendj/ldap/spi/BasicTransportProvider.java
@@ -16,7 +16,8 @@
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;
@@ -43,10 +44,9 @@
}
@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
diff --git a/opendj-grizzly/pom.xml b/opendj-grizzly/pom.xml
index 90333fb..85d9a2d 100644
--- a/opendj-grizzly/pom.xml
+++ b/opendj-grizzly/pom.xml
@@ -69,6 +69,10 @@
<artifactId>forgerock-build-tools</artifactId>
<scope>test</scope>
</dependency>
+ <dependency>
+ <groupId>org.forgerock.http</groupId>
+ <artifactId>chf-http-core</artifactId>
+ </dependency>
</dependencies>
diff --git a/opendj-grizzly/src/main/java/com/forgerock/opendj/grizzly/GrizzlyTransportProvider.java b/opendj-grizzly/src/main/java/com/forgerock/opendj/grizzly/GrizzlyTransportProvider.java
index bde6ddc..e67b849 100644
--- a/opendj-grizzly/src/main/java/com/forgerock/opendj/grizzly/GrizzlyTransportProvider.java
+++ b/opendj-grizzly/src/main/java/com/forgerock/opendj/grizzly/GrizzlyTransportProvider.java
@@ -11,26 +11,62 @@
* 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) {
@@ -38,10 +74,19 @@
}
@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
@@ -49,4 +94,91 @@
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)));
+ }
+ };
+ }
}
diff --git a/opendj-grizzly/src/main/java/com/forgerock/opendj/grizzly/ServerConnectionAdaptor.java b/opendj-grizzly/src/main/java/com/forgerock/opendj/grizzly/ServerConnectionAdaptor.java
new file mode 100644
index 0000000..9318052
--- /dev/null
+++ b/opendj-grizzly/src/main/java/com/forgerock/opendj/grizzly/ServerConnectionAdaptor.java
@@ -0,0 +1,154 @@
+/*
+ * 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);
+ }
+ }
+}
diff --git a/opendj-grizzly/src/main/java/org/forgerock/opendj/grizzly/ASN1BufferReader.java b/opendj-grizzly/src/main/java/org/forgerock/opendj/grizzly/ASN1BufferReader.java
index 1226bde..2618456 100644
--- a/opendj-grizzly/src/main/java/org/forgerock/opendj/grizzly/ASN1BufferReader.java
+++ b/opendj-grizzly/src/main/java/org/forgerock/opendj/grizzly/ASN1BufferReader.java
@@ -19,6 +19,7 @@
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;
@@ -29,9 +30,6 @@
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 {
@@ -127,15 +125,16 @@
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
@@ -147,11 +146,10 @@
* @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);
}
/**
@@ -162,7 +160,7 @@
*/
@Override
public void close() throws IOException {
- buffer.dispose();
+ // Nothing to do
}
/**
@@ -388,26 +386,20 @@
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);
@@ -453,12 +445,16 @@
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;
}
/**
diff --git a/opendj-grizzly/src/main/java/org/forgerock/opendj/grizzly/ASN1BufferWriter.java b/opendj-grizzly/src/main/java/org/forgerock/opendj/grizzly/ASN1BufferWriter.java
index c50e864..1f5169d 100644
--- a/opendj-grizzly/src/main/java/org/forgerock/opendj/grizzly/ASN1BufferWriter.java
+++ b/opendj-grizzly/src/main/java/org/forgerock/opendj/grizzly/ASN1BufferWriter.java
@@ -16,10 +16,9 @@
*/
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;
@@ -30,7 +29,7 @@
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;
@@ -46,7 +45,9 @@
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;
}
@@ -67,39 +68,13 @@
}
@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);
}
}
@@ -118,7 +93,7 @@
child = new ChildSequenceBuffer();
child.parent = this;
}
- outBuffer.ensureAdditionalCapacity(1);
+ ensureAdditionalCapacity(1);
outBuffer.put(type);
child.buffer.clear();
return child;
@@ -126,14 +101,21 @@
@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);
}
}
@@ -145,33 +127,44 @@
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));
+ }
}
/**
@@ -201,7 +194,7 @@
@Override
public void recycle() {
sequenceBuffer = rootBuffer;
- outBuffer.clear();
+ outBuffer = memoryManager.allocate(BUFFER_INIT_SIZE);
}
@Override
@@ -210,7 +203,9 @@
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;
}
@@ -238,27 +233,35 @@
|| ((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;
}
@@ -270,20 +273,26 @@
|| ((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);
@@ -291,7 +300,9 @@
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);
@@ -300,7 +311,9 @@
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);
@@ -310,7 +323,9 @@
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);
@@ -321,7 +336,9 @@
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));
@@ -332,7 +349,9 @@
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;
}
@@ -342,7 +361,9 @@
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;
}
@@ -353,7 +374,9 @@
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;
}
@@ -362,12 +385,15 @@
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;
}
@@ -384,7 +410,9 @@
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;
}
@@ -393,7 +421,9 @@
// 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;
}
@@ -404,8 +434,8 @@
return writeStartSequence(type);
}
- Buffer getBuffer() {
- outBuffer.usable = false;
+ public Buffer getBuffer() {
+ outBuffer.allowBufferDispose(true);
return outBuffer.flip();
}
diff --git a/opendj-grizzly/src/main/java/org/forgerock/opendj/grizzly/DefaultTCPNIOTransport.java b/opendj-grizzly/src/main/java/org/forgerock/opendj/grizzly/DefaultTCPNIOTransport.java
index beb4102..0332452 100644
--- a/opendj-grizzly/src/main/java/org/forgerock/opendj/grizzly/DefaultTCPNIOTransport.java
+++ b/opendj-grizzly/src/main/java/org/forgerock/opendj/grizzly/DefaultTCPNIOTransport.java
@@ -12,7 +12,7 @@
* 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;
@@ -66,12 +66,7 @@
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) {
diff --git a/opendj-grizzly/src/main/java/org/forgerock/opendj/grizzly/GrizzlyLDAPConnection.java b/opendj-grizzly/src/main/java/org/forgerock/opendj/grizzly/GrizzlyLDAPConnection.java
index 42c33de..cffc311 100644
--- a/opendj-grizzly/src/main/java/org/forgerock/opendj/grizzly/GrizzlyLDAPConnection.java
+++ b/opendj-grizzly/src/main/java/org/forgerock/opendj/grizzly/GrizzlyLDAPConnection.java
@@ -186,7 +186,7 @@
}
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);
@@ -212,7 +212,7 @@
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);
@@ -285,7 +285,7 @@
}
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.
@@ -333,7 +333,7 @@
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);
@@ -363,7 +363,7 @@
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);
@@ -409,7 +409,7 @@
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);
@@ -454,7 +454,7 @@
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);
@@ -484,7 +484,7 @@
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);
@@ -524,7 +524,7 @@
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);
@@ -668,7 +668,7 @@
* 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);
diff --git a/opendj-grizzly/src/main/java/org/forgerock/opendj/grizzly/GrizzlyLDAPListener.java b/opendj-grizzly/src/main/java/org/forgerock/opendj/grizzly/GrizzlyLDAPListener.java
index caaf571..1de0ea0 100644
--- a/opendj-grizzly/src/main/java/org/forgerock/opendj/grizzly/GrizzlyLDAPListener.java
+++ b/opendj-grizzly/src/main/java/org/forgerock/opendj/grizzly/GrizzlyLDAPListener.java
@@ -12,30 +12,40 @@
* 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.
@@ -43,80 +53,76 @@
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();
@@ -129,23 +135,19 @@
}
@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;
}
diff --git a/opendj-grizzly/src/main/java/org/forgerock/opendj/grizzly/GrizzlyUtils.java b/opendj-grizzly/src/main/java/org/forgerock/opendj/grizzly/GrizzlyUtils.java
index 2881ab2..0cd00f2 100644
--- a/opendj-grizzly/src/main/java/org/forgerock/opendj/grizzly/GrizzlyUtils.java
+++ b/opendj-grizzly/src/main/java/org/forgerock/opendj/grizzly/GrizzlyUtils.java
@@ -11,7 +11,7 @@
* 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;
@@ -31,7 +31,9 @@
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;
@@ -65,11 +67,14 @@
* 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();
}
}
@@ -113,17 +118,14 @@
*/
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();
}
/**
@@ -141,7 +143,7 @@
*/
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);
}
@@ -155,10 +157,10 @@
* @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;
diff --git a/opendj-grizzly/src/main/java/org/forgerock/opendj/grizzly/LDAPBaseFilter.java b/opendj-grizzly/src/main/java/org/forgerock/opendj/grizzly/LDAPBaseFilter.java
index 3f35af3..d6230fb 100644
--- a/opendj-grizzly/src/main/java/org/forgerock/opendj/grizzly/LDAPBaseFilter.java
+++ b/opendj-grizzly/src/main/java/org/forgerock/opendj/grizzly/LDAPBaseFilter.java
@@ -11,25 +11,15 @@
* 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 {
@@ -58,64 +48,4 @@
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);
-
}
diff --git a/opendj-grizzly/src/main/java/org/forgerock/opendj/grizzly/LDAPClientFilter.java b/opendj-grizzly/src/main/java/org/forgerock/opendj/grizzly/LDAPClientFilter.java
index b5b308c..61af8a3 100644
--- a/opendj-grizzly/src/main/java/org/forgerock/opendj/grizzly/LDAPClientFilter.java
+++ b/opendj-grizzly/src/main/java/org/forgerock/opendj/grizzly/LDAPClientFilter.java
@@ -17,13 +17,19 @@
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;
@@ -53,6 +59,7 @@
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;
@@ -60,10 +67,6 @@
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.
@@ -75,36 +78,14 @@
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,
@@ -139,7 +120,8 @@
// 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();
@@ -435,6 +417,32 @@
}
@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);
@@ -445,15 +453,6 @@
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.
@@ -466,15 +465,11 @@
* @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);
diff --git a/opendj-grizzly/src/main/java/org/forgerock/opendj/grizzly/LDAPServerFilter.java b/opendj-grizzly/src/main/java/org/forgerock/opendj/grizzly/LDAPServerFilter.java
index fc3fa12..787a336 100644
--- a/opendj-grizzly/src/main/java/org/forgerock/opendj/grizzly/LDAPServerFilter.java
+++ b/opendj-grizzly/src/main/java/org/forgerock/opendj/grizzly/LDAPServerFilter.java
@@ -16,584 +16,80 @@
*/
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
@@ -620,259 +116,424 @@
{ "_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));
+ }
}
}
diff --git a/opendj-grizzly/src/main/java/org/forgerock/opendj/grizzly/LdapCodec.java b/opendj-grizzly/src/main/java/org/forgerock/opendj/grizzly/LdapCodec.java
new file mode 100644
index 0000000..730a7b1
--- /dev/null
+++ b/opendj-grizzly/src/main/java/org/forgerock/opendj/grizzly/LdapCodec.java
@@ -0,0 +1,175 @@
+/*
+ * 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();
+ }
+}
diff --git a/opendj-grizzly/src/main/java/org/forgerock/opendj/grizzly/LdapResponseMessageWriter.java b/opendj-grizzly/src/main/java/org/forgerock/opendj/grizzly/LdapResponseMessageWriter.java
new file mode 100644
index 0000000..9e3fa30
--- /dev/null
+++ b/opendj-grizzly/src/main/java/org/forgerock/opendj/grizzly/LdapResponseMessageWriter.java
@@ -0,0 +1,77 @@
+/*
+ * 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();
+ }
+}
diff --git a/opendj-grizzly/src/main/java/org/forgerock/opendj/grizzly/SaslFilter.java b/opendj-grizzly/src/main/java/org/forgerock/opendj/grizzly/SaslFilter.java
new file mode 100644
index 0000000..f188de3
--- /dev/null
+++ b/opendj-grizzly/src/main/java/org/forgerock/opendj/grizzly/SaslFilter.java
@@ -0,0 +1,98 @@
+/*
+ * 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();
+ }
+ }
+}
diff --git a/opendj-grizzly/src/main/java/org/forgerock/opendj/grizzly/SaslUtils.java b/opendj-grizzly/src/main/java/org/forgerock/opendj/grizzly/SaslUtils.java
new file mode 100644
index 0000000..5cdfde1
--- /dev/null
+++ b/opendj-grizzly/src/main/java/org/forgerock/opendj/grizzly/SaslUtils.java
@@ -0,0 +1,44 @@
+/*
+ * 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() {
+ }
+}
diff --git a/opendj-grizzly/src/test/java/org/forgerock/opendj/grizzly/ASN1BufferReaderTestCase.java b/opendj-grizzly/src/test/java/org/forgerock/opendj/grizzly/ASN1BufferReaderTestCase.java
index c5cd7af..81ff078 100644
--- a/opendj-grizzly/src/test/java/org/forgerock/opendj/grizzly/ASN1BufferReaderTestCase.java
+++ b/opendj-grizzly/src/test/java/org/forgerock/opendj/grizzly/ASN1BufferReaderTestCase.java
@@ -12,7 +12,7 @@
* 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;
@@ -23,7 +23,6 @@
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.
@@ -31,10 +30,6 @@
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)));
}
}
diff --git a/opendj-grizzly/src/test/java/org/forgerock/opendj/grizzly/ASN1BufferWriterTestCase.java b/opendj-grizzly/src/test/java/org/forgerock/opendj/grizzly/ASN1BufferWriterTestCase.java
index bedc9f4..d82baa4 100644
--- a/opendj-grizzly/src/test/java/org/forgerock/opendj/grizzly/ASN1BufferWriterTestCase.java
+++ b/opendj-grizzly/src/test/java/org/forgerock/opendj/grizzly/ASN1BufferWriterTestCase.java
@@ -12,7 +12,7 @@
* 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;
@@ -27,13 +27,15 @@
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 {
@@ -45,11 +47,7 @@
@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
diff --git a/opendj-grizzly/src/test/java/org/forgerock/opendj/grizzly/ConnectionFactoryTestCase.java b/opendj-grizzly/src/test/java/org/forgerock/opendj/grizzly/ConnectionFactoryTestCase.java
index a7ac5ed..d41fd1c 100644
--- a/opendj-grizzly/src/test/java/org/forgerock/opendj/grizzly/ConnectionFactoryTestCase.java
+++ b/opendj-grizzly/src/test/java/org/forgerock/opendj/grizzly/ConnectionFactoryTestCase.java
@@ -12,7 +12,7 @@
* 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;
@@ -20,26 +20,17 @@
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;
@@ -148,7 +139,7 @@
// 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(),
@@ -255,7 +246,7 @@
*
* @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();
@@ -555,8 +546,9 @@
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;
@@ -638,8 +630,9 @@
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 {
diff --git a/opendj-grizzly/src/test/java/org/forgerock/opendj/grizzly/GrizzlyLDAPConnectionFactoryTestCase.java b/opendj-grizzly/src/test/java/org/forgerock/opendj/grizzly/GrizzlyLDAPConnectionFactoryTestCase.java
index 349e2ca..0546413 100644
--- a/opendj-grizzly/src/test/java/org/forgerock/opendj/grizzly/GrizzlyLDAPConnectionFactoryTestCase.java
+++ b/opendj-grizzly/src/test/java/org/forgerock/opendj/grizzly/GrizzlyLDAPConnectionFactoryTestCase.java
@@ -11,12 +11,24 @@
* 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;
@@ -27,16 +39,16 @@
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;
@@ -59,15 +71,6 @@
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.
*/
@@ -97,9 +100,11 @@
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;
diff --git a/opendj-grizzly/src/test/java/org/forgerock/opendj/grizzly/GrizzlyLDAPListenerTestCase.java b/opendj-grizzly/src/test/java/org/forgerock/opendj/grizzly/GrizzlyLDAPListenerTestCase.java
index cd22bc3..a893b367 100644
--- a/opendj-grizzly/src/test/java/org/forgerock/opendj/grizzly/GrizzlyLDAPListenerTestCase.java
+++ b/opendj-grizzly/src/test/java/org/forgerock/opendj/grizzly/GrizzlyLDAPListenerTestCase.java
@@ -238,13 +238,12 @@
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();
@@ -268,8 +267,9 @@
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.
@@ -285,8 +285,8 @@
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 =
@@ -311,13 +311,14 @@
};
+ 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();
@@ -349,6 +350,8 @@
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.
@@ -364,8 +367,8 @@
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 =
@@ -400,11 +403,14 @@
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());
@@ -438,8 +444,9 @@
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();
@@ -464,8 +471,8 @@
try {
lcf =
new LDAPConnectionFactory(
- onlineServerListener.getHostName(),
- onlineServerListener.getPort());
+ onlineServerAddr.getHostName(),
+ onlineServerAddr.getPort());
lcf.getConnection().close();
} catch (final Exception e) {
// Unexpected.
@@ -491,8 +498,8 @@
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();
@@ -521,8 +528,9 @@
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() {
@@ -546,8 +554,8 @@
} 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) {
@@ -565,15 +573,16 @@
}
};
+ 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());
@@ -601,17 +610,18 @@
* @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
@@ -664,12 +674,13 @@
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) {
@@ -684,8 +695,8 @@
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");
diff --git a/opendj-grizzly/src/test/java/org/forgerock/opendj/grizzly/GrizzlyLDAPReaderWriterTestCase.java b/opendj-grizzly/src/test/java/org/forgerock/opendj/grizzly/GrizzlyLDAPReaderWriterTestCase.java
index 73c7bae..5c7315b 100644
--- a/opendj-grizzly/src/test/java/org/forgerock/opendj/grizzly/GrizzlyLDAPReaderWriterTestCase.java
+++ b/opendj-grizzly/src/test/java/org/forgerock/opendj/grizzly/GrizzlyLDAPReaderWriterTestCase.java
@@ -11,17 +11,20 @@
* 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;
/**
@@ -32,7 +35,7 @@
@Override
protected LDAPWriter<? extends ASN1Writer> getLDAPWriter() {
- return GrizzlyUtils.getWriter();
+ return GrizzlyUtils.getWriter(MemoryManager.DEFAULT_MEMORY_MANAGER);
}
@Override
@@ -41,11 +44,9 @@
}
@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));
}
-
}
diff --git a/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/Server.java b/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/Server.java
index a422e84..349611b 100644
--- a/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/Server.java
+++ b/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/Server.java
@@ -12,7 +12,7 @@
* 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;
@@ -111,7 +111,7 @@
@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);
}
};
diff --git a/opendj-server-legacy/pom.xml b/opendj-server-legacy/pom.xml
index 940f2f8..eb280dd 100644
--- a/opendj-server-legacy/pom.xml
+++ b/opendj-server-legacy/pom.xml
@@ -163,16 +163,6 @@
<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>
@@ -680,15 +670,9 @@
<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>
diff --git a/opendj-server-legacy/src/main/java/org/forgerock/opendj/adapter/server3x/Adapters.java b/opendj-server-legacy/src/main/java/org/forgerock/opendj/adapter/server3x/Adapters.java
index b11287f..7cc9d78 100644
--- a/opendj-server-legacy/src/main/java/org/forgerock/opendj/adapter/server3x/Adapters.java
+++ b/opendj-server-legacy/src/main/java/org/forgerock/opendj/adapter/server3x/Adapters.java
@@ -146,7 +146,7 @@
@Override
public void handleInternalSearchEntry(InternalSearchOperation searchOperation,
SearchResultEntry searchEntry) throws DirectoryException {
- handler.handleEntry(from(searchEntry));
+ handler.handleEntry(partiallyWrap(searchEntry));
}
};
diff --git a/opendj-server-legacy/src/main/java/org/forgerock/opendj/adapter/server3x/Converters.java b/opendj-server-legacy/src/main/java/org/forgerock/opendj/adapter/server3x/Converters.java
index 4625c11..5a3560c 100644
--- a/opendj-server-legacy/src/main/java/org/forgerock/opendj/adapter/server3x/Converters.java
+++ b/opendj-server-legacy/src/main/java/org/forgerock/opendj/adapter/server3x/Converters.java
@@ -15,12 +15,11 @@
*/
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;
@@ -33,21 +32,28 @@
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;
@@ -63,6 +69,8 @@
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 {
@@ -451,6 +459,130 @@
}
/**
+ * 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}.
@@ -494,6 +626,163 @@
/**
* 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}.
*
diff --git a/opendj-server-legacy/src/main/java/org/forgerock/opendj/reactive/Components.java b/opendj-server-legacy/src/main/java/org/forgerock/opendj/reactive/Components.java
new file mode 100644
index 0000000..5bea25b
--- /dev/null
+++ b/opendj-server-legacy/src/main/java/org/forgerock/opendj/reactive/Components.java
@@ -0,0 +1,271 @@
+/*
+ * 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
+ }
+}
diff --git a/opendj-server-legacy/src/main/java/org/forgerock/opendj/reactive/LDAPClientConnection2.java b/opendj-server-legacy/src/main/java/org/forgerock/opendj/reactive/LDAPClientConnection2.java
new file mode 100644
index 0000000..2bff320
--- /dev/null
+++ b/opendj-server-legacy/src/main/java/org/forgerock/opendj/reactive/LDAPClientConnection2.java
@@ -0,0 +1,1944 @@
+/*
+ * 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;
+ }
+}
diff --git a/opendj-server-legacy/src/main/java/org/forgerock/opendj/reactive/LDAPConnectionHandler2.java b/opendj-server-legacy/src/main/java/org/forgerock/opendj/reactive/LDAPConnectionHandler2.java
new file mode 100644
index 0000000..af7326a
--- /dev/null
+++ b/opendj-server-legacy/src/main/java/org/forgerock/opendj/reactive/LDAPConnectionHandler2.java
@@ -0,0 +1,1116 @@
+/*
+ * 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();
+ }
+ }
+ }
+}
diff --git a/opendj-server-legacy/src/main/java/org/forgerock/opendj/reactive/ProxyToHandler.java b/opendj-server-legacy/src/main/java/org/forgerock/opendj/reactive/ProxyToHandler.java
new file mode 100644
index 0000000..b468952
--- /dev/null
+++ b/opendj-server-legacy/src/main/java/org/forgerock/opendj/reactive/ProxyToHandler.java
@@ -0,0 +1,168 @@
+/*
+ * 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);
+ }
+ }
+}
diff --git a/opendj-server-legacy/src/main/java/org/forgerock/opendj/reactive/Route.java b/opendj-server-legacy/src/main/java/org/forgerock/opendj/reactive/Route.java
new file mode 100644
index 0000000..3cbe6a0
--- /dev/null
+++ b/opendj-server-legacy/src/main/java/org/forgerock/opendj/reactive/Route.java
@@ -0,0 +1,61 @@
+/*
+ * 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");
+ }
+}
diff --git a/opendj-server-legacy/src/main/java/org/forgerock/opendj/reactive/package-info.java b/opendj-server-legacy/src/main/java/org/forgerock/opendj/reactive/package-info.java
new file mode 100644
index 0000000..727489a
--- /dev/null
+++ b/opendj-server-legacy/src/main/java/org/forgerock/opendj/reactive/package-info.java
@@ -0,0 +1,19 @@
+/*
+ * 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;
+
diff --git a/opendj-server-legacy/src/main/java/org/opends/server/core/LdapEndpointConfigManager.java b/opendj-server-legacy/src/main/java/org/opends/server/core/LdapEndpointConfigManager.java
new file mode 100644
index 0000000..32fdbf8
--- /dev/null
+++ b/opendj-server-legacy/src/main/java/org/opends/server/core/LdapEndpointConfigManager.java
@@ -0,0 +1,798 @@
+/*
+ * 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);
+ }
+ }
+}
--
Gitblit v1.10.0