mirror of https://github.com/OpenIdentityPlatform/OpenDJ.git

Matthew Swift
06.33.2016 39a420d9aa3817dbe2dc9eff52464e5b464dbdde
OPENDJ-2860: add support for JSON property mapping in Rest2LDAP

Added support for "json" attribute mapping to Rest2LDAP. Example:

"properties": {
...

"token": {
"type": "json",
"ldapAttribute": "json"
},
...
}

Known issue: it's not possible to PATCH fields within the JSON object.
Instead, the whole object must be replaced.
5 files modified
125 ■■■■ changed files
opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Rest2LdapJsonConfigurator.java 10 ●●●●● patch | view | raw | blame | history
opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Utils.java 29 ●●●●● patch | view | raw | blame | history
opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/schema/JsonQueryEqualityMatchingRuleImpl.java 14 ●●●● patch | view | raw | blame | history
opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/schema/JsonSchema.java 61 ●●●●● patch | view | raw | blame | history
opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/schema/JsonSyntaxImpl.java 11 ●●●● patch | view | raw | blame | history
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();
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) {
                if (isJsonPrimitive(value)) {
            public ByteString apply(final Object value) throws Exception {
                    final Syntax syntax = ad.getAttributeType().getSyntax();
                if (isJsonPrimitive(value)) {
                    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()));
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));
            }
        }
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.
    }
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;
        }
    }