From b4c79b62478d4aec0f3312af1250137ad45caf95 Mon Sep 17 00:00:00 2001
From: Matthew Swift <matthew.swift@forgerock.com>
Date: Mon, 11 Feb 2013 14:13:31 +0000
Subject: [PATCH] Fix OPENDJ-691: Implement add/create support

---
 opendj-sdk/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/SimpleAttributeMapper.java       |   85 ++++++++++++++--
 opendj-sdk/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Rest2LDAP.java                   |    8 
 opendj-sdk/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Utils.java                       |   79 +++++++++++++--
 opendj-sdk/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/DefaultAttributeMapper.java      |   35 ------
 opendj-sdk/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/LDAPConstantAttributeMapper.java |   14 ++
 opendj-sdk/opendj3/opendj-rest2ldap/src/test/java/org/forgerock/opendj/rest2ldap/Example.java                     |   21 ++-
 6 files changed, 171 insertions(+), 71 deletions(-)

diff --git a/opendj-sdk/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/DefaultAttributeMapper.java b/opendj-sdk/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/DefaultAttributeMapper.java
index 3d29ddc..96b68f7 100644
--- a/opendj-sdk/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/DefaultAttributeMapper.java
+++ b/opendj-sdk/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/DefaultAttributeMapper.java
@@ -17,11 +17,11 @@
 
 import static org.forgerock.opendj.rest2ldap.Utils.attributeToJson;
 import static org.forgerock.opendj.rest2ldap.Utils.getAttributeName;
+import static org.forgerock.opendj.rest2ldap.Utils.jsonToAttribute;
 import static org.forgerock.opendj.rest2ldap.Utils.toFilter;
 import static org.forgerock.opendj.rest2ldap.Utils.toLowerCase;
 
 import java.util.ArrayList;
-import java.util.Collection;
 import java.util.Collections;
 import java.util.LinkedHashMap;
 import java.util.List;
@@ -34,7 +34,6 @@
 import org.forgerock.json.resource.ResultHandler;
 import org.forgerock.opendj.ldap.Attribute;
 import org.forgerock.opendj.ldap.AttributeDescription;
-import org.forgerock.opendj.ldap.Attributes;
 import org.forgerock.opendj.ldap.Entry;
 import org.forgerock.opendj.ldap.Filter;
 
@@ -138,38 +137,16 @@
                 if (!isIncludedAttribute(field.getKey())) {
                     continue;
                 }
-                final AttributeDescription ad;
                 try {
-                    ad = AttributeDescription.valueOf(field.getKey(), c.getConfig().schema());
+                    final AttributeDescription ad =
+                            AttributeDescription.valueOf(field.getKey(), c.getConfig().schema());
+                    result.add(jsonToAttribute(field.getValue(), ad));
                 } catch (final Exception e) {
                     // FIXME: improve error message.
                     h.handleError(new BadRequestException("The field " + field.getKey()
                             + " is invalid"));
                     return;
                 }
-                final Object value = field.getValue();
-                if (isJSONPrimitive(value)) {
-                    result.add(Attributes.singletonAttribute(ad, value));
-                } else if (value instanceof Collection<?>) {
-                    final Attribute a =
-                            c.getConfig().decodeOptions().getAttributeFactory().newAttribute(ad);
-                    for (final Object o : (Collection<?>) value) {
-                        if (isJSONPrimitive(o)) {
-                            a.add(o);
-                        } else {
-                            // FIXME: improve error message.
-                            h.handleError(new BadRequestException("The field " + field.getKey()
-                                    + " is invalid"));
-                            return;
-                        }
-                    }
-                    result.add(a);
-                } else {
-                    // FIXME: improve error message.
-                    h.handleError(new BadRequestException("The field " + field.getKey()
-                            + " is invalid"));
-                    return;
-                }
             }
             h.handleResult(result);
         } else {
@@ -192,8 +169,4 @@
 
         return false;
     }
-
-    private boolean isJSONPrimitive(final Object value) {
-        return value instanceof String || value instanceof Boolean || value instanceof Number;
-    }
 }
diff --git a/opendj-sdk/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/LDAPConstantAttributeMapper.java b/opendj-sdk/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/LDAPConstantAttributeMapper.java
index 87272b6..eb5d0cb 100644
--- a/opendj-sdk/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/LDAPConstantAttributeMapper.java
+++ b/opendj-sdk/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/LDAPConstantAttributeMapper.java
@@ -15,6 +15,7 @@
  */
 package org.forgerock.opendj.rest2ldap;
 
+import static java.util.Collections.singletonList;
 import static org.forgerock.opendj.ldap.Attributes.singletonAttribute;
 
 import java.util.Collections;
@@ -29,6 +30,7 @@
 import org.forgerock.opendj.ldap.AttributeDescription;
 import org.forgerock.opendj.ldap.Entry;
 import org.forgerock.opendj.ldap.Filter;
+import org.forgerock.opendj.ldap.LinkedAttribute;
 
 /**
  * An attribute mapper which maps a single LDAP attribute to a fixed value.
@@ -37,8 +39,16 @@
     private final List<Attribute> attributes;
 
     LDAPConstantAttributeMapper(final AttributeDescription attributeName,
-            final Object attributeValue) {
-        attributes = Collections.singletonList(singletonAttribute(attributeName, attributeValue));
+            final Object... attributeValues) {
+        if (attributeValues.length == 1) {
+            attributes = singletonList(singletonAttribute(attributeName, attributeValues[0]));
+        } else {
+            Attribute attribute = new LinkedAttribute(attributeName);
+            for (Object o : attributeValues) {
+                attribute.add(o);
+            }
+            attributes = singletonList(attribute);
+        }
     }
 
     @Override
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 0c564aa..843e895 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
@@ -359,13 +359,13 @@
     }
 
     public static AttributeMapper mapLDAPConstant(final AttributeDescription attribute,
-            final Object attributeValue) {
-        return new LDAPConstantAttributeMapper(attribute, attributeValue);
+            final Object... attributeValues) {
+        return new LDAPConstantAttributeMapper(attribute, attributeValues);
     }
 
     public static AttributeMapper mapLDAPConstant(final String attribute,
-            final Object attributeValue) {
-        return mapLDAPConstant(AttributeDescription.valueOf(attribute), attributeValue);
+            final Object... attributeValues) {
+        return mapLDAPConstant(AttributeDescription.valueOf(attribute), attributeValues);
     }
 
     private static AttributeMapper mapOf(final Collection<AttributeMapper> mappers) {
diff --git a/opendj-sdk/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/SimpleAttributeMapper.java b/opendj-sdk/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/SimpleAttributeMapper.java
index dabc613..a8375e3 100644
--- a/opendj-sdk/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/SimpleAttributeMapper.java
+++ b/opendj-sdk/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/SimpleAttributeMapper.java
@@ -15,7 +15,15 @@
  */
 package org.forgerock.opendj.rest2ldap;
 
+import static java.util.Collections.emptyList;
+import static java.util.Collections.emptySet;
+import static java.util.Collections.singleton;
+import static java.util.Collections.singletonList;
+import static java.util.Collections.singletonMap;
+import static org.forgerock.opendj.ldap.Functions.fixedFunction;
 import static org.forgerock.opendj.rest2ldap.Utils.byteStringToJson;
+import static org.forgerock.opendj.rest2ldap.Utils.jsonToAttribute;
+import static org.forgerock.opendj.rest2ldap.Utils.jsonToByteString;
 import static org.forgerock.opendj.rest2ldap.Utils.toFilter;
 import static org.forgerock.opendj.rest2ldap.Utils.toLowerCase;
 
@@ -27,6 +35,7 @@
 
 import org.forgerock.json.fluent.JsonPointer;
 import org.forgerock.json.fluent.JsonValue;
+import org.forgerock.json.resource.BadRequestException;
 import org.forgerock.json.resource.ResultHandler;
 import org.forgerock.opendj.ldap.Attribute;
 import org.forgerock.opendj.ldap.AttributeDescription;
@@ -34,7 +43,7 @@
 import org.forgerock.opendj.ldap.Entry;
 import org.forgerock.opendj.ldap.Filter;
 import org.forgerock.opendj.ldap.Function;
-import org.forgerock.opendj.ldap.Functions;
+import org.forgerock.opendj.ldap.LinkedAttribute;
 
 /**
  * An attribute mapper which maps a single JSON attribute to a single LDAP
@@ -43,8 +52,10 @@
 public final class SimpleAttributeMapper extends AttributeMapper {
 
     private Function<ByteString, ?, Void> decoder = null;
-    private Object defaultValue = null;
-    private Collection<Object> defaultValues = Collections.emptySet();
+    private Object defaultJSONValue = null;
+    private Collection<Object> defaultJSONValues = Collections.emptySet();
+    private ByteString defaultLDAPValue = null;
+    private Function<Object, ByteString, Void> encoder = null;
     private boolean forceSingleValued = false;
 
     // private boolean isReadOnly = false;
@@ -90,9 +101,34 @@
      * @return This attribute mapper.
      */
     public SimpleAttributeMapper defaultJSONValue(final Object defaultValue) {
-        this.defaultValue = defaultValue;
-        this.defaultValues =
-                defaultValue != null ? Collections.singleton(defaultValue) : Collections.emptySet();
+        this.defaultJSONValue = defaultValue;
+        this.defaultJSONValues = defaultValue != null ? singleton(defaultValue) : emptySet();
+        return this;
+    }
+
+    /**
+     * Sets the default LDAP value which should be substituted when the JSON
+     * attribute is not found in the JSON value.
+     *
+     * @param defaultValue
+     *            The default LDAP value.
+     * @return This attribute mapper.
+     */
+    public SimpleAttributeMapper defaultLDAPValue(final Object defaultValue) {
+        this.defaultLDAPValue = defaultValue != null ? ByteString.valueOf(defaultValue) : null;
+        return this;
+    }
+
+    /**
+     * Sets the encoder which will be used for converting JSON values to LDAP
+     * attribute values.
+     *
+     * @param f
+     *            The function to use for encoding LDAP attribute values.
+     * @return This attribute mapper.
+     */
+    public SimpleAttributeMapper encoder(final Function<Object, ByteString, Void> f) {
+        this.encoder = f;
         return this;
     }
 
@@ -145,21 +181,44 @@
     @Override
     void toJSON(final Context c, final Entry e, final ResultHandler<Map<String, Object>> h) {
         final Function<ByteString, ?, Void> f =
-                decoder == null ? Functions.fixedFunction(byteStringToJson(), ldapAttributeName)
-                        : decoder;
+                decoder == null ? fixedFunction(byteStringToJson(), ldapAttributeName) : decoder;
         final Object value;
         if (forceSingleValued || ldapAttributeName.getAttributeType().isSingleValue()) {
-            value = e.parseAttribute(ldapAttributeName).as(f, defaultValue);
+            value = e.parseAttribute(ldapAttributeName).as(f, defaultJSONValue);
         } else {
-            value = e.parseAttribute(ldapAttributeName).asSetOf(f, defaultValues);
+            value = e.parseAttribute(ldapAttributeName).asSetOf(f, defaultJSONValues);
         }
-        h.handleResult(Collections.singletonMap(jsonAttributeName, value));
+        h.handleResult(singletonMap(jsonAttributeName, value));
     }
 
     @Override
     void toLDAP(final Context c, final JsonValue v, final ResultHandler<List<Attribute>> h) {
-        // TODO Auto-generated method stub
-
+        if (v.isMap()) {
+            final Object value = v.get(jsonAttributeName).getObject();
+            try {
+                final List<Attribute> result;
+                if (value != null) {
+                    final Function<Object, ByteString, Void> f =
+                            encoder != null ? encoder : fixedFunction(jsonToByteString(),
+                                    ldapAttributeName);
+                    result = singletonList(jsonToAttribute(value, ldapAttributeName, f));
+                } else if (defaultLDAPValue != null) {
+                    result =
+                            singletonList((Attribute) new LinkedAttribute(ldapAttributeName,
+                                    defaultLDAPValue));
+                } else {
+                    result = emptyList();
+                }
+                h.handleResult(result);
+            } catch (final Exception e) {
+                // FIXME: improve error message.
+                h.handleError(new BadRequestException("The field " + jsonAttributeName
+                        + " is invalid"));
+                return;
+            }
+        } else {
+            h.handleResult(Collections.<Attribute> emptyList());
+        }
     }
 
     private boolean matches(final JsonPointer jsonAttribute) {
diff --git a/opendj-sdk/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Utils.java b/opendj-sdk/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Utils.java
index 43c47a5..e230bf4 100644
--- a/opendj-sdk/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Utils.java
+++ b/opendj-sdk/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Utils.java
@@ -15,6 +15,13 @@
  */
 package org.forgerock.opendj.rest2ldap;
 
+import static javax.xml.bind.DatatypeConverter.parseDateTime;
+import static javax.xml.bind.DatatypeConverter.printDateTime;
+import static org.forgerock.opendj.ldap.Functions.byteStringToBoolean;
+import static org.forgerock.opendj.ldap.Functions.byteStringToGeneralizedTime;
+import static org.forgerock.opendj.ldap.Functions.byteStringToLong;
+import static org.forgerock.opendj.ldap.Functions.byteStringToString;
+import static org.forgerock.opendj.ldap.Functions.fixedFunction;
 import static org.forgerock.opendj.ldap.schema.CoreSchema.getBooleanSyntax;
 import static org.forgerock.opendj.ldap.schema.CoreSchema.getGeneralizedTimeSyntax;
 import static org.forgerock.opendj.ldap.schema.CoreSchema.getIntegerSyntax;
@@ -26,8 +33,6 @@
 import java.util.Locale;
 import java.util.concurrent.atomic.AtomicInteger;
 
-import javax.xml.bind.DatatypeConverter;
-
 import org.forgerock.json.resource.ResourceException;
 import org.forgerock.json.resource.ResultHandler;
 import org.forgerock.opendj.ldap.Attribute;
@@ -35,7 +40,8 @@
 import org.forgerock.opendj.ldap.ByteString;
 import org.forgerock.opendj.ldap.Filter;
 import org.forgerock.opendj.ldap.Function;
-import org.forgerock.opendj.ldap.Functions;
+import org.forgerock.opendj.ldap.GeneralizedTime;
+import org.forgerock.opendj.ldap.LinkedAttribute;
 import org.forgerock.opendj.ldap.schema.Syntax;
 
 /**
@@ -85,24 +91,45 @@
 
     }
 
-    // @Checkstyle:off
     private static final Function<ByteString, Object, AttributeDescription> BYTESTRING_TO_JSON =
             new Function<ByteString, Object, AttributeDescription>() {
                 @Override
                 public Object apply(final ByteString value, final AttributeDescription ad) {
                     final Syntax syntax = ad.getAttributeType().getSyntax();
                     if (syntax.equals(getBooleanSyntax())) {
-                        return Functions.byteStringToBoolean().apply(value, null);
+                        return byteStringToBoolean().apply(value, null);
                     } else if (syntax.equals(getIntegerSyntax())) {
-                        return Functions.byteStringToLong().apply(value, null);
+                        return byteStringToLong().apply(value, null);
                     } else if (syntax.equals(getGeneralizedTimeSyntax())) {
-                        return DatatypeConverter.printDateTime(Functions
-                                .byteStringToGeneralizedTime().apply(value, null).toCalendar());
+                        return printDateTime(byteStringToGeneralizedTime().apply(value, null)
+                                .toCalendar());
                     } else if (syntax.isHumanReadable()) {
-                        return Functions.byteStringToString().apply(value, null);
+                        return byteStringToString().apply(value, null);
                     } else {
                         // Base 64 encoded binary.
-                        return DatatypeConverter.printBase64Binary(value.toByteArray());
+                        return value.toBase64String();
+                    }
+                }
+            };
+
+    private static final Function<Object, ByteString, AttributeDescription> JSON_TO_BYTESTRING =
+            new Function<Object, ByteString, AttributeDescription>() {
+                @Override
+                public ByteString apply(final Object value, final AttributeDescription ad) {
+                    if (isJSONPrimitive(value)) {
+                        final Syntax syntax = ad.getAttributeType().getSyntax();
+                        if (syntax.equals(getGeneralizedTimeSyntax())) {
+                            return ByteString.valueOf(GeneralizedTime.valueOf(parseDateTime(value
+                                    .toString())));
+                        } else if (syntax.isHumanReadable()) {
+                            return ByteString.valueOf(value);
+                        } else {
+                            // Base 64 encoded binary.
+                            return ByteString.valueOfBase64(value.toString());
+                        }
+                    } else {
+                        throw new IllegalArgumentException("Unrecognized type of JSON value: "
+                                + value.getClass().getName());
                     }
                 }
             };
@@ -113,14 +140,12 @@
 
     static Object attributeToJson(final Attribute a) {
         final Function<ByteString, Object, Void> f =
-                Functions.fixedFunction(BYTESTRING_TO_JSON, a.getAttributeDescription());
+                fixedFunction(BYTESTRING_TO_JSON, a.getAttributeDescription());
         final boolean isSingleValued =
                 a.getAttributeDescription().getAttributeType().isSingleValue();
         return isSingleValued ? a.parse().as(f) : asList(a.parse().asSetOf(f));
     }
 
-    // @Checkstyle:on
-
     static Function<ByteString, Object, AttributeDescription> byteStringToJson() {
         return BYTESTRING_TO_JSON;
     }
@@ -143,6 +168,34 @@
         return a.getAttributeDescription().withoutOption("binary").toString();
     }
 
+    static boolean isJSONPrimitive(final Object value) {
+        return value instanceof String || value instanceof Boolean || value instanceof Number;
+    }
+
+    static Attribute jsonToAttribute(final Object value, final AttributeDescription ad) {
+        return jsonToAttribute(value, ad, fixedFunction(jsonToByteString(), ad));
+    }
+
+    static Attribute jsonToAttribute(final Object value, final AttributeDescription ad,
+            final Function<Object, ByteString, Void> f) {
+        if (isJSONPrimitive(value)) {
+            return new LinkedAttribute(ad, f.apply(value, null));
+        } else if (value instanceof Collection<?>) {
+            final Attribute a = new LinkedAttribute(ad);
+            for (final Object o : (Collection<?>) value) {
+                a.add(f.apply(o, null));
+            }
+            return a;
+        } else {
+            throw new IllegalArgumentException("Unrecognized type of JSON value: "
+                    + value.getClass().getName());
+        }
+    }
+
+    static Function<Object, ByteString, AttributeDescription> jsonToByteString() {
+        return JSON_TO_BYTESTRING;
+    }
+
     static Filter toFilter(final Context c, final FilterType type, final String ldapAttribute,
             final Object valueAssertion) {
         final String v = String.valueOf(valueAssertion);
diff --git a/opendj-sdk/opendj3/opendj-rest2ldap/src/test/java/org/forgerock/opendj/rest2ldap/Example.java b/opendj-sdk/opendj3/opendj-rest2ldap/src/test/java/org/forgerock/opendj/rest2ldap/Example.java
index d939567..8a042c8 100644
--- a/opendj-sdk/opendj3/opendj-rest2ldap/src/test/java/org/forgerock/opendj/rest2ldap/Example.java
+++ b/opendj-sdk/opendj3/opendj-rest2ldap/src/test/java/org/forgerock/opendj/rest2ldap/Example.java
@@ -18,11 +18,9 @@
 
 import static org.forgerock.json.resource.Resources.newInternalConnectionFactory;
 import static org.forgerock.opendj.ldap.Connections.newAuthenticatedConnectionFactory;
-import static org.forgerock.opendj.rest2ldap.Rest2LDAP.builder;
-import static org.forgerock.opendj.rest2ldap.Rest2LDAP.map;
-import static org.forgerock.opendj.rest2ldap.Rest2LDAP.mapAllOf;
-import static org.forgerock.opendj.rest2ldap.Rest2LDAP.mapComplex;
+import static org.forgerock.opendj.rest2ldap.Rest2LDAP.*;
 
+import java.util.Arrays;
 import java.util.logging.Logger;
 
 import org.forgerock.json.resource.CollectionResourceProvider;
@@ -65,12 +63,19 @@
         // Create user resource.
         CollectionResourceProvider users =
                 builder().factory(ldapFactory).baseDN("ou=people,dc=example,dc=com").map(
+                        mapJSONConstant("schemas", Arrays.asList("urn:scim:schemas:core:1.0")),
                         map("id", "entryUUID").singleValued(true),
-                        mapAllOf("uid", "isMemberOf", "modifyTimestamp"),
-                        mapComplex("name", mapAllOf("cn", "sn", "givenName")),
+                        map("externalId", "uid").singleValued(true),
+                        map("userName", "mail").singleValued(true),
+                        map("displayName", "cn").singleValued(true),
+                        mapComplex("name", map("givenName", "givenName").singleValued(true), map(
+                                "familyName", "sn").singleValued(true)),
                         mapComplex("contactInformation", map("telephoneNumber").decoder(
-                                Functions.byteStringToString()).singleValued(true), map(
-                                "emailAddress", "mail").singleValued(true))).build();
+                                Functions.byteStringToString()).encoder(
+                                Functions.objectToByteString()).singleValued(true), map(
+                                "emailAddress", "mail").singleValued(true)),
+                        mapLDAPConstant("objectClass", "top", "person", "organizationalPerson", "inetOrgPerson"))
+                        .build();
         router.addRoute("/users", users);
 
         // Create group resource.

--
Gitblit v1.10.0