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

Gaetan Boismal
04.00.2015 1df4f51adf614210ca4a9b9728327090ec5ea264
opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/ObjectAttributeMapper.java
@@ -15,6 +15,12 @@
 */
package org.forgerock.opendj.rest2ldap;
import static org.forgerock.json.resource.PatchOperation.operation;
import static org.forgerock.opendj.ldap.Filter.alwaysFalse;
import static org.forgerock.opendj.rest2ldap.Rest2LDAP.asResourceException;
import static org.forgerock.opendj.rest2ldap.Utils.i18n;
import static org.forgerock.opendj.rest2ldap.Utils.toLowerCase;
import java.util.AbstractMap.SimpleImmutableEntry;
import java.util.ArrayList;
import java.util.Collections;
@@ -23,27 +29,20 @@
import java.util.Map;
import java.util.Set;
import org.forgerock.json.fluent.JsonPointer;
import org.forgerock.json.fluent.JsonValue;
import org.forgerock.json.JsonPointer;
import org.forgerock.json.JsonValue;
import org.forgerock.json.resource.BadRequestException;
import org.forgerock.json.resource.PatchOperation;
import org.forgerock.json.resource.ResourceException;
import org.forgerock.json.resource.ResultHandler;
import org.forgerock.opendj.ldap.Attribute;
import org.forgerock.opendj.ldap.Entry;
import org.forgerock.opendj.ldap.Filter;
import org.forgerock.opendj.ldap.Modification;
import org.forgerock.util.Function;
import org.forgerock.util.promise.NeverThrowsException;
import org.forgerock.util.promise.Promise;
import org.forgerock.util.promise.Promises;
import static org.forgerock.json.resource.PatchOperation.*;
import static org.forgerock.opendj.ldap.Filter.*;
import static org.forgerock.opendj.rest2ldap.Rest2LDAP.*;
import static org.forgerock.opendj.rest2ldap.Utils.*;
/**
 * An attribute mapper which maps JSON objects to LDAP attributes.
 */
/** An attribute mapper which maps JSON objects to LDAP attributes. */
public final class ObjectAttributeMapper extends AttributeMapper {
    private static final class Mapping {
@@ -88,8 +87,8 @@
    }
    @Override
    void create(final Context c, final JsonPointer path, final JsonValue v,
            final ResultHandler<List<Attribute>> h) {
    Promise<List<Attribute>, ResourceException> create(
            final RequestState requestState, final JsonPointer path, final JsonValue v) {
        try {
            /*
             * First check that the JSON value is an object and that the fields
@@ -98,66 +97,67 @@
            final Map<String, Mapping> missingMappings = checkMapping(path, v);
            // Accumulate the results of the subordinate mappings.
            final ResultHandler<List<Attribute>> handler = accumulator(h);
            final List<Promise<List<Attribute>, ResourceException>> promises = new ArrayList<>();
            // Invoke mappings for which there are values provided.
            if (v != null && !v.isNull()) {
                for (final Map.Entry<String, Object> me : v.asMap().entrySet()) {
                    final Mapping mapping = getMapping(me.getKey());
                    final JsonValue subValue = new JsonValue(me.getValue());
                    mapping.mapper.create(c, path.child(me.getKey()), subValue, handler);
                    promises.add(mapping.mapper.create(requestState, path.child(me.getKey()), subValue));
                }
            }
            // Invoke mappings for which there were no values provided.
            for (final Mapping mapping : missingMappings.values()) {
                mapping.mapper.create(c, path.child(mapping.name), null, handler);
                promises.add(mapping.mapper.create(requestState, path.child(mapping.name), null));
            }
            return Promises.when(promises)
                           .then(this.<Attribute> accumulateResults());
        } catch (final Exception e) {
            h.handleError(asResourceException(e));
            return Promises.newExceptionPromise(asResourceException(e));
        }
    }
    @Override
    void getLDAPAttributes(final Context c, final JsonPointer path, final JsonPointer subPath,
    void getLDAPAttributes(final RequestState requestState, final JsonPointer path, final JsonPointer subPath,
            final Set<String> ldapAttributes) {
        if (subPath.isEmpty()) {
            // Request all subordinate mappings.
            for (final Mapping mapping : mappings.values()) {
                mapping.mapper.getLDAPAttributes(c, path.child(mapping.name), subPath,
                        ldapAttributes);
                mapping.mapper.getLDAPAttributes(requestState, path.child(mapping.name), subPath, ldapAttributes);
            }
        } else {
            // Request single subordinate mapping.
            final Mapping mapping = getMapping(subPath);
            if (mapping != null) {
                mapping.mapper.getLDAPAttributes(c, path.child(subPath.get(0)), subPath
                        .relativePointer(), ldapAttributes);
                mapping.mapper.getLDAPAttributes(
                        requestState, path.child(subPath.get(0)), subPath.relativePointer(), ldapAttributes);
            }
        }
    }
    @Override
    void getLDAPFilter(final Context c, final JsonPointer path, final JsonPointer subPath,
            final FilterType type, final String operator, final Object valueAssertion,
            final ResultHandler<Filter> h) {
    Promise<Filter, ResourceException> getLDAPFilter(final RequestState requestState, final JsonPointer path,
            final JsonPointer subPath, final FilterType type, final String operator, final Object valueAssertion) {
        final Mapping mapping = getMapping(subPath);
        if (mapping != null) {
            mapping.mapper.getLDAPFilter(c, path.child(subPath.get(0)), subPath.relativePointer(),
                    type, operator, valueAssertion, h);
            return mapping.mapper.getLDAPFilter(requestState, path.child(subPath.get(0)),
                    subPath.relativePointer(), type, operator, valueAssertion);
        } else {
            /*
             * Either the filter targeted the entire object (i.e. it was "/"),
             * or it targeted an unrecognized attribute within the object.
             * Either way, the filter will never match.
             */
            h.handleResult(alwaysFalse());
            return Promises.newResultPromise(alwaysFalse());
        }
    }
    @Override
    void patch(final Context c, final JsonPointer path, final PatchOperation operation,
            final ResultHandler<List<Modification>> h) {
    Promise<List<Modification>, ResourceException> patch(
            final RequestState requestState, final JsonPointer path, final PatchOperation operation) {
        try {
            final JsonPointer field = operation.getField();
            final JsonValue v = operation.getValue();
@@ -168,11 +168,10 @@
                 * by allowing the JSON value to be a partial object and
                 * add/remove/replace only the provided values.
                 */
                final Map<String, Mapping> missingMappings = checkMapping(path, v);
                checkMapping(path, v);
                // Accumulate the results of the subordinate mappings.
                final ResultHandler<List<Modification>> handler =
                        accumulator(mappings.size() - missingMappings.size(), h);
                final List<Promise<List<Modification>, ResourceException>> promises = new ArrayList<>();
                // Invoke mappings for which there are values provided.
                if (!v.isNull()) {
@@ -181,9 +180,12 @@
                        final JsonValue subValue = new JsonValue(me.getValue());
                        final PatchOperation subOperation =
                                operation(operation.getOperation(), field /* empty */, subValue);
                        mapping.mapper.patch(c, path.child(me.getKey()), subOperation, handler);
                        promises.add(mapping.mapper.patch(requestState, path.child(me.getKey()), subOperation));
                    }
                }
                return Promises.when(promises)
                               .then(this.<Modification> accumulateResults());
            } else {
                /*
                 * The patch operation targets a subordinate field. Create a new
@@ -199,92 +201,92 @@
                }
                final PatchOperation subOperation =
                        operation(operation.getOperation(), field.relativePointer(), v);
                mapping.mapper.patch(c, path.child(fieldName), subOperation, h);
                return mapping.mapper.patch(requestState, path.child(fieldName), subOperation);
            }
        } catch (final Exception ex) {
            h.handleError(asResourceException(ex));
            return Promises.newExceptionPromise(asResourceException(ex));
        }
    }
    @Override
    void read(final Context c, final JsonPointer path, final Entry e,
            final ResultHandler<JsonValue> h) {
    Promise<JsonValue, ResourceException> read(final RequestState requestState, final JsonPointer path, final Entry e) {
        /*
         * Use an accumulator which will aggregate the results from the
         * subordinate mappers into a single list. On completion, the
         * accumulator combines the results into a single JSON map object.
         */
        final ResultHandler<Map.Entry<String, JsonValue>> handler =
                accumulate(mappings.size(), transform(
                        new Function<List<Map.Entry<String, JsonValue>>, JsonValue, NeverThrowsException>() {
                            @Override
                            public JsonValue apply(final List<Map.Entry<String, JsonValue>> value) {
                                if (value.isEmpty()) {
                                    /*
                                     * No subordinate attributes, so omit the
                                     * entire JSON object from the resource.
                                     */
                                    return null;
                                } else {
                                    // Combine the sub-attributes into a single JSON object.
                                    final Map<String, Object> result = new LinkedHashMap<>(value.size());
                                    for (final Map.Entry<String, JsonValue> e : value) {
                                        result.put(e.getKey(), e.getValue().getObject());
                                    }
                                    return new JsonValue(result);
                                }
                            }
                        }, h));
        final List<Promise<Map.Entry<String, JsonValue>, ResourceException>> promises =
                new ArrayList<>(mappings.size());
        for (final Mapping mapping : mappings.values()) {
            mapping.mapper.read(c, path.child(mapping.name), e, transform(
                    new Function<JsonValue, Map.Entry<String, JsonValue>, NeverThrowsException>() {
            promises.add(mapping.mapper.read(requestState, path.child(mapping.name), e)
                    .then(new Function<JsonValue, Map.Entry<String, JsonValue>, ResourceException>() {
                        @Override
                        public Map.Entry<String, JsonValue> apply(final JsonValue value) {
                            return value != null ? new SimpleImmutableEntry<String, JsonValue>(
                                    mapping.name, value) : null;
                            return value != null ? new SimpleImmutableEntry<String, JsonValue>(mapping.name, value)
                                                 : null;
                        }
                    }, handler));
                    }));
        }
        return Promises.when(promises)
                .then(new Function<List<Map.Entry<String, JsonValue>>, JsonValue, ResourceException>() {
                    @Override
                    public JsonValue apply(final List<Map.Entry<String, JsonValue>> value) {
                        if (value.isEmpty()) {
                            /*
                             * No subordinate attributes, so omit the entire
                             * JSON object from the resource.
                             */
                            return null;
                        } else {
                            // Combine the sub-attributes into a single JSON object.
                            final Map<String, Object> result = new LinkedHashMap<>(value.size());
                            for (final Map.Entry<String, JsonValue> e : value) {
                                if (e != null) {
                                    result.put(e.getKey(), e.getValue().getObject());
                                }
                            }
                            return new JsonValue(result);
                        }
                    }
                });
    }
    @Override
    void update(final Context c, final JsonPointer path, final Entry e, final JsonValue v,
            final ResultHandler<List<Modification>> h) {
    Promise<List<Modification>, ResourceException> update(
            final RequestState requestState, final JsonPointer path, final Entry e, final JsonValue v) {
        try {
            /*
             * First check that the JSON value is an object and that the fields
             * it contains are known by this mapper.
             */
            // First check that the JSON value is an object and that the fields
            // it contains are known by this mapper.
            final Map<String, Mapping> missingMappings = checkMapping(path, v);
            // Accumulate the results of the subordinate mappings.
            final ResultHandler<List<Modification>> handler = accumulator(h);
            final List<Promise<List<Modification>, ResourceException>> promises = new ArrayList<>();
            // Invoke mappings for which there are values provided.
            if (v != null && !v.isNull()) {
                for (final Map.Entry<String, Object> me : v.asMap().entrySet()) {
                    final Mapping mapping = getMapping(me.getKey());
                    final JsonValue subValue = new JsonValue(me.getValue());
                    mapping.mapper.update(c, path.child(me.getKey()), e, subValue, handler);
                    promises.add(mapping.mapper.update(requestState, path.child(me.getKey()), e, subValue));
                }
            }
            // Invoke mappings for which there were no values provided.
            for (final Mapping mapping : missingMappings.values()) {
                mapping.mapper.update(c, path.child(mapping.name), e, null, handler);
                promises.add(mapping.mapper.update(requestState, path.child(mapping.name), e, null));
            }
            return Promises.when(promises)
                           .then(this.<Modification> accumulateResults());
        } catch (final Exception ex) {
            h.handleError(asResourceException(ex));
            return Promises.newExceptionPromise(asResourceException(ex));
        }
    }
    private <T> ResultHandler<List<T>> accumulator(final ResultHandler<List<T>> h) {
        return accumulator(mappings.size(), h);
    }
    private <T> ResultHandler<List<T>> accumulator(final int size, final ResultHandler<List<T>> h) {
        return accumulate(size, transform(new Function<List<List<T>>, List<T>, NeverThrowsException>() {
    private <T> Function<List<List<T>>, List<T>, ResourceException> accumulateResults() {
        return new Function<List<List<T>>, List<T>, ResourceException>() {
            @Override
            public List<T> apply(final List<List<T>> value) {
                switch (value.size()) {
@@ -300,13 +302,10 @@
                    return attributes;
                }
            }
        }, h));
        };
    }
    /**
     * Fail immediately if the JSON value has the wrong type or contains unknown
     * attributes.
     */
    /** Fail immediately if the JSON value has the wrong type or contains unknown attributes. */
    private Map<String, Mapping> checkMapping(final JsonPointer path, final JsonValue v)
            throws ResourceException {
        final Map<String, Mapping> missingMappings = new LinkedHashMap<>(mappings);