| | |
| | | */ |
| | | 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; |
| | |
| | | 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 { |
| | |
| | | } |
| | | |
| | | @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 |
| | |
| | | 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(); |
| | |
| | | * 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()) { |
| | |
| | | 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 |
| | |
| | | } |
| | | 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()) { |
| | |
| | | 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); |