From 4ca1423e387874accc55c1d0ffcada3eddb833c5 Mon Sep 17 00:00:00 2001
From: Matthew Swift <matthew.swift@forgerock.com>
Date: Wed, 01 May 2013 00:12:40 +0000
Subject: [PATCH] Partial fix for CREST-3: Add patch support
---
opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/ObjectAttributeMapper.java | 203 +++++++++++++++++++++++++++++++++++++-------------
1 files changed, 149 insertions(+), 54 deletions(-)
diff --git a/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/ObjectAttributeMapper.java b/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/ObjectAttributeMapper.java
index d4ba83f..c05677f 100644
--- a/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/ObjectAttributeMapper.java
+++ b/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/ObjectAttributeMapper.java
@@ -15,7 +15,9 @@
*/
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.accumulate;
import static org.forgerock.opendj.rest2ldap.Utils.i18n;
import static org.forgerock.opendj.rest2ldap.Utils.toLowerCase;
@@ -32,7 +34,10 @@
import org.forgerock.json.fluent.JsonPointer;
import org.forgerock.json.fluent.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.Function;
@@ -80,6 +85,37 @@
}
@Override
+ void create(final Context c, final JsonPointer path, final JsonValue v,
+ final ResultHandler<List<Attribute>> h) {
+ try {
+ /*
+ * 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<Attribute>> handler = accumulator(h);
+
+ // 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);
+ }
+ }
+
+ // 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);
+ }
+ } catch (final Exception e) {
+ h.handleError(asResourceException(e));
+ }
+ }
+
+ @Override
void getLDAPAttributes(final Context c, final JsonPointer path, final JsonPointer subPath,
final Set<String> ldapAttributes) {
if (subPath.isEmpty()) {
@@ -117,7 +153,55 @@
}
@Override
- void toJSON(final Context c, final JsonPointer path, final Entry e,
+ void patch(final Context c, final JsonPointer path, final PatchOperation operation,
+ final ResultHandler<List<Modification>> h) {
+ try {
+ final JsonPointer field = operation.getField();
+ final JsonValue v = operation.getValue();
+
+ if (field.isEmpty()) {
+ /*
+ * The patch operation applies to this object. We'll handle this
+ * by allowing the JSON value to be a partial object and
+ * add/remove/replace only the provided values.
+ */
+ checkMapping(path, operation.getValue());
+
+ // Accumulate the results of the subordinate mappings.
+ final ResultHandler<List<Modification>> handler = accumulator(h);
+
+ // Invoke the sub-mappers using a new patch operation targeted at each field.
+ for (final Map.Entry<String, Object> me : v.asMap().entrySet()) {
+ final Mapping mapping = getMapping(me.getKey());
+ 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);
+ }
+ } else {
+ /*
+ * The patch operation targets a subordinate field. Create a new
+ * patch operation targeting the field and forward it to the
+ * appropriate mapper.
+ */
+ final String fieldName = field.get(0);
+ final Mapping mapping = getMapping(fieldName);
+ if (mapping == null) {
+ throw new BadRequestException(i18n(
+ "The request cannot be processed because it included "
+ + "an unrecognized field '%s'", path.child(fieldName)));
+ }
+ final PatchOperation subOperation =
+ operation(operation.getOperation(), field.relativePointer(), v);
+ mapping.mapper.patch(c, path.child(fieldName), subOperation, h);
+ }
+ } catch (final Exception ex) {
+ h.handleError(asResourceException(ex));
+ }
+ }
+
+ @Override
+ void read(final Context c, final JsonPointer path, final Entry e,
final ResultHandler<JsonValue> h) {
/*
* Use an accumulator which will aggregate the results from the
@@ -152,7 +236,7 @@
}, h));
for (final Mapping mapping : mappings.values()) {
- mapping.mapper.toJSON(c, path.child(mapping.name), e, transform(
+ mapping.mapper.read(c, path.child(mapping.name), e, transform(
new Function<JsonValue, Map.Entry<String, JsonValue>, Void>() {
@Override
public Map.Entry<String, JsonValue> apply(final JsonValue value,
@@ -165,69 +249,80 @@
}
@Override
- void toLDAP(final Context c, final JsonPointer path, final Entry e, final JsonValue v,
+ void update(final Context c, final JsonPointer path, final Entry e, final JsonValue v,
final ResultHandler<List<Modification>> h) {
- /*
- * Fail immediately if the JSON value has the wrong type or contains
- * unknown attributes.
- */
+ try {
+ /*
+ * 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);
+
+ // 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);
+ }
+ }
+
+ // 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);
+ }
+ } catch (final Exception ex) {
+ h.handleError(asResourceException(ex));
+ }
+ }
+
+ private <T> ResultHandler<List<T>> accumulator(final ResultHandler<List<T>> h) {
+ return accumulate(mappings.size(), transform(new Function<List<List<T>>, List<T>, Void>() {
+ @Override
+ public List<T> apply(final List<List<T>> value, final Void p) {
+ switch (value.size()) {
+ case 0:
+ return Collections.emptyList();
+ case 1:
+ return value.get(0);
+ default:
+ final List<T> attributes = new ArrayList<T>(value.size());
+ for (final List<T> a : value) {
+ attributes.addAll(a);
+ }
+ return attributes;
+ }
+ }
+ }, h));
+ }
+
+ /*
+ * 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<String, Mapping>(mappings);
if (v != null && !v.isNull()) {
if (v.isMap()) {
for (final String attribute : v.asMap().keySet()) {
if (missingMappings.remove(toLowerCase(attribute)) == null) {
- h.handleError(new BadRequestException(i18n(
- "The request cannot be processed because the JSON resource "
- + "contains an unrecognized field '%s'", path
- .child(attribute))));
- return;
+ throw new BadRequestException(i18n(
+ "The request cannot be processed because it included "
+ + "an unrecognized field '%s'", path.child(attribute)));
}
}
} else {
- h.handleError(new BadRequestException(i18n(
- "The request cannot be processed because the JSON resource "
- + "contains the field '%s' whose value is the wrong type: "
- + "an object is expected", path)));
- return;
+ throw new BadRequestException(i18n(
+ "The request cannot be processed because it included "
+ + "the field '%s' whose value is the wrong type: "
+ + "an object is expected", path));
}
}
-
- // Accumulate the results of the subordinate mappings.
- final ResultHandler<List<Modification>> handler =
- accumulate(mappings.size(), transform(
- new Function<List<List<Modification>>, List<Modification>, Void>() {
- @Override
- public List<Modification> apply(final List<List<Modification>> value,
- final Void p) {
- switch (value.size()) {
- case 0:
- return Collections.emptyList();
- case 1:
- return value.get(0);
- default:
- final List<Modification> attributes =
- new ArrayList<Modification>(value.size());
- for (final List<Modification> a : value) {
- attributes.addAll(a);
- }
- return attributes;
- }
- }
- }, h));
-
- // 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.toLDAP(c, path.child(me.getKey()), e, subValue, handler);
- }
- }
-
- // Invoke mappings for which there were no values provided.
- for (final Mapping mapping : missingMappings.values()) {
- mapping.mapper.toLDAP(c, path.child(mapping.name), e, null, handler);
- }
+ return missingMappings;
}
private Mapping getMapping(final JsonPointer jsonAttribute) {
--
Gitblit v1.10.0