From 5361332fccb9a4401cf989681d25dee05099b975 Mon Sep 17 00:00:00 2001
From: Matthew Swift <matthew.swift@forgerock.com>
Date: Fri, 15 Feb 2013 23:45:50 +0000
Subject: [PATCH] OPENDJ-757: Add Rest2LDAP gateway Servlet

---
 opendj-sdk/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Rest2LDAP.java |  225 +++++++++++++++++++++++++++++++++++++-------------------
 1 files changed, 148 insertions(+), 77 deletions(-)

diff --git a/opendj-sdk/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Rest2LDAP.java b/opendj-sdk/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Rest2LDAP.java
index 29a091f..d0c11b3 100644
--- a/opendj-sdk/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Rest2LDAP.java
+++ b/opendj-sdk/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Rest2LDAP.java
@@ -21,9 +21,12 @@
 import static org.forgerock.opendj.rest2ldap.ReadOnUpdatePolicy.USE_READ_ENTRY_CONTROLS;
 import static org.forgerock.opendj.rest2ldap.Utils.ensureNotNull;
 
+import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Set;
+import java.util.concurrent.TimeUnit;
 
 import org.forgerock.json.fluent.JsonValue;
 import org.forgerock.json.resource.BadRequestException;
@@ -33,11 +36,15 @@
 import org.forgerock.opendj.ldap.AttributeDescription;
 import org.forgerock.opendj.ldap.ByteString;
 import org.forgerock.opendj.ldap.ConnectionFactory;
+import org.forgerock.opendj.ldap.Connections;
 import org.forgerock.opendj.ldap.DN;
 import org.forgerock.opendj.ldap.Entry;
+import org.forgerock.opendj.ldap.FailoverLoadBalancingAlgorithm;
 import org.forgerock.opendj.ldap.Filter;
+import org.forgerock.opendj.ldap.LDAPConnectionFactory;
 import org.forgerock.opendj.ldap.LinkedAttribute;
 import org.forgerock.opendj.ldap.RDN;
+import org.forgerock.opendj.ldap.RoundRobinLoadBalancingAlgorithm;
 import org.forgerock.opendj.ldap.SearchScope;
 import org.forgerock.opendj.ldap.requests.SearchRequest;
 import org.forgerock.opendj.ldap.schema.AttributeType;
@@ -115,6 +122,80 @@
                     mvccStrategy, new Config(readOnUpdatePolicy, schema), additionalLDAPAttributes);
         }
 
+        /**
+         * Configures the JSON to LDAP mapping using the provided JSON
+         * configuration. The caller is still required to set the connection
+         * factory. The configuration should look like this, excluding the
+         * C-like comments:
+         *
+         * <pre>
+         * {
+         *     // The base DN beneath which LDAP entries are to be found.
+         *     "baseDN" : "ou=people,dc=example,dc=com",
+         *
+         *     // The mechanism which should be used for read resources during updates, must be
+         *     // one of "disabled", "useReadEntryControls", or "useSearch".
+         *     "readOnUpdatePolicy" : "useReadEntryControls",
+         *
+         *     // Additional LDAP attributes which should be included with entries during add (create) operations.
+         *     "additionalLDAPAttributes" : [
+         *         {
+         *             "type" : "objectClass",
+         *             "values" : [
+         *                 "top",
+         *                 "person"
+         *             ]
+         *         }
+         *     ],
+         *
+         *     // The strategy which should be used for deriving LDAP entry names from JSON resources.
+         *     "namingStrategy" : {
+         *         // Option 1) the RDN and resource ID are both derived from a single user attribute in the entry.
+         *         "strategy" : "clientDNNaming",
+         *         "dnAttribute" : "uid"
+         *
+         *         // Option 2) the RDN and resource ID are derived from separate user attributes in the entry.
+         *         "strategy" : "clientNaming",
+         *         "dnAttribute" : "uid",
+         *         "idAttribute" : "mail"
+         *
+         *         // Option 3) the RDN and is derived from a user attribute and the resource ID from an operational
+         *         //           attribute in the entry.
+         *         "strategy" : "serverNaming",
+         *         "dnAttribute" : "uid",
+         *         "idAttribute" : "entryUUID"
+         *     },
+         *
+         *     // The attribute which will be used for performing MVCC.
+         *     "etagAttribute" : "etag",
+         *
+         *     // The JSON to LDAP attribute mappings.
+         *     "attributes" : [
+         *         "schemas"     : { "constant" : [ "urn:scim:schemas:core:1.0" ] },
+         *         "id"          : { "simple"   : { "ldapAttribute" : "uid", "isSingleValued" : true, "isRequired" : true, "writability" : "createOnly" } },
+         *         "rev"         : { "simple"   : { "ldapAttribute" : "etag", "isSingleValued" : true, "writability" : "readOnly" } },
+         *         "userName"    : { "simple"   : { "ldapAttribute" : "mail", "isSingleValued" : true, "writability" : "readOnly" } },
+         *         "displayName" : { "simple"   : { "ldapAttribute" : "cn", "isSingleValued" : true, "isRequired" : true } },
+         *         "name"        : { "object"   : [
+         *             "givenName"  : { "simple"   : { "ldapAttribute" : "givenName", "isSingleValued" : true } },
+         *             "familyName" : { "simple"   : { "ldapAttribute" : "sn", "isSingleValued" : true, "isRequired" : true } },
+         *         ],
+         *         ...
+         *     ]
+         * }
+         * </pre>
+         *
+         * @param configuration
+         *            The JSON configuration.
+         * @return A reference to this builder.
+         * @throws IllegalArgumentException
+         *             If the configuration is invalid.
+         */
+        public Builder configureMapping(final JsonValue configuration)
+                throws IllegalArgumentException {
+            return this;
+        }
+
         public Builder factory(final ConnectionFactory factory) {
             ensureNotNull(factory);
             this.factory = factory;
@@ -318,26 +399,8 @@
     }
 
     /**
-     * Creates a new builder from the provided JSON configuration. See the
-     * documentation of {@link #valueOf(JsonValue)} for a detailed specification
-     * of the JSON configuration.
-     *
-     * @param configuration
-     *            The JSON configuration.
-     * @return A new builder from the provided JSON configuration.
-     * @throws IllegalArgumentException
-     *             If the configuration is invalid.
-     */
-    public static Builder builder(final JsonValue configuration) throws IllegalArgumentException {
-        final Builder builder = builder();
-
-        return builder;
-    }
-
-    /**
-     * Creates a new REST 2 LDAP resource provider from the provided JSON
-     * configuration. The configuration should look like this, excluding the
-     * C-like comments:
+     * Creates a new connection factory using the provided JSON configuration.
+     * The configuration should look like this, excluding the C-like comments:
      *
      * <pre>
      * {
@@ -365,6 +428,10 @@
      *         },
      *     ],
      *
+     *     // Connection pool configuration.
+     *     "connectionPoolSize"       : 10,
+     *     "heartBeatIntervalSeconds" : 30,
+     *
      *     // SSL/TLS configuration (optional and TBD).
      *     "useSSL" : {
      *         // Elect to use StartTLS instead of SSL.
@@ -376,72 +443,53 @@
      *     "authentication" : {
      *         ...
      *     },
-     *
-     *     // The base DN beneath which LDAP entries are to be found.
-     *     "baseDN" : "ou=people,dc=example,dc=com",
-     *
-     *     // The mechanism which should be used for read resources during updates, must be
-     *     // one of "disabled", "useReadEntryControls", or "useSearch".
-     *     "readOnUpdatePolicy" : "useReadEntryControls",
-     *
-     *     // Additional LDAP attributes which should be included with entries during add (create) operations.
-     *     "additionalLDAPAttributes" : [
-     *         {
-     *             "type" : "objectClass",
-     *             "values" : [
-     *                 "top",
-     *                 "person"
-     *             ]
-     *         }
-     *     ],
-     *
-     *     // The strategy which should be used for deriving LDAP entry names from JSON resources.
-     *     "namingStrategy" : {
-     *         // Option 1) the RDN and resource ID are both derived from a single user attribute in the entry.
-     *         "strategy" : "clientDNNaming",
-     *         "dnAttribute" : "uid"
-     *
-     *         // Option 2) the RDN and resource ID are derived from separate user attributes in the entry.
-     *         "strategy" : "clientNaming",
-     *         "dnAttribute" : "uid",
-     *         "idAttribute" : "mail"
-     *
-     *         // Option 3) the RDN and is derived from a user attribute and the resource ID from an operational
-     *         //           attribute in the entry.
-     *         "strategy" : "serverNaming",
-     *         "dnAttribute" : "uid",
-     *         "idAttribute" : "entryUUID"
-     *     },
-     *
-     *     // The attribute which will be used for performing MVCC.
-     *     "etagAttribute" : "etag",
-     *
-     *     // The JSON to LDAP attribute mappings.
-     *     "attributes" : [
-     *         "schemas"     : { "constant" : [ "urn:scim:schemas:core:1.0" ] },
-     *         "id"          : { "simple"   : { "ldapAttribute" : "uid", "isSingleValued" : true, "isRequired" : true, "writability" : "createOnly" } },
-     *         "rev"         : { "simple"   : { "ldapAttribute" : "etag", "isSingleValued" : true, "writability" : "readOnly" } },
-     *         "userName"    : { "simple"   : { "ldapAttribute" : "mail", "isSingleValued" : true, "writability" : "readOnly" } },
-     *         "displayName" : { "simple"   : { "ldapAttribute" : "cn", "isSingleValued" : true, "isRequired" : true } },
-     *         "name"        : { "object"   : [
-     *             "givenName"  : { "simple"   : { "ldapAttribute" : "givenName", "isSingleValued" : true } },
-     *             "familyName" : { "simple"   : { "ldapAttribute" : "sn", "isSingleValued" : true, "isRequired" : true } },
-     *         ],
-     *         ...
-     *     ]
      * }
      * </pre>
      *
      * @param configuration
      *            The JSON configuration.
-     * @return A new REST 2 LDAP resource provider configured using the provided
-     *         JSON configuration.
+     * @return A new connection factory using the provided JSON configuration.
      * @throws IllegalArgumentException
      *             If the configuration is invalid.
      */
-    public static CollectionResourceProvider valueOf(final JsonValue configuration)
+    public static ConnectionFactory connectionFactory(final JsonValue configuration)
             throws IllegalArgumentException {
-        return builder(configuration).build();
+        // Parse pool parameters,
+        final int connectionPoolSize =
+                Math.max(configuration.get("connectionPoolSize").defaultTo(10).asInteger(), 1);
+        final int heartBeatIntervalSeconds =
+                Math.max(configuration.get("heartBeatIntervalSeconds").defaultTo(30).asInteger(), 1);
+
+        // Parse primary data center.
+        final JsonValue primaryLDAPServers = configuration.get("primaryLDAPServers");
+        if (primaryLDAPServers == null || !primaryLDAPServers.isList()
+                || primaryLDAPServers.size() == 0) {
+            throw new IllegalArgumentException("No primaryLDAPServers");
+        }
+        final ConnectionFactory primary =
+                parseLDAPServers(primaryLDAPServers, connectionPoolSize, heartBeatIntervalSeconds);
+
+        // Parse secondary data center(s).
+        final JsonValue secondaryLDAPServers = configuration.get("secondaryLDAPServers");
+        final ConnectionFactory secondary;
+        if (secondaryLDAPServers != null && secondaryLDAPServers.isList()
+                && secondaryLDAPServers.size() != 0) {
+            secondary =
+                    parseLDAPServers(secondaryLDAPServers, connectionPoolSize,
+                            heartBeatIntervalSeconds);
+        } else if (secondaryLDAPServers != null && !secondaryLDAPServers.isList()) {
+            throw new IllegalArgumentException("Invalid secondaryLDAPServers configuration");
+        } else {
+            secondary = null;
+        }
+
+        // Create fail-over.
+        if (secondary != null) {
+            return Connections.newLoadBalancer(new FailoverLoadBalancingAlgorithm(Arrays.asList(
+                    primary, secondary), heartBeatIntervalSeconds, TimeUnit.SECONDS));
+        } else {
+            return primary;
+        }
     }
 
     public static AttributeMapper constant(final Object value) {
@@ -460,6 +508,29 @@
         return simple(AttributeDescription.valueOf(attribute));
     }
 
+    private static ConnectionFactory parseLDAPServers(final JsonValue config,
+            final int connectionPoolSize, final int heartBeatIntervalSeconds) {
+        final List<ConnectionFactory> servers = new ArrayList<ConnectionFactory>(config.size());
+        for (final JsonValue server : config) {
+            final String host = server.get("hostname").required().asString();
+            final int port = server.get("port").required().asInteger();
+            ConnectionFactory factory = new LDAPConnectionFactory(host, port);
+            if (connectionPoolSize > 1) {
+                factory =
+                        Connections.newHeartBeatConnectionFactory(factory,
+                                heartBeatIntervalSeconds, TimeUnit.SECONDS);
+                factory = Connections.newFixedConnectionPool(factory, connectionPoolSize);
+            }
+            servers.add(factory);
+        }
+        if (servers.size() > 1) {
+            return Connections.newLoadBalancer(new RoundRobinLoadBalancingAlgorithm(servers,
+                    heartBeatIntervalSeconds, TimeUnit.SECONDS));
+        } else {
+            return servers.get(0);
+        }
+    }
+
     private Rest2LDAP() {
         // Prevent instantiation.
     }

--
Gitblit v1.10.0