From 693f202e6a2b99f21b9ea208ad70567487a72643 Mon Sep 17 00:00:00 2001
From: Matthew Swift <matthew.swift@forgerock.com>
Date: Fri, 08 Mar 2013 18:20:41 +0000
Subject: [PATCH] Final fix for OPENDJ-699: Implement DN reference mapping
---
opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/ReferenceAttributeMapper.java | 227 +++++++++++++++++++++++++++++++++++++++++++-------------
1 files changed, 174 insertions(+), 53 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 132e76e..4db950a 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,6 +15,7 @@
*/
package org.forgerock.opendj.rest2ldap;
+import static java.util.Collections.singletonList;
import static org.forgerock.opendj.ldap.ErrorResultException.newErrorResult;
import static org.forgerock.opendj.rest2ldap.Utils.accumulate;
import static org.forgerock.opendj.rest2ldap.Utils.adapt;
@@ -28,9 +29,12 @@
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicReference;
import org.forgerock.json.fluent.JsonPointer;
import org.forgerock.json.fluent.JsonValue;
+import org.forgerock.json.resource.BadRequestException;
import org.forgerock.json.resource.ResourceException;
import org.forgerock.json.resource.ResultHandler;
import org.forgerock.opendj.ldap.Attribute;
@@ -40,6 +44,7 @@
import org.forgerock.opendj.ldap.ErrorResultException;
import org.forgerock.opendj.ldap.Filter;
import org.forgerock.opendj.ldap.Function;
+import org.forgerock.opendj.ldap.LinkedAttribute;
import org.forgerock.opendj.ldap.ResultCode;
import org.forgerock.opendj.ldap.SearchResultHandler;
import org.forgerock.opendj.ldap.SearchScope;
@@ -65,13 +70,15 @@
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 AttributeMapper mapper) {
+ final AttributeDescription primaryKey, final AttributeMapper mapper) {
this.ldapAttributeName = ldapAttributeName;
this.baseDN = baseDN;
+ this.primaryKey = primaryKey;
this.mapper = mapper;
}
@@ -160,68 +167,63 @@
@Override
void getLDAPFilter(final Context c, final FilterType type, final JsonPointer jsonAttribute,
final String operator, final Object valueAssertion, final ResultHandler<Filter> h) {
- // First construct a filter which can be used to find referenced resources.
- final ResultHandler<Filter> filterHandler = new ResultHandler<Filter>() {
- @Override
- public void handleError(final ResourceException error) {
- h.handleError(error); // Propagate.
- }
-
- @Override
- public void handleResult(final Filter result) {
- // Now construct a search to find candidate DNs.
- final Filter searchFilter = filter != null ? Filter.and(filter, result) : result;
- final SearchRequest request =
- Requests.newSearchRequest(baseDN, scope, searchFilter, "1.1");
-
- // Create a result handler which will collect the returned entries and construct a search filter.
- final SearchResultHandler searchHandler = new SearchResultHandler() {
- final List<Filter> subFilters = new LinkedList<Filter>();
-
+ // Construct a filter which can be used to find referenced resources.
+ mapper.getLDAPFilter(c, type, jsonAttribute, operator, valueAssertion,
+ new ResultHandler<Filter>() {
@Override
- public boolean handleEntry(final SearchResultEntry entry) {
- if (subFilters.size() < SEARCH_MAX_CANDIDATES) {
- subFilters.add(Filter.equality(ldapAttributeName.toString(), entry
- .getName()));
- return true;
- } else {
- // No point in continuing - maximum candidates reached.
- return false;
- }
+ public void handleError(final ResourceException error) {
+ h.handleError(error); // Propagate.
}
@Override
- public void handleErrorResult(final ErrorResultException error) {
- h.handleError(adapt(error)); // Propagate.
- }
+ public void handleResult(final Filter result) {
+ // Search for all referenced entries and construct a filter.
+ final SearchRequest request = createSearchRequest(result);
+ c.getConnection().searchAsync(request, null, new SearchResultHandler() {
+ final List<Filter> subFilters = new LinkedList<Filter>();
- @Override
- public boolean handleReference(final SearchResultReference reference) {
- // Ignore references.
- return true;
- }
+ @Override
+ public boolean handleEntry(final SearchResultEntry entry) {
+ if (subFilters.size() < SEARCH_MAX_CANDIDATES) {
+ subFilters.add(Filter.equality(ldapAttributeName.toString(),
+ entry.getName()));
+ return true;
+ } else {
+ // No point in continuing - maximum candidates reached.
+ return false;
+ }
+ }
- @Override
- public void handleResult(final Result result) {
- if (subFilters.size() >= SEARCH_MAX_CANDIDATES) {
- handleErrorResult(newErrorResult(ResultCode.ADMIN_LIMIT_EXCEEDED));
- } else if (subFilters.size() == 1) {
- h.handleResult(subFilters.get(0));
- } else {
- h.handleResult(Filter.or(subFilters));
- }
+ @Override
+ public void handleErrorResult(final ErrorResultException error) {
+ h.handleError(adapt(error)); // Propagate.
+ }
+
+ @Override
+ public boolean handleReference(final SearchResultReference reference) {
+ // Ignore references.
+ return true;
+ }
+
+ @Override
+ public void handleResult(final Result result) {
+ if (subFilters.size() >= SEARCH_MAX_CANDIDATES) {
+ handleErrorResult(newErrorResult(ResultCode.ADMIN_LIMIT_EXCEEDED));
+ } else if (subFilters.size() == 1) {
+ h.handleResult(subFilters.get(0));
+ } else {
+ h.handleResult(Filter.or(subFilters));
+ }
+ }
+ });
}
- };
- c.getConnection().searchAsync(request, null, searchHandler);
- }
- };
- mapper.getLDAPFilter(c, type, jsonAttribute, operator, valueAssertion, filterHandler);
+ });
}
@Override
void toJSON(final Context c, final Entry e, final ResultHandler<JsonValue> h) {
final Attribute attribute = e.getAttribute(ldapAttributeName);
- if (attribute == null) {
+ if (attribute == null || attribute.isEmpty()) {
h.handleResult(null);
} else if (attributeIsSingleValued()) {
try {
@@ -266,8 +268,121 @@
@Override
void toLDAP(final Context c, final JsonValue v, final ResultHandler<List<Attribute>> h) {
- // TODO:
- h.handleResult(Collections.<Attribute> emptyList());
+ 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<ErrorResultException> exception =
+ new AtomicReference<ErrorResultException>();
+
+ for (final JsonValue value : valueList) {
+ mapper.toLDAP(c, value, new ResultHandler<List<Attribute>>() {
+
+ @Override
+ public void handleError(final ResourceException error) {
+ h.handleError(error);
+ }
+
+ @Override
+ public void handleResult(final List<Attribute> result) {
+ Attribute primaryKeyAttribute = null;
+ for (final Attribute attribute : result) {
+ if (attribute.getAttributeDescription().equals(primaryKey)) {
+ primaryKeyAttribute = attribute;
+ 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.
+ h.handleError(new BadRequestException(
+ "reference primary key attribute is empty"));
+ return;
+ }
+
+ if (primaryKeyAttribute.size() > 1) {
+ // FIXME: improve error message.
+ h.handleError(new BadRequestException(
+ "reference primary key attribute contains multiple values"));
+ return;
+ }
+
+ // Now search for the referenced entry in to get its DN.
+ final Filter filter =
+ Filter.equality(primaryKey.toString(), primaryKeyAttribute
+ .firstValue());
+ final SearchRequest search = createSearchRequest(filter);
+ c.getConnection()
+ .searchSingleEntryAsync(
+ search,
+ new org.forgerock.opendj.ldap.ResultHandler<SearchResultEntry>() {
+
+ @Override
+ public void handleErrorResult(
+ final ErrorResultException error) {
+ exception.compareAndSet(null, error);
+ completeIfNecessary();
+ }
+
+ @Override
+ public void handleResult(
+ final SearchResultEntry result) {
+ synchronized (reference) {
+ reference.add(result.getName());
+ }
+ completeIfNecessary();
+ }
+ });
+ }
+
+ private void completeIfNecessary() {
+ if (pendingSearches.decrementAndGet() == 0) {
+ if (exception.get() == null) {
+ h.handleResult(singletonList(reference));
+ } else {
+ h.handleError(adapt(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() {
@@ -278,6 +393,12 @@
return isSingleValued || ldapAttributeName.getAttributeType().isSingleValue();
}
+ 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;
+ }
+
private void readEntry(final Context c, final DN dn, final ResultHandler<JsonValue> handler) {
final Set<String> requestedLDAPAttributes = new LinkedHashSet<String>();
mapper.getLDAPAttributes(c, new JsonPointer(), requestedLDAPAttributes);
--
Gitblit v1.10.0