From 507e00fb190713b1654579123d284bcd3d750abe Mon Sep 17 00:00:00 2001
From: Matthew Swift <matthew.swift@forgerock.com>
Date: Wed, 10 Apr 2013 10:31:19 +0000
Subject: [PATCH] Partial fix for OPENDJ-693: Implement modify/update support
---
opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/ReferenceAttributeMapper.java | 276 +++++++++++++++++++++---------------------------------
1 files changed, 107 insertions(+), 169 deletions(-)
diff --git a/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/ReferenceAttributeMapper.java b/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/ReferenceAttributeMapper.java
index 51b3a09..3ac297f 100644
--- a/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/ReferenceAttributeMapper.java
+++ b/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/ReferenceAttributeMapper.java
@@ -15,16 +15,15 @@
*/
package org.forgerock.opendj.rest2ldap;
-import static java.util.Collections.singletonList;
import static org.forgerock.opendj.ldap.ErrorResultException.newErrorResult;
+import static org.forgerock.opendj.ldap.requests.Requests.newSearchRequest;
import static org.forgerock.opendj.rest2ldap.Rest2LDAP.asResourceException;
import static org.forgerock.opendj.rest2ldap.Utils.accumulate;
import static org.forgerock.opendj.rest2ldap.Utils.ensureNotNull;
+import static org.forgerock.opendj.rest2ldap.Utils.i18n;
import static org.forgerock.opendj.rest2ldap.Utils.transform;
-import static org.forgerock.opendj.rest2ldap.WritabilityPolicy.READ_WRITE;
import java.util.ArrayList;
-import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
@@ -39,6 +38,7 @@
import org.forgerock.json.resource.ResultHandler;
import org.forgerock.opendj.ldap.Attribute;
import org.forgerock.opendj.ldap.AttributeDescription;
+import org.forgerock.opendj.ldap.Attributes;
import org.forgerock.opendj.ldap.ByteString;
import org.forgerock.opendj.ldap.DN;
import org.forgerock.opendj.ldap.Entry;
@@ -47,11 +47,11 @@
import org.forgerock.opendj.ldap.Filter;
import org.forgerock.opendj.ldap.Function;
import org.forgerock.opendj.ldap.LinkedAttribute;
+import org.forgerock.opendj.ldap.Modification;
import org.forgerock.opendj.ldap.MultipleEntriesFoundException;
import org.forgerock.opendj.ldap.ResultCode;
import org.forgerock.opendj.ldap.SearchResultHandler;
import org.forgerock.opendj.ldap.SearchScope;
-import org.forgerock.opendj.ldap.requests.Requests;
import org.forgerock.opendj.ldap.requests.SearchRequest;
import org.forgerock.opendj.ldap.responses.Result;
import org.forgerock.opendj.ldap.responses.SearchResultEntry;
@@ -61,7 +61,8 @@
* An attribute mapper which provides a mapping from a JSON value to a single DN
* valued LDAP attribute.
*/
-public final class ReferenceAttributeMapper extends AttributeMapper {
+public final class ReferenceAttributeMapper extends
+ AbstractLDAPAttributeMapper<ReferenceAttributeMapper> {
/**
* The maximum number of candidate references to allow in search filters.
*/
@@ -69,45 +70,19 @@
private final DN baseDN;
private Filter filter = null;
- private boolean isRequired = false;
- private boolean isSingleValued = false;
- private final AttributeDescription ldapAttributeName;
private final AttributeMapper mapper;
private final AttributeDescription primaryKey;
private SearchScope scope = SearchScope.WHOLE_SUBTREE;
- private WritabilityPolicy writabilityPolicy = READ_WRITE;
ReferenceAttributeMapper(final AttributeDescription ldapAttributeName, final DN baseDN,
final AttributeDescription primaryKey, final AttributeMapper mapper) {
- this.ldapAttributeName = ldapAttributeName;
+ super(ldapAttributeName);
this.baseDN = baseDN;
this.primaryKey = primaryKey;
this.mapper = mapper;
}
/**
- * Indicates that the LDAP attribute is mandatory and must be provided
- * during create requests.
- *
- * @return This attribute mapper.
- */
- public ReferenceAttributeMapper isRequired() {
- this.isRequired = true;
- return this;
- }
-
- /**
- * Indicates that multi-valued LDAP attribute should be represented as a
- * single-valued JSON value, rather than an array of values.
- *
- * @return This attribute mapper.
- */
- public ReferenceAttributeMapper isSingleValued() {
- this.isSingleValued = true;
- return this;
- }
-
- /**
* Sets the filter which should be used when searching for referenced LDAP
* entries. The default is {@code (objectClass=*)}.
*
@@ -148,30 +123,12 @@
return this;
}
- /**
- * Indicates whether or not the LDAP attribute supports updates. The default
- * is {@link WritabilityPolicy#READ_WRITE}.
- *
- * @param policy
- * The writability policy.
- * @return This attribute mapper.
- */
- public ReferenceAttributeMapper writability(final WritabilityPolicy policy) {
- this.writabilityPolicy = policy;
- return this;
- }
-
@Override
- void getLDAPAttributes(final Context c, final JsonPointer jsonAttribute,
- final Set<String> ldapAttributes) {
- ldapAttributes.add(ldapAttributeName.toString());
- }
-
- @Override
- void getLDAPFilter(final Context c, final FilterType type, final JsonPointer jsonAttribute,
- final String operator, final Object valueAssertion, final ResultHandler<Filter> h) {
+ void getLDAPFilter(final Context c, final JsonPointer path, final JsonPointer subPath,
+ final FilterType type, final String operator, final Object valueAssertion,
+ final ResultHandler<Filter> h) {
// Construct a filter which can be used to find referenced resources.
- mapper.getLDAPFilter(c, type, jsonAttribute, operator, valueAssertion,
+ mapper.getLDAPFilter(c, path, subPath, type, operator, valueAssertion,
new ResultHandler<Filter>() {
@Override
public void handleError(final ResourceException error) {
@@ -224,86 +181,27 @@
}
@Override
- void toJSON(final Context c, final Entry e, final ResultHandler<JsonValue> h) {
- final Attribute attribute = e.getAttribute(ldapAttributeName);
- if (attribute == null || attribute.isEmpty()) {
- h.handleResult(null);
- } else if (attributeIsSingleValued()) {
- try {
- final DN dn = attribute.parse().usingSchema(c.getConfig().schema()).asDN();
- readEntry(c, dn, h);
- } catch (final Exception ex) {
- // The LDAP attribute could not be decoded.
- h.handleError(asResourceException(ex));
- }
- } else {
- try {
- final Set<DN> dns =
- attribute.parse().usingSchema(c.getConfig().schema()).asSetOfDN();
- final ResultHandler<JsonValue> handler =
- accumulate(dns.size(), transform(
- new Function<List<JsonValue>, JsonValue, Void>() {
- @Override
- public JsonValue apply(final List<JsonValue> value, final Void p) {
- if (value.isEmpty()) {
- // No values, so omit the entire JSON object from the resource.
- return null;
- } else {
- // Combine values into a single JSON array.
- final List<Object> result =
- new ArrayList<Object>(value.size());
- for (final JsonValue e : value) {
- result.add(e.getObject());
- }
- return new JsonValue(result);
- }
- }
- }, h));
- for (final DN dn : dns) {
- readEntry(c, dn, handler);
- }
- } catch (final Exception ex) {
- // The LDAP attribute could not be decoded.
- h.handleError(asResourceException(ex));
- }
+ void getNewLDAPAttributes(final Context c, final JsonPointer path,
+ final List<Object> newValues, final ResultHandler<Attribute> h) {
+ // No need to do anything if there are no values.
+ if (newValues.isEmpty()) {
+ h.handleResult(Attributes.emptyAttribute(ldapAttributeName));
+ return;
}
- }
- @Override
- void toLDAP(final Context c, final JsonValue v, final ResultHandler<List<Attribute>> h) {
- try {
- if (v == null || v.isNull()) {
- if (attributeIsRequired()) {
- // FIXME: improve error message.
- throw new BadRequestException("no value provided");
- } else {
- h.handleResult(Collections.<Attribute> emptyList());
- }
- } else if (v.isList() && attributeIsSingleValued()) {
- // FIXME: improve error message.
- throw new BadRequestException("expected single value, but got multiple values");
- } else if (!writabilityPolicy.canCreate(ldapAttributeName)) {
- if (writabilityPolicy.discardWrites()) {
- h.handleResult(Collections.<Attribute> emptyList());
- } else {
- // FIXME: improve error message.
- throw new BadRequestException("attempted to create a read-only value");
- }
- } else {
- /*
- * For each value use the subordinate mapper to obtain the LDAP
- * primary key, the perform a search for each one to find the
- * corresponding entries.
- */
- final JsonValue valueList =
- v.isList() ? v : new JsonValue(singletonList(v.getObject()));
- final Attribute reference = new LinkedAttribute(ldapAttributeName);
- final AtomicInteger pendingSearches = new AtomicInteger(valueList.size());
- final AtomicReference<ResourceException> exception =
- new AtomicReference<ResourceException>();
+ /*
+ * For each value use the subordinate mapper to obtain the LDAP primary
+ * key, the perform a search for each one to find the corresponding
+ * entries.
+ */
+ final Attribute newLDAPAttribute = new LinkedAttribute(ldapAttributeName);
+ final AtomicInteger pendingSearches = new AtomicInteger(newValues.size());
+ final AtomicReference<ResourceException> exception =
+ new AtomicReference<ResourceException>();
- for (final JsonValue value : valueList) {
- mapper.toLDAP(c, value, new ResultHandler<List<Attribute>>() {
+ for (final Object value : newValues) {
+ mapper.toLDAP(c, path, null /* force create */, new JsonValue(value),
+ new ResultHandler<List<Modification>>() {
@Override
public void handleError(final ResourceException error) {
@@ -311,32 +209,29 @@
}
@Override
- public void handleResult(final List<Attribute> result) {
+ public void handleResult(final List<Modification> result) {
Attribute primaryKeyAttribute = null;
- for (final Attribute attribute : result) {
- if (attribute.getAttributeDescription().equals(primaryKey)) {
- primaryKeyAttribute = attribute;
+ for (final Modification modification : result) {
+ if (modification.getAttribute().getAttributeDescription().equals(
+ primaryKey)) {
+ primaryKeyAttribute = modification.getAttribute();
break;
}
}
- if (primaryKeyAttribute == null) {
- // FIXME: improve error message.
- h.handleError(new BadRequestException(
- "reference primary key attribute is missing"));
- return;
- }
- if (primaryKeyAttribute.isEmpty()) {
- // FIXME: improve error message.
+ if (primaryKeyAttribute == null || primaryKeyAttribute.isEmpty()) {
h.handleError(new BadRequestException(
- "reference primary key attribute is empty"));
+ i18n("The request cannot be processed because the reference "
+ + "field '%s' contains a value which does not contain "
+ + "a primary key", path)));
return;
}
if (primaryKeyAttribute.size() > 1) {
- // FIXME: improve error message.
h.handleError(new BadRequestException(
- "reference primary key attribute contains multiple values"));
+ i18n("The request cannot be processed because the reference "
+ + "field '%s' contains a value which contains multiple "
+ + "primary keys", path)));
return;
}
@@ -356,7 +251,7 @@
ResourceException re;
try {
throw error;
- } catch (EntryNotFoundException e) {
+ } catch (final EntryNotFoundException e) {
// FIXME: improve error message.
re =
new BadRequestException(
@@ -364,7 +259,7 @@
+ primaryKeyValue
.toString()
+ "' does not exist");
- } catch (MultipleEntriesFoundException e) {
+ } catch (final MultipleEntriesFoundException e) {
// FIXME: improve error message.
re =
new BadRequestException(
@@ -372,7 +267,7 @@
+ primaryKeyValue
.toString()
+ "' is ambiguous");
- } catch (ErrorResultException e) {
+ } catch (final ErrorResultException e) {
re = asResourceException(e);
}
exception.compareAndSet(null, re);
@@ -382,8 +277,8 @@
@Override
public void handleResult(
final SearchResultEntry result) {
- synchronized (reference) {
- reference.add(result.getName());
+ synchronized (newLDAPAttribute) {
+ newLDAPAttribute.add(result.getName());
}
completeIfNecessary();
}
@@ -393,40 +288,80 @@
private void completeIfNecessary() {
if (pendingSearches.decrementAndGet() == 0) {
if (exception.get() == null) {
- h.handleResult(singletonList(reference));
+ h.handleResult(newLDAPAttribute);
} else {
h.handleError(exception.get());
}
}
}
});
- }
- }
- } catch (final ResourceException e) {
- h.handleError(e);
- } catch (final Exception e) {
- // FIXME: improve error message.
- h.handleError(new BadRequestException(e.getMessage()));
}
}
- private boolean attributeIsRequired() {
- return isRequired;
+ @Override
+ ReferenceAttributeMapper getThis() {
+ return this;
}
- private boolean attributeIsSingleValued() {
- return isSingleValued || ldapAttributeName.getAttributeType().isSingleValue();
+ @Override
+ void toJSON(final Context c, final JsonPointer path, final Entry e,
+ final ResultHandler<JsonValue> h) {
+ final Attribute attribute = e.getAttribute(ldapAttributeName);
+ if (attribute == null || attribute.isEmpty()) {
+ h.handleResult(null);
+ } else if (attributeIsSingleValued()) {
+ try {
+ final DN dn = attribute.parse().usingSchema(c.getConfig().schema()).asDN();
+ readEntry(c, path, dn, h);
+ } catch (final Exception ex) {
+ // The LDAP attribute could not be decoded.
+ h.handleError(asResourceException(ex));
+ }
+ } else {
+ try {
+ final Set<DN> dns =
+ attribute.parse().usingSchema(c.getConfig().schema()).asSetOfDN();
+ final ResultHandler<JsonValue> handler =
+ accumulate(dns.size(), transform(
+ new Function<List<JsonValue>, JsonValue, Void>() {
+ @Override
+ public JsonValue apply(final List<JsonValue> value, final Void p) {
+ if (value.isEmpty()) {
+ /*
+ * No values, so omit the entire
+ * JSON object from the resource.
+ */
+ return null;
+ } else {
+ // Combine values into a single JSON array.
+ final List<Object> result =
+ new ArrayList<Object>(value.size());
+ for (final JsonValue e : value) {
+ result.add(e.getObject());
+ }
+ return new JsonValue(result);
+ }
+ }
+ }, h));
+ for (final DN dn : dns) {
+ readEntry(c, path, dn, handler);
+ }
+ } catch (final Exception ex) {
+ // The LDAP attribute could not be decoded.
+ h.handleError(asResourceException(ex));
+ }
+ }
}
private SearchRequest createSearchRequest(final Filter result) {
final Filter searchFilter = filter != null ? Filter.and(filter, result) : result;
- final SearchRequest request = Requests.newSearchRequest(baseDN, scope, searchFilter, "1.1");
- return request;
+ return newSearchRequest(baseDN, scope, searchFilter, "1.1");
}
- private void readEntry(final Context c, final DN dn, final ResultHandler<JsonValue> handler) {
+ private void readEntry(final Context c, final JsonPointer path, final DN dn,
+ final ResultHandler<JsonValue> handler) {
final Set<String> requestedLDAPAttributes = new LinkedHashSet<String>();
- mapper.getLDAPAttributes(c, new JsonPointer(), requestedLDAPAttributes);
+ mapper.getLDAPAttributes(c, path, new JsonPointer(), requestedLDAPAttributes);
c.getConnection().readEntryAsync(dn, requestedLDAPAttributes,
new org.forgerock.opendj.ldap.ResultHandler<SearchResultEntry>() {
@@ -435,14 +370,17 @@
if (!(error instanceof EntryNotFoundException)) {
handler.handleError(asResourceException(error));
} else {
- // The referenced entry does not exist so ignore it since it cannot be mapped.
+ /*
+ * The referenced entry does not exist so ignore it
+ * since it cannot be mapped.
+ */
handler.handleResult(null);
}
}
@Override
public void handleResult(final SearchResultEntry result) {
- mapper.toJSON(c, result, handler);
+ mapper.toJSON(c, path, result, handler);
}
});
}
--
Gitblit v1.10.0