From 2a98df5779f11957712ea3b7a7a10a07f51558ba Mon Sep 17 00:00:00 2001
From: Matthew Swift <matthew.swift@forgerock.com>
Date: Thu, 21 Feb 2013 22:55:52 +0000
Subject: [PATCH] OPENDJ-757: Add Rest2LDAP gateway Servlet

---
 opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/ReadOnUpdatePolicy.java             |    4 
 opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Rest2LDAP.java                      |  162 +++++++++++++++++++++++++++++++---------
 opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/LDAPCollectionResourceProvider.java |    4 
 opendj3/opendj-rest2ldap/src/test/java/org/forgerock/opendj/rest2ldap/Example.java                        |   30 ++++---
 4 files changed, 145 insertions(+), 55 deletions(-)

diff --git a/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/LDAPCollectionResourceProvider.java b/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/LDAPCollectionResourceProvider.java
index 4c55d64..fd6742e 100644
--- a/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/LDAPCollectionResourceProvider.java
+++ b/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/LDAPCollectionResourceProvider.java
@@ -17,7 +17,7 @@
 
 import static org.forgerock.opendj.ldap.Filter.alwaysFalse;
 import static org.forgerock.opendj.ldap.Filter.alwaysTrue;
-import static org.forgerock.opendj.rest2ldap.ReadOnUpdatePolicy.USE_READ_ENTRY_CONTROLS;
+import static org.forgerock.opendj.rest2ldap.ReadOnUpdatePolicy.CONTROLS;
 import static org.forgerock.opendj.rest2ldap.Utils.accumulate;
 import static org.forgerock.opendj.rest2ldap.Utils.toFilter;
 import static org.forgerock.opendj.rest2ldap.Utils.transform;
@@ -223,7 +223,7 @@
                     handler.handleError(e);
                     return;
                 }
-                if (config.readOnUpdatePolicy() == USE_READ_ENTRY_CONTROLS) {
+                if (config.readOnUpdatePolicy() == CONTROLS) {
                     final String[] attributes = getLDAPAttributes(c, request.getFieldFilters());
                     addRequest.addControl(PostReadRequestControl.newControl(false, attributes));
                 }
diff --git a/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/ReadOnUpdatePolicy.java b/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/ReadOnUpdatePolicy.java
index 1d68304..16ec961 100644
--- a/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/ReadOnUpdatePolicy.java
+++ b/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/ReadOnUpdatePolicy.java
@@ -35,7 +35,7 @@
      * state of the resource at the time the update was performed. This policy
      * requires that the LDAP server supports RFC 4527.
      */
-    USE_READ_ENTRY_CONTROLS,
+    CONTROLS,
 
     /**
      * The LDAP entry will be read non-atomically using an LDAP search when an
@@ -44,5 +44,5 @@
      * may not reflect the state of the resource at the time the update was
      * performed.
      */
-    USE_SEARCH;
+    SEARCH;
 }
diff --git a/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Rest2LDAP.java b/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Rest2LDAP.java
index cfb3b5d..6ce5469 100644
--- a/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Rest2LDAP.java
+++ b/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Rest2LDAP.java
@@ -18,7 +18,7 @@
 
 import static org.forgerock.opendj.ldap.requests.Requests.newSearchRequest;
 import static org.forgerock.opendj.ldap.schema.CoreSchema.getEntryUUIDAttributeType;
-import static org.forgerock.opendj.rest2ldap.ReadOnUpdatePolicy.USE_READ_ENTRY_CONTROLS;
+import static org.forgerock.opendj.rest2ldap.ReadOnUpdatePolicy.CONTROLS;
 import static org.forgerock.opendj.rest2ldap.Utils.ensureNotNull;
 
 import java.util.ArrayList;
@@ -29,6 +29,7 @@
 import java.util.concurrent.TimeUnit;
 
 import org.forgerock.json.fluent.JsonValue;
+import org.forgerock.json.fluent.JsonValueException;
 import org.forgerock.json.resource.BadRequestException;
 import org.forgerock.json.resource.CollectionResourceProvider;
 import org.forgerock.json.resource.ResourceException;
@@ -66,8 +67,8 @@
         private ConnectionFactory factory;
         private MVCCStrategy mvccStrategy;
         private NameStrategy nameStrategy;
-        private ReadOnUpdatePolicy readOnUpdatePolicy = USE_READ_ENTRY_CONTROLS;
-        private final ObjectAttributeMapper rootMapper = new ObjectAttributeMapper();
+        private ReadOnUpdatePolicy readOnUpdatePolicy = CONTROLS;
+        private AttributeMapper rootMapper;
         private Schema schema = Schema.getDefaultSchema();
 
         Builder() {
@@ -81,23 +82,7 @@
         }
 
         public Builder additionalLDAPAttribute(final String attribute, final Object... values) {
-            additionalLDAPAttributes.add(new LinkedAttribute(attribute, values));
-            return this;
-        }
-
-        /**
-         * Creates a mapping for the named JSON attribute.
-         *
-         * @param name
-         *            The name of the JSON attribute to be mapped.
-         * @param mapper
-         *            The attribute mapper responsible for mapping the JSON
-         *            attribute to LDAP attribute(s).
-         * @return A reference to this builder.
-         */
-        public Builder attribute(final String name, final AttributeMapper mapper) {
-            rootMapper.attribute(name, mapper);
-            return this;
+            return additionalLDAPAttribute(new LinkedAttribute(ad(attribute), values));
         }
 
         public Builder baseDN(final DN dn) {
@@ -107,15 +92,13 @@
         }
 
         public Builder baseDN(final String dn) {
-            ensureNotNull(dn);
-            this.baseDN = DN.valueOf(dn);
-            return this;
+            return baseDN(DN.valueOf(dn, schema));
         }
 
         public CollectionResourceProvider build() {
             ensureNotNull(factory);
             ensureNotNull(baseDN);
-            if (rootMapper.isEmpty()) {
+            if (rootMapper == null) {
                 throw new IllegalStateException("No mappings provided");
             }
             return new LDAPCollectionResourceProvider(baseDN, rootMapper, factory, nameStrategy,
@@ -152,8 +135,8 @@
          *     "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",
+         *     // one of "disabled", "controls", or "search".
+         *     "readOnUpdatePolicy" : "controls",
          *
          *     // Additional LDAP attributes which should be included with entries during add (create) operations.
          *     "additionalLDAPAttributes" : [
@@ -188,18 +171,18 @@
          *     "etagAttribute" : "etag",
          *
          *     // The JSON to LDAP attribute mappings.
-         *     "attributes" : [
+         *     "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"   : [
+         *         "name"        : { "object"   : {
          *             "givenName"  : { "simple"   : { "ldapAttribute" : "givenName", "isSingleValued" : true } },
          *             "familyName" : { "simple"   : { "ldapAttribute" : "sn", "isSingleValued" : true, "isRequired" : true } },
-         *         ],
+         *         },
          *         ...
-         *     ]
+         *     }
          * }
          * </pre>
          *
@@ -211,6 +194,43 @@
          */
         public Builder configureMapping(final JsonValue configuration)
                 throws IllegalArgumentException {
+            baseDN(configuration.get("baseDN").required().asString());
+
+            final JsonValue readOnUpdatePolicy = configuration.get("readOnUpdatePolicy");
+            if (!readOnUpdatePolicy.isNull()) {
+                readOnUpdatePolicy(readOnUpdatePolicy.asEnum(ReadOnUpdatePolicy.class));
+            }
+
+            for (final JsonValue v : configuration.get("additionalLDAPAttributes")) {
+                final String type = v.get("type").required().asString();
+                final List<Object> values = v.get("values").required().asList();
+                additionalLDAPAttribute(new LinkedAttribute(type, values));
+            }
+
+            final JsonValue namingStrategy = configuration.get("namingStrategy");
+            if (!namingStrategy.isNull()) {
+                final String name = namingStrategy.get("strategy").required().asString();
+                if (name.equalsIgnoreCase("clientDNNaming")) {
+                    useClientDNNaming(namingStrategy.get("dnAttribute").required().asString());
+                } else if (name.equalsIgnoreCase("clientNaming")) {
+                    useClientNaming(namingStrategy.get("dnAttribute").required().asString(),
+                            namingStrategy.get("idAttribute").required().asString());
+                } else if (name.equalsIgnoreCase("serverNaming")) {
+                    useServerNaming(namingStrategy.get("dnAttribute").required().asString(),
+                            namingStrategy.get("idAttribute").required().asString());
+                } else {
+                    throw new IllegalArgumentException(
+                            "Illegal naming strategy. Must be one of: clientDNNaming, clientNaming, or serverNaming");
+                }
+            }
+
+            final JsonValue etagAttribute = configuration.get("etagAttribute");
+            if (!etagAttribute.isNull()) {
+                useEtagAttribute(etagAttribute.asString());
+            }
+
+            mapper(configureObjectMapper(configuration.get("attributes").required()));
+
             return this;
         }
 
@@ -220,6 +240,11 @@
             return this;
         }
 
+        public Builder mapper(final AttributeMapper mapper) {
+            this.rootMapper = mapper;
+            return this;
+        }
+
         /**
          * Sets the policy which should be used in order to read an entry before
          * it is deleted, or after it is added or modified.
@@ -254,7 +279,7 @@
         }
 
         public Builder useClientDNNaming(final String attribute) {
-            return useClientDNNaming(Schema.getDefaultSchema().getAttributeType(attribute));
+            return useClientDNNaming(at(attribute));
         }
 
         public Builder useClientNaming(final AttributeType dnAttribute,
@@ -264,8 +289,7 @@
         }
 
         public Builder useClientNaming(final String dnAttribute, final String idAttribute) {
-            return useClientNaming(Schema.getDefaultSchema().getAttributeType(dnAttribute),
-                    AttributeDescription.valueOf(idAttribute));
+            return useClientNaming(at(dnAttribute), ad(idAttribute));
         }
 
         public Builder useEtagAttribute() {
@@ -278,7 +302,7 @@
         }
 
         public Builder useEtagAttribute(final String attribute) {
-            return useEtagAttribute(AttributeDescription.valueOf(attribute));
+            return useEtagAttribute(ad(attribute));
         }
 
         public Builder useServerEntryUUIDNaming(final AttributeType dnAttribute) {
@@ -287,7 +311,7 @@
         }
 
         public Builder useServerEntryUUIDNaming(final String dnAttribute) {
-            return useServerEntryUUIDNaming(Schema.getDefaultSchema().getAttributeType(dnAttribute));
+            return useServerEntryUUIDNaming(at(dnAttribute));
         }
 
         public Builder useServerNaming(final AttributeType dnAttribute,
@@ -297,8 +321,72 @@
         }
 
         public Builder useServerNaming(final String dnAttribute, final String idAttribute) {
-            return useServerNaming(Schema.getDefaultSchema().getAttributeType(dnAttribute),
-                    AttributeDescription.valueOf(idAttribute));
+            return useServerNaming(at(dnAttribute), ad(idAttribute));
+        }
+
+        private AttributeDescription ad(final String attribute) {
+            return AttributeDescription.valueOf(attribute, schema);
+        }
+
+        private AttributeType at(final String attribute) {
+            return schema.getAttributeType(attribute);
+        }
+
+        private AttributeMapper configureMapper(final JsonValue mapper) {
+            if (mapper.isDefined("constant")) {
+                return constant(mapper.get("constant").getObject());
+            } else if (mapper.isDefined("simple")) {
+                final JsonValue config = mapper.get("simple");
+                final SimpleAttributeMapper s =
+                        simple(ad(config.get("ldapAttribute").required().asString()));
+                if (config.isDefined("defaultJSONValue")) {
+                    s.defaultJSONValue(config.get("defaultJSONValue").getObject());
+                }
+                if (config.isDefined("defaultLDAPValue")) {
+                    s.defaultLDAPValue(config.get("defaultLDAPValue").getObject());
+                }
+                if (config.get("isBinary").defaultTo(false).asBoolean()) {
+                    s.isBinary();
+                }
+                if (config.get("isRequired").defaultTo(false).asBoolean()) {
+                    s.isRequired();
+                }
+                if (config.get("isSingleValued").defaultTo(false).asBoolean()) {
+                    s.isSingleValued();
+                }
+                if (config.isDefined("writability")) {
+                    final String writability = config.get("writability").asString();
+                    if (writability.equalsIgnoreCase("readOnly")) {
+                        s.writability(WritabilityPolicy.READ_ONLY);
+                    } else if (writability.equalsIgnoreCase("readOnlyDiscardWrites")) {
+                        s.writability(WritabilityPolicy.READ_ONLY_DISCARD_WRITES);
+                    } else if (writability.equalsIgnoreCase("createOnly")) {
+                        s.writability(WritabilityPolicy.CREATE_ONLY);
+                    } else if (writability.equalsIgnoreCase("createOnlyDiscardWrites")) {
+                        s.writability(WritabilityPolicy.CREATE_ONLY_DISCARD_WRITES);
+                    } else if (writability.equalsIgnoreCase("readWrite")) {
+                        s.writability(WritabilityPolicy.READ_WRITE);
+                    } else {
+                        throw new JsonValueException(mapper,
+                                "Illegal writability: must be one of readOnly, readOnlyDiscardWrites, "
+                                        + "createOnly, createOnlyDiscardWrites, or readWrite");
+                    }
+                }
+                return s;
+            } else if (mapper.isDefined("object")) {
+                return configureObjectMapper(mapper.get("object"));
+            } else {
+                throw new JsonValueException(mapper,
+                        "Illegal mapping: must contain constant, simple, or object");
+            }
+        }
+
+        private ObjectAttributeMapper configureObjectMapper(final JsonValue mapper) {
+            final ObjectAttributeMapper object = object();
+            for (final String attribute : mapper.keys()) {
+                object.attribute(attribute, configureMapper(mapper.get(attribute)));
+            }
+            return object;
         }
     }
 
diff --git a/opendj3/opendj-rest2ldap/src/test/java/org/forgerock/opendj/rest2ldap/Example.java b/opendj3/opendj-rest2ldap/src/test/java/org/forgerock/opendj/rest2ldap/Example.java
index f57639b..1dbbc4e 100644
--- a/opendj3/opendj-rest2ldap/src/test/java/org/forgerock/opendj/rest2ldap/Example.java
+++ b/opendj3/opendj-rest2ldap/src/test/java/org/forgerock/opendj/rest2ldap/Example.java
@@ -67,17 +67,18 @@
         // Create user resource.
         CollectionResourceProvider users =
                 builder().connectionFactory(ldapFactory).baseDN("ou=people,dc=example,dc=com")
-                    .attribute("schemas", constant(Arrays.asList("urn:scim:schemas:core:1.0")))
-                    .attribute("id", simple("uid").isSingleValued().isRequired().writability(CREATE_ONLY))
-                    .attribute("rev", simple("etag").isSingleValued().writability(READ_ONLY))
-                    .attribute("userName", simple("mail").isSingleValued().writability(READ_ONLY))
-                    .attribute("displayName", simple("cn").isSingleValued().isRequired())
-                    .attribute("name", object()
-                            .attribute("givenName", simple("givenName").isSingleValued())
-                            .attribute("familyName", simple("sn").isSingleValued().isRequired()))
-                    .attribute("contactInformation", object()
-                            .attribute("telephoneNumber", simple("telephoneNumber").isSingleValued())
-                            .attribute("emailAddress", simple("mail").isSingleValued()))
+                    .mapper(object()
+                            .attribute("schemas", constant(Arrays.asList("urn:scim:schemas:core:1.0")))
+                            .attribute("id", simple("uid").isSingleValued().isRequired().writability(CREATE_ONLY))
+                            .attribute("rev", simple("etag").isSingleValued().writability(READ_ONLY))
+                            .attribute("userName", simple("mail").isSingleValued().writability(READ_ONLY))
+                            .attribute("displayName", simple("cn").isSingleValued().isRequired())
+                            .attribute("name", object()
+                                    .attribute("givenName", simple("givenName").isSingleValued())
+                                    .attribute("familyName", simple("sn").isSingleValued().isRequired()))
+                            .attribute("contactInformation", object()
+                                    .attribute("telephoneNumber", simple("telephoneNumber").isSingleValued())
+                                    .attribute("emailAddress", simple("mail").isSingleValued())))
                     .additionalLDAPAttribute("objectClass", "top", "person", "organizationalPerson", "inetOrgPerson")
                     .build();
         router.addRoute("/users", users);
@@ -85,9 +86,10 @@
         // Create group resource.
         CollectionResourceProvider groups =
                 builder().connectionFactory(ldapFactory).baseDN("ou=groups,dc=example,dc=com")
-                    .attribute("cn", simple("cn").isSingleValued())
-                    .attribute("description", simple("description"))
-                    .attribute("member", simple("uniquemember"))
+                    .mapper(object()
+                            .attribute("cn", simple("cn").isSingleValued())
+                            .attribute("description", simple("description"))
+                            .attribute("member", simple("uniquemember")))
                     .build();
         router.addRoute("/groups", groups);
 

--
Gitblit v1.10.0