From 39a420d9aa3817dbe2dc9eff52464e5b464dbdde Mon Sep 17 00:00:00 2001
From: Matthew Swift <matthew.swift@forgerock.com>
Date: Thu, 06 Oct 2016 15:33:04 +0000
Subject: [PATCH] OPENDJ-2860: add support for JSON property mapping in Rest2LDAP

---
 opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Rest2LdapJsonConfigurator.java                |   10 +++
 opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/schema/JsonSyntaxImpl.java                    |   11 ---
 opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/schema/JsonSchema.java                        |   61 ++++++++++++++++++++
 opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/schema/JsonQueryEqualityMatchingRuleImpl.java |   14 +---
 opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Utils.java                                    |   29 ++++++---
 5 files changed, 94 insertions(+), 31 deletions(-)

diff --git a/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Rest2LdapJsonConfigurator.java b/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Rest2LdapJsonConfigurator.java
index 2760cd3..d902445 100644
--- a/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Rest2LdapJsonConfigurator.java
+++ b/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Rest2LdapJsonConfigurator.java
@@ -79,6 +79,7 @@
 import org.forgerock.opendj.ldap.SSLContextBuilder;
 import org.forgerock.opendj.ldap.requests.BindRequest;
 import org.forgerock.opendj.ldap.requests.Requests;
+import org.forgerock.opendj.rest2ldap.schema.JsonSchema;
 import org.forgerock.services.context.Context;
 import org.forgerock.util.Options;
 import org.forgerock.util.promise.Promise;
@@ -356,6 +357,15 @@
                     .isRequired(mapper.get("isRequired").defaultTo(false).asBoolean())
                     .isMultiValued(mapper.get("isMultiValued").defaultTo(false).asBoolean())
                     .writability(parseWritability(mapper));
+        case "json":
+            return simple(mapper.get("ldapAttribute").defaultTo(defaultLdapAttribute).required().asString())
+                    .defaultJsonValue(mapper.get("defaultJsonValue").getObject())
+                    .isRequired(mapper.get("isRequired").defaultTo(false).asBoolean())
+                    .isMultiValued(mapper.get("isMultiValued").defaultTo(false).asBoolean())
+                    .encoder(JsonSchema.jsonToByteString())
+                    .decoder(JsonSchema.byteStringToJson())
+                    .jsonSchema(mapper.isDefined("schema") ? mapper.get("schema") : null)
+                    .writability(parseWritability(mapper));
         case "reference":
             final String ldapAttribute = mapper.get("ldapAttribute")
                                                .defaultTo(defaultLdapAttribute).required().asString();
diff --git a/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Utils.java b/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Utils.java
index 83dc801..dc52087 100644
--- a/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Utils.java
+++ b/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Utils.java
@@ -26,6 +26,7 @@
 import static org.forgerock.opendj.ldap.schema.CoreSchema.getGeneralizedTimeSyntax;
 import static org.forgerock.opendj.ldap.schema.CoreSchema.getIntegerSyntax;
 import static org.forgerock.opendj.rest2ldap.Rest2ldapMessages.ERR_UNRECOGNIZED_JSON_VALUE;
+import static org.forgerock.opendj.rest2ldap.schema.JsonSchema.getJsonSyntax;
 
 import java.util.Collection;
 import java.util.Collections;
@@ -45,6 +46,7 @@
 import org.forgerock.opendj.ldap.GeneralizedTime;
 import org.forgerock.opendj.ldap.LinkedAttribute;
 import org.forgerock.opendj.ldap.schema.Syntax;
+import org.forgerock.opendj.rest2ldap.schema.JsonSchema;
 import org.forgerock.services.context.Context;
 import org.forgerock.util.Function;
 import org.forgerock.util.promise.NeverThrowsException;
@@ -54,8 +56,8 @@
  */
 final class Utils {
 
-    private static final Function<Object, ByteString, NeverThrowsException> BASE64_TO_BYTESTRING =
-            new Function<Object, ByteString, NeverThrowsException>() {
+    private static final Function<Object, ByteString, LocalizedIllegalArgumentException> BASE64_TO_BYTESTRING =
+            new Function<Object, ByteString, LocalizedIllegalArgumentException>() {
                 @Override
                 public ByteString apply(final Object value) {
                     return ByteString.valueOfBase64(String.valueOf(value));
@@ -70,7 +72,7 @@
                 }
             };
 
-    static Function<Object, ByteString, NeverThrowsException> base64ToByteString() {
+    static Function<Object, ByteString, LocalizedIllegalArgumentException> base64ToByteString() {
         return BASE64_TO_BYTESTRING;
     }
 
@@ -78,8 +80,9 @@
         return BYTESTRING_TO_BASE64;
     }
 
-    static Function<ByteString, Object, NeverThrowsException> byteStringToJson(final AttributeDescription ad) {
-        return new Function<ByteString, Object, NeverThrowsException>() {
+    static Function<ByteString, Object, LocalizedIllegalArgumentException> byteStringToJson(
+            final AttributeDescription ad) {
+        return new Function<ByteString, Object, LocalizedIllegalArgumentException>() {
             @Override
             public Object apply(final ByteString value) {
                 final Syntax syntax = ad.getAttributeType().getSyntax();
@@ -88,8 +91,9 @@
                 } else if (syntax.equals(getIntegerSyntax())) {
                     return byteStringToLong().apply(value);
                 } else if (syntax.equals(getGeneralizedTimeSyntax())) {
-                    return printDateTime(byteStringToGeneralizedTime().apply(value)
-                            .toCalendar());
+                    return printDateTime(byteStringToGeneralizedTime().apply(value).toCalendar());
+                } else if (syntax.equals(getJsonSyntax())) {
+                    return JsonSchema.byteStringToJson().apply(value);
                 } else {
                     return byteStringToString().apply(value);
                 }
@@ -120,17 +124,20 @@
         }
     }
 
-    static Function<Object, ByteString, NeverThrowsException> jsonToByteString(final AttributeDescription ad) {
-        return new Function<Object, ByteString, NeverThrowsException>() {
+    static Function<Object, ByteString, Exception> jsonToByteString(
+            final AttributeDescription ad) {
+        return new Function<Object, ByteString, Exception>() {
             @Override
-            public ByteString apply(final Object value) {
+            public ByteString apply(final Object value) throws Exception {
+                final Syntax syntax = ad.getAttributeType().getSyntax();
                 if (isJsonPrimitive(value)) {
-                    final Syntax syntax = ad.getAttributeType().getSyntax();
                     if (syntax.equals(getGeneralizedTimeSyntax())) {
                         return ByteString.valueOfObject(GeneralizedTime.valueOf(parseDateTime(value.toString())));
                     } else {
                         return ByteString.valueOfObject(value);
                     }
+                } else if (syntax.equals(getJsonSyntax())) {
+                    return JsonSchema.jsonToByteString().apply(value);
                 } else {
                     throw new LocalizedIllegalArgumentException(ERR_UNRECOGNIZED_JSON_VALUE.get(value.getClass()
                                                                                                      .getName()));
diff --git a/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/schema/JsonQueryEqualityMatchingRuleImpl.java b/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/schema/JsonQueryEqualityMatchingRuleImpl.java
index 250770f..8e64239 100644
--- a/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/schema/JsonQueryEqualityMatchingRuleImpl.java
+++ b/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/schema/JsonQueryEqualityMatchingRuleImpl.java
@@ -25,6 +25,7 @@
 import static org.forgerock.opendj.rest2ldap.schema.JsonSchema.IGNORE_WHITE_SPACE;
 import static org.forgerock.opendj.rest2ldap.schema.JsonSchema.INDEXED_FIELD_PATTERNS;
 import static org.forgerock.opendj.rest2ldap.schema.JsonSchema.ValidationPolicy.LENIENT;
+import static org.forgerock.opendj.rest2ldap.schema.JsonSchema.jsonParsingException;
 
 import java.io.IOException;
 import java.io.InputStream;
@@ -56,7 +57,6 @@
 import org.forgerock.util.query.QueryFilterVisitor;
 
 import com.fasterxml.jackson.core.JsonParser;
-import com.fasterxml.jackson.core.JsonProcessingException;
 import com.fasterxml.jackson.core.JsonToken;
 
 /**
@@ -203,12 +203,8 @@
             return normalizedValue.toByteString();
         } catch (DecodeException e) {
             throw e;
-        } catch (JsonProcessingException e) {
-            throw DecodeException.error(ERR_JSON_PARSE_ERROR.get(e.getLocation().getLineNr(),
-                                                                 e.getLocation().getColumnNr(),
-                                                                 e.getOriginalMessage()));
         } catch (IOException e) {
-            throw DecodeException.error(ERR_JSON_IO_ERROR.get(e.getMessage()));
+            throw DecodeException.error(jsonParsingException(e));
         }
     }
 
@@ -380,12 +376,8 @@
                 }
             } catch (DecodeException e) {
                 throw e;
-            } catch (JsonProcessingException e) {
-                throw DecodeException.error(ERR_JSON_PARSE_ERROR.get(e.getLocation().getLineNr(),
-                                                                     e.getLocation().getColumnNr(),
-                                                                     e.getOriginalMessage()));
             } catch (IOException e) {
-                throw DecodeException.error(ERR_JSON_IO_ERROR.get(e.getMessage()));
+                throw DecodeException.error(jsonParsingException(e));
             }
         }
 
diff --git a/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/schema/JsonSchema.java b/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/schema/JsonSchema.java
index 50ee3b8..e87be40 100644
--- a/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/schema/JsonSchema.java
+++ b/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/schema/JsonSchema.java
@@ -20,20 +20,30 @@
 import static com.fasterxml.jackson.core.JsonParser.Feature.ALLOW_UNQUOTED_CONTROL_CHARS;
 import static java.util.Collections.emptyList;
 import static org.forgerock.opendj.ldap.schema.Schema.getCoreSchema;
+import static org.forgerock.opendj.rest2ldap.Rest2ldapMessages.ERR_JSON_IO_ERROR;
+import static org.forgerock.opendj.rest2ldap.Rest2ldapMessages.ERR_JSON_PARSE_ERROR;
+import static org.forgerock.opendj.rest2ldap.schema.JsonSchema.ValidationPolicy.LENIENT;
 import static org.forgerock.opendj.rest2ldap.schema.JsonSchema.ValidationPolicy.STRICT;
 import static org.forgerock.util.Options.defaultOptions;
 
+import java.io.IOException;
+import java.io.InputStream;
 import java.util.Collection;
 
+import org.forgerock.i18n.LocalizableMessage;
+import org.forgerock.i18n.LocalizedIllegalArgumentException;
+import org.forgerock.opendj.ldap.ByteString;
 import org.forgerock.opendj.ldap.schema.MatchingRule;
 import org.forgerock.opendj.ldap.schema.MatchingRuleImpl;
 import org.forgerock.opendj.ldap.schema.Schema;
 import org.forgerock.opendj.ldap.schema.SchemaBuilder;
 import org.forgerock.opendj.ldap.schema.Syntax;
+import org.forgerock.util.Function;
 import org.forgerock.util.Option;
 import org.forgerock.util.Options;
 
 import com.fasterxml.jackson.core.JsonFactory;
+import com.fasterxml.jackson.core.JsonProcessingException;
 import com.fasterxml.jackson.databind.ObjectMapper;
 
 /**
@@ -54,6 +64,7 @@
                                   .enable(ALLOW_UNQUOTED_CONTROL_CHARS)),
         /** JSON validation policy which does not perform any validation. */
         DISABLED(null);
+
         private final ObjectMapper objectMapper;
 
         ValidationPolicy(final ObjectMapper objectMapper) {
@@ -128,6 +139,37 @@
     private static final Syntax JSON_QUERY_SYNTAX;
     private static final MatchingRule CASE_IGNORE_JSON_QUERY_MATCHING_RULE;
     private static final MatchingRule CASE_EXACT_JSON_QUERY_MATCHING_RULE;
+    private static final Function<ByteString, Object, LocalizedIllegalArgumentException> BYTESTRING_TO_JSON =
+            new Function<ByteString, Object, LocalizedIllegalArgumentException>() {
+                @Override
+                public Object apply(final ByteString value) {
+                    try (final InputStream inputStream = value.asReader().asInputStream()) {
+                        return LENIENT.getObjectMapper().readValue(inputStream, Object.class);
+                    } catch (final IOException e) {
+                        throw new LocalizedIllegalArgumentException(jsonParsingException(e));
+                    }
+                }
+            };
+
+    static LocalizableMessage jsonParsingException(final IOException e) {
+        if (e instanceof JsonProcessingException) {
+            final JsonProcessingException jpe = (JsonProcessingException) e;
+            if (jpe.getLocation() != null) {
+                return ERR_JSON_PARSE_ERROR.get(jpe.getLocation().getLineNr(),
+                                                jpe.getLocation().getColumnNr(),
+                                                jpe.getOriginalMessage());
+            }
+        }
+        return ERR_JSON_IO_ERROR.get(e.getMessage());
+    }
+
+    private static final Function<Object, ByteString, JsonProcessingException> JSON_TO_BYTESTRING =
+            new Function<Object, ByteString, JsonProcessingException>() {
+                @Override
+                public ByteString apply(final Object value) throws JsonProcessingException {
+                    return ByteString.wrap(LENIENT.getObjectMapper().writeValueAsBytes(value));
+                }
+            };
 
     static {
         final Schema schema = addJsonSyntaxesAndMatchingRulesToSchema(new SchemaBuilder(getCoreSchema())).toSchema();
@@ -247,6 +289,25 @@
         return builder;
     }
 
+    /**
+     * Returns a function which parses {@code JSON} values. Invalid values will result in a
+     * {@code LocalizedIllegalArgumentException}.
+     *
+     * @return A function which parses {@code JSON} values.
+     */
+    public static Function<ByteString, Object, LocalizedIllegalArgumentException> byteStringToJson() {
+        return BYTESTRING_TO_JSON;
+    }
+
+    /**
+     * Returns a function which converts a JSON {@code Object} to a {@code ByteString}.
+     *
+     * @return A function which converts a JSON {@code Object} to a {@code ByteString}.
+     */
+    public static Function<Object, ByteString, JsonProcessingException> jsonToByteString() {
+        return JSON_TO_BYTESTRING;
+    }
+
     private JsonSchema() {
         // Prevent instantiation.
     }
diff --git a/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/schema/JsonSyntaxImpl.java b/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/schema/JsonSyntaxImpl.java
index 76cfd9c..19523b8 100644
--- a/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/schema/JsonSyntaxImpl.java
+++ b/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/schema/JsonSyntaxImpl.java
@@ -16,13 +16,12 @@
 package org.forgerock.opendj.rest2ldap.schema;
 
 import static org.forgerock.opendj.rest2ldap.Rest2ldapMessages.ERR_JSON_EMPTY_CONTENT;
-import static org.forgerock.opendj.rest2ldap.Rest2ldapMessages.ERR_JSON_IO_ERROR;
-import static org.forgerock.opendj.rest2ldap.Rest2ldapMessages.ERR_JSON_PARSE_ERROR;
 import static org.forgerock.opendj.rest2ldap.Rest2ldapMessages.ERR_JSON_TRAILING_CONTENT;
 import static org.forgerock.opendj.rest2ldap.schema.JsonSchema.EMR_CASE_IGNORE_JSON_QUERY_OID;
 import static org.forgerock.opendj.rest2ldap.schema.JsonSchema.VALIDATION_POLICY;
 import static org.forgerock.opendj.rest2ldap.schema.JsonSchema.ValidationPolicy.DISABLED;
 import static org.forgerock.opendj.rest2ldap.schema.JsonSchema.SYNTAX_JSON_DESCRIPTION;
+import static org.forgerock.opendj.rest2ldap.schema.JsonSchema.jsonParsingException;
 
 import java.io.IOException;
 import java.io.InputStream;
@@ -34,7 +33,6 @@
 import org.forgerock.opendj.rest2ldap.schema.JsonSchema.ValidationPolicy;
 
 import com.fasterxml.jackson.core.JsonParser;
-import com.fasterxml.jackson.core.JsonProcessingException;
 import com.fasterxml.jackson.core.JsonToken;
 
 /** This class implements the JSON attribute syntax. */
@@ -110,13 +108,8 @@
                 return false;
             }
             return true;
-        } catch (JsonProcessingException e) {
-            invalidReason.append(ERR_JSON_PARSE_ERROR.get(e.getLocation().getLineNr(),
-                                                          e.getLocation().getColumnNr(),
-                                                          e.getOriginalMessage()));
-            return false;
         } catch (IOException e) {
-            invalidReason.append(ERR_JSON_IO_ERROR.get(e.getMessage()));
+            invalidReason.append(jsonParsingException(e));
             return false;
         }
     }

--
Gitblit v1.10.0