From a95e7c21293142583e48517c9b30adbf2d4ace73 Mon Sep 17 00:00:00 2001
From: Matthew Swift <matthew.swift@forgerock.com>
Date: Wed, 06 Mar 2013 11:57:52 +0000
Subject: [PATCH] Partial fix for OPENDJ-699: Implement DN reference mapping
---
opendj-sdk/opendj3/opendj-rest2ldap-servlet/src/main/webapp/opendj-rest2ldap-servlet.json | 3
opendj-sdk/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Rest2LDAP.java | 15
opendj-sdk/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/LDAPCollectionResourceProvider.java | 494 +++++++++++++++++++++-----------------------
opendj-sdk/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/ReferenceAttributeMapper.java | 127 ++++++++++
4 files changed, 372 insertions(+), 267 deletions(-)
diff --git a/opendj-sdk/opendj3/opendj-rest2ldap-servlet/src/main/webapp/opendj-rest2ldap-servlet.json b/opendj-sdk/opendj3/opendj-rest2ldap-servlet/src/main/webapp/opendj-rest2ldap-servlet.json
index 457b52b..57af90a 100644
--- a/opendj-sdk/opendj3/opendj-rest2ldap-servlet/src/main/webapp/opendj-rest2ldap-servlet.json
+++ b/opendj-sdk/opendj3/opendj-rest2ldap-servlet/src/main/webapp/opendj-rest2ldap-servlet.json
@@ -46,6 +46,7 @@
} },
"manager" : { "reference" : {
"ldapAttribute" : "manager",
+ "baseDN" : "ou=people,dc=example,dc=com",
"mapper" : { "object" : {
"id" : { "simple" : { "ldapAttribute" : "uid", "isSingleValued" : true } },
"displayName" : { "simple" : { "ldapAttribute" : "cn", "isSingleValued" : true } }
@@ -53,6 +54,7 @@
} },
"groups" : { "reference" : {
"ldapAttribute" : "isMemberOf",
+ "baseDN" : "ou=groups,dc=example,dc=com",
"writability" : "readOnly",
"mapper" : { "object" : {
"id" : { "simple" : { "ldapAttribute" : "cn", "isSingleValued" : true } }
@@ -92,6 +94,7 @@
"displayName" : { "simple" : { "ldapAttribute" : "cn", "isSingleValued" : true, "isRequired" : true, "writability" : "readOnly" } },
"members" : { "reference" : {
"ldapAttribute" : "uniqueMember",
+ "baseDN" : "dc=example,dc=com",
"mapper" : { "object" : {
"id" : { "simple" : { "ldapAttribute" : "uid", "isSingleValued" : true } },
"displayName" : { "simple" : { "ldapAttribute" : "cn", "isSingleValued" : true } }
diff --git a/opendj-sdk/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/LDAPCollectionResourceProvider.java b/opendj-sdk/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/LDAPCollectionResourceProvider.java
index 05e7764..d2aa3fb 100644
--- a/opendj-sdk/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/LDAPCollectionResourceProvider.java
+++ b/opendj-sdk/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/LDAPCollectionResourceProvider.java
@@ -73,81 +73,43 @@
import org.forgerock.opendj.ldap.responses.Result;
import org.forgerock.opendj.ldap.responses.SearchResultEntry;
import org.forgerock.opendj.ldap.responses.SearchResultReference;
-import org.forgerock.opendj.ldif.ChangeRecord;
/**
* A {@code CollectionResourceProvider} implementation which maps a JSON
* resource collection to LDAP entries beneath a base DN.
*/
final class LDAPCollectionResourceProvider implements CollectionResourceProvider {
- private <V> ResultHandler<V> wrap(final Context c, final ResultHandler<V> handler) {
- return new ResultHandler<V>() {
- @Override
- public void handleError(ResourceException error) {
- c.close();
- handler.handleError(error);
- }
-
- @Override
- public void handleResult(V result) {
- c.close();
- handler.handleResult(result);
- }
- };
- }
-
- private QueryResultHandler wrap(final Context c, final QueryResultHandler handler) {
- return new QueryResultHandler() {
- @Override
- public void handleError(ResourceException error) {
- c.close();
- handler.handleError(error);
- }
-
- @Override
- public boolean handleResource(Resource resource) {
- return handler.handleResource(resource);
- }
-
- @Override
- public void handleResult(QueryResult result) {
- c.close();
- handler.handleResult(result);
- }
- };
- }
-
- private abstract class ConnectionCompletionHandler<R> implements
+ private abstract class ConnectionCompletionHandler implements
org.forgerock.opendj.ldap.ResultHandler<Connection> {
- private final org.forgerock.opendj.ldap.ResultHandler<? super R> resultHandler;
private final Context c;
+ private final ResultHandler<?> handler;
- ConnectionCompletionHandler(final Context c,
- final org.forgerock.opendj.ldap.ResultHandler<? super R> resultHandler) {
+ ConnectionCompletionHandler(final Context c, final ResultHandler<?> handler) {
this.c = c;
- this.resultHandler = resultHandler;
+ this.handler = handler;
}
@Override
public final void handleErrorResult(final ErrorResultException error) {
- resultHandler.handleErrorResult(error);
+ handler.handleError(adapt(error));
}
@Override
- public final void handleResult(Connection connection) {
+ public final void handleResult(final Connection connection) {
c.setConnection(connection);
chain();
}
abstract void chain();
-
}
// Dummy exception used for signalling search success.
private static final ResourceException SUCCESS = new UncategorizedException(0, null, null);
private final List<Attribute> additionalLDAPAttributes;
+
private final AttributeMapper attributeMapper;
+
private final DN baseDN; // TODO: support template variables.
private final Config config;
private final ConnectionFactory factory;
@@ -185,33 +147,45 @@
final Context c = wrap(context);
final ResultHandler<Resource> h = wrap(c, handler);
- attributeMapper.toLDAP(c, request.getContent(), new ResultHandler<List<Attribute>>() {
+ // Get the connection, then determine entry content, then perform add.
+ factory.getConnectionAsync(new ConnectionCompletionHandler(c, h) {
@Override
- public void handleError(final ResourceException error) {
- h.handleError(error);
- }
+ void chain() {
+ // Calculate entry content.
+ attributeMapper.toLDAP(c, request.getContent(),
+ new ResultHandler<List<Attribute>>() {
+ @Override
+ public void handleError(final ResourceException error) {
+ h.handleError(error);
+ }
- @Override
- public void handleResult(final List<Attribute> result) {
- final AddRequest addRequest = Requests.newAddRequest(DN.rootDN());
- for (final Attribute attribute : additionalLDAPAttributes) {
- addRequest.addAttribute(attribute);
- }
- for (final Attribute attribute : result) {
- addRequest.addAttribute(attribute);
- }
- try {
- nameStrategy.setResourceId(c, getBaseDN(c), request.getNewResourceId(),
- addRequest);
- } catch (ResourceException e) {
- h.handleError(e);
- return;
- }
- if (config.readOnUpdatePolicy() == CONTROLS) {
- final String[] attributes = getLDAPAttributes(c, request.getFieldFilters());
- addRequest.addControl(PostReadRequestControl.newControl(false, attributes));
- }
- applyUpdate(c, addRequest, h);
+ @Override
+ public void handleResult(final List<Attribute> result) {
+ // Perform add operation.
+ final AddRequest addRequest = Requests.newAddRequest(DN.rootDN());
+ for (final Attribute attribute : additionalLDAPAttributes) {
+ addRequest.addAttribute(attribute);
+ }
+ for (final Attribute attribute : result) {
+ addRequest.addAttribute(attribute);
+ }
+ try {
+ nameStrategy.setResourceId(c, getBaseDN(c), request
+ .getNewResourceId(), addRequest);
+ } catch (final ResourceException e) {
+ h.handleError(e);
+ return;
+ }
+ if (config.readOnUpdatePolicy() == CONTROLS) {
+ final String[] attributes =
+ getLDAPAttributes(c, request.getFieldFilters());
+ addRequest.addControl(PostReadRequestControl.newControl(false,
+ attributes));
+ }
+ c.getConnection().applyChangeAsync(addRequest, null,
+ postUpdateHandler(c, h));
+ }
+ });
}
});
}
@@ -234,122 +208,120 @@
final Context c = wrap(context);
final QueryResultHandler h = wrap(c, handler);
- // The handler which will be invoked for each LDAP search result.
- final SearchResultHandler searchHandler = new SearchResultHandler() {
- private final AtomicInteger pendingResourceCount = new AtomicInteger();
- private final AtomicReference<ResourceException> pendingResult =
- new AtomicReference<ResourceException>();
- private final AtomicBoolean resultSent = new AtomicBoolean();
-
+ // Get the connection, then calculate the search filter, then perform the search.
+ factory.getConnectionAsync(new ConnectionCompletionHandler(c, h) {
@Override
- public boolean handleEntry(final SearchResultEntry entry) {
- /*
- * Search result entries will be returned before the search
- * result/error so the only reason pendingResult will be
- * non-null is if a mapping error has occurred.
- */
- if (pendingResult.get() != null) {
- return false;
- }
-
- final String id = nameStrategy.getResourceId(c, entry);
- final String revision = mvccStrategy.getRevisionFromEntry(c, entry);
- final ResultHandler<JsonValue> mapHandler = new ResultHandler<JsonValue>() {
+ void chain() {
+ // Calculate the filter (this may require the connection).
+ getLDAPFilter(c, request.getQueryFilter(), new ResultHandler<Filter>() {
@Override
- public void handleError(final ResourceException e) {
- pendingResult.compareAndSet(null, e);
- pendingResourceCount.decrementAndGet();
- completeIfNecessary();
+ public void handleError(final ResourceException error) {
+ h.handleError(error);
}
@Override
- public void handleResult(final JsonValue result) {
- final Resource resource = new Resource(id, revision, result);
- h.handleResource(resource);
- pendingResourceCount.decrementAndGet();
- completeIfNecessary();
- }
- };
-
- pendingResourceCount.incrementAndGet();
- attributeMapper.toJSON(c, entry, mapHandler);
- return true;
- }
-
- @Override
- public void handleErrorResult(final ErrorResultException error) {
- pendingResult.compareAndSet(null, adapt(error));
- completeIfNecessary();
- }
-
- @Override
- public boolean handleReference(final SearchResultReference reference) {
- // TODO: should this be classed as an error since rest2ldap
- // assumes entries are all colocated?
- return true;
- }
-
- @Override
- public void handleResult(final Result result) {
- pendingResult.compareAndSet(null, SUCCESS);
- completeIfNecessary();
- }
-
- /*
- * Close out the query result set if there are no more pending
- * resources and the LDAP result has been received.
- */
- private void completeIfNecessary() {
- if (pendingResourceCount.get() == 0) {
- final ResourceException result = pendingResult.get();
- if (result != null && resultSent.compareAndSet(false, true)) {
- if (result == SUCCESS) {
+ public void handleResult(final Filter ldapFilter) {
+ // Avoid performing a search if the filter could not be mapped or if it will never match.
+ if (ldapFilter == null || ldapFilter == alwaysFalse()) {
h.handleResult(new QueryResult());
} else {
- h.handleError(result);
- }
- }
- }
- }
- };
-
- // The handler which will be invoked once the LDAP filter has been transformed.
- final ResultHandler<Filter> filterHandler = new ResultHandler<Filter>() {
- @Override
- public void handleError(final ResourceException error) {
- h.handleError(error);
- }
-
- @Override
- public void handleResult(final Filter ldapFilter) {
- // Avoid performing a search if the filter could not be mapped or if it will never match.
- if (ldapFilter == null || ldapFilter == alwaysFalse()) {
- h.handleResult(new QueryResult());
- } else {
- final ConnectionCompletionHandler<Result> outerHandler =
- new ConnectionCompletionHandler<Result>(c, searchHandler) {
+ // Perform the search.
+ final String[] attributes =
+ getLDAPAttributes(c, request.getFieldFilters());
+ final SearchRequest request =
+ Requests.newSearchRequest(getBaseDN(c),
+ SearchScope.SINGLE_LEVEL, ldapFilter == Filter
+ .alwaysTrue() ? Filter.objectClassPresent()
+ : ldapFilter, attributes);
+ c.getConnection().searchAsync(request, null, new SearchResultHandler() {
+ private final AtomicInteger pendingResourceCount =
+ new AtomicInteger();
+ private final AtomicReference<ResourceException> pendingResult =
+ new AtomicReference<ResourceException>();
+ private final AtomicBoolean resultSent = new AtomicBoolean();
@Override
- void chain() {
- final String[] attributes =
- getLDAPAttributes(c, request.getFieldFilters());
- final SearchRequest request =
- Requests.newSearchRequest(getBaseDN(c),
- SearchScope.SINGLE_LEVEL, ldapFilter == Filter
- .alwaysTrue() ? Filter
- .objectClassPresent() : ldapFilter,
- attributes);
- c.getConnection().searchAsync(request, null, searchHandler);
+ public boolean handleEntry(final SearchResultEntry entry) {
+ /*
+ * Search result entries will be returned
+ * before the search result/error so the
+ * only reason pendingResult will be
+ * non-null is if a mapping error has
+ * occurred.
+ */
+ if (pendingResult.get() != null) {
+ return false;
+ }
+
+ final String id = nameStrategy.getResourceId(c, entry);
+ final String revision =
+ mvccStrategy.getRevisionFromEntry(c, entry);
+ final ResultHandler<JsonValue> mapHandler =
+ new ResultHandler<JsonValue>() {
+ @Override
+ public void handleError(final ResourceException e) {
+ pendingResult.compareAndSet(null, e);
+ pendingResourceCount.decrementAndGet();
+ completeIfNecessary();
+ }
+
+ @Override
+ public void handleResult(final JsonValue result) {
+ final Resource resource =
+ new Resource(id, revision, result);
+ h.handleResource(resource);
+ pendingResourceCount.decrementAndGet();
+ completeIfNecessary();
+ }
+ };
+
+ pendingResourceCount.incrementAndGet();
+ attributeMapper.toJSON(c, entry, mapHandler);
+ return true;
}
- };
+ @Override
+ public void handleErrorResult(final ErrorResultException error) {
+ pendingResult.compareAndSet(null, adapt(error));
+ completeIfNecessary();
+ }
- factory.getConnectionAsync(outerHandler);
- }
+ @Override
+ public boolean handleReference(final SearchResultReference reference) {
+ // TODO: should this be classed as an error since rest2ldap
+ // assumes entries are all colocated?
+ return true;
+ }
+
+ @Override
+ public void handleResult(final Result result) {
+ pendingResult.compareAndSet(null, SUCCESS);
+ completeIfNecessary();
+ }
+
+ /*
+ * Close out the query result set if there are
+ * no more pending resources and the LDAP result
+ * has been received.
+ */
+ private void completeIfNecessary() {
+ if (pendingResourceCount.get() == 0) {
+ final ResourceException result = pendingResult.get();
+ if (result != null && resultSent.compareAndSet(false, true)) {
+ if (result == SUCCESS) {
+ h.handleResult(new QueryResult());
+ } else {
+ h.handleError(result);
+ }
+ }
+ }
+ }
+ });
+ }
+ }
+ });
}
- };
-
- getLDAPFilter(c, request.getQueryFilter(), filterHandler);
+ });
}
@Override
@@ -358,36 +330,29 @@
final Context c = wrap(context);
final ResultHandler<Resource> h = wrap(c, handler);
- // The handler which will be invoked for the LDAP search result.
- final org.forgerock.opendj.ldap.ResultHandler<SearchResultEntry> searchHandler =
- new org.forgerock.opendj.ldap.ResultHandler<SearchResultEntry>() {
- @Override
- public void handleErrorResult(final ErrorResultException error) {
- h.handleError(adapt(error));
- }
+ // Get connection then perform the search.
+ factory.getConnectionAsync(new ConnectionCompletionHandler(c, h) {
+ @Override
+ void chain() {
+ // Do the search.
+ final String[] attributes = getLDAPAttributes(c, request.getFieldFilters());
+ final SearchRequest request =
+ nameStrategy.createSearchRequest(c, getBaseDN(c), resourceId).addAttribute(
+ attributes);
+ c.getConnection().searchSingleEntryAsync(request,
+ new org.forgerock.opendj.ldap.ResultHandler<SearchResultEntry>() {
+ @Override
+ public void handleErrorResult(final ErrorResultException error) {
+ h.handleError(adapt(error));
+ }
- @Override
- public void handleResult(final SearchResultEntry entry) {
- adaptEntry(c, entry, h);
- }
-
- };
-
- // The handler which will be invoked
- final ConnectionCompletionHandler<SearchResultEntry> outerHandler =
- new ConnectionCompletionHandler<SearchResultEntry>(c, searchHandler) {
- @Override
- void chain() {
- final String[] attributes = getLDAPAttributes(c, request.getFieldFilters());
- final SearchRequest request =
- nameStrategy.createSearchRequest(c, getBaseDN(c), resourceId)
- .addAttribute(attributes);
- c.getConnection().searchSingleEntryAsync(request, searchHandler);
- }
-
- };
-
- factory.getConnectionAsync(outerHandler);
+ @Override
+ public void handleResult(final SearchResultEntry entry) {
+ adaptEntry(c, entry, h);
+ }
+ });
+ }
+ });
}
@Override
@@ -408,20 +373,6 @@
}, handler));
}
- private void applyUpdate(final Context c, final ChangeRecord request,
- final ResultHandler<Resource> handler) {
- final org.forgerock.opendj.ldap.ResultHandler<Result> resultHandler =
- postUpdateHandler(c, handler);
- final ConnectionCompletionHandler<Result> outerHandler =
- new ConnectionCompletionHandler<Result>(c, resultHandler) {
- @Override
- void chain() {
- c.getConnection().applyChangeAsync(request, null, resultHandler);
- }
- };
- factory.getConnectionAsync(outerHandler);
- }
-
private DN getBaseDN(final Context context) {
return baseDN;
}
@@ -636,48 +587,83 @@
private org.forgerock.opendj.ldap.ResultHandler<Result> postUpdateHandler(final Context c,
final ResultHandler<Resource> handler) {
// The handler which will be invoked for the LDAP add result.
- final org.forgerock.opendj.ldap.ResultHandler<Result> resultHandler =
- new org.forgerock.opendj.ldap.ResultHandler<Result>() {
- @Override
- public void handleErrorResult(final ErrorResultException error) {
- handler.handleError(adapt(error));
- }
+ return new org.forgerock.opendj.ldap.ResultHandler<Result>() {
+ @Override
+ public void handleErrorResult(final ErrorResultException error) {
+ handler.handleError(adapt(error));
+ }
- @Override
- public void handleResult(final Result result) {
- // FIXME: handle USE_SEARCH policy.
- Entry entry;
- try {
- final PostReadResponseControl postReadControl =
- result.getControl(PostReadResponseControl.DECODER, config
- .decodeOptions());
- if (postReadControl != null) {
- entry = postReadControl.getEntry();
- } else {
- final PreReadResponseControl preReadControl =
- result.getControl(PreReadResponseControl.DECODER, config
- .decodeOptions());
- if (preReadControl != null) {
- entry = preReadControl.getEntry();
- } else {
- entry = null;
- }
- }
- } catch (final DecodeException e) {
- // FIXME: log something?
+ @Override
+ public void handleResult(final Result result) {
+ // FIXME: handle USE_SEARCH policy.
+ Entry entry;
+ try {
+ final PostReadResponseControl postReadControl =
+ result.getControl(PostReadResponseControl.DECODER, config
+ .decodeOptions());
+ if (postReadControl != null) {
+ entry = postReadControl.getEntry();
+ } else {
+ final PreReadResponseControl preReadControl =
+ result.getControl(PreReadResponseControl.DECODER, config
+ .decodeOptions());
+ if (preReadControl != null) {
+ entry = preReadControl.getEntry();
+ } else {
entry = null;
}
- if (entry != null) {
- adaptEntry(c, entry, handler);
- } else {
- final Resource resource =
- new Resource(null, null, new JsonValue(Collections.emptyMap()));
- handler.handleResult(resource);
- }
}
+ } catch (final DecodeException e) {
+ // FIXME: log something?
+ entry = null;
+ }
+ if (entry != null) {
+ adaptEntry(c, entry, handler);
+ } else {
+ final Resource resource =
+ new Resource(null, null, new JsonValue(Collections.emptyMap()));
+ handler.handleResult(resource);
+ }
+ }
- };
- return resultHandler;
+ };
+ }
+
+ private QueryResultHandler wrap(final Context c, final QueryResultHandler handler) {
+ return new QueryResultHandler() {
+ @Override
+ public void handleError(final ResourceException error) {
+ c.close();
+ handler.handleError(error);
+ }
+
+ @Override
+ public boolean handleResource(final Resource resource) {
+ return handler.handleResource(resource);
+ }
+
+ @Override
+ public void handleResult(final QueryResult result) {
+ c.close();
+ handler.handleResult(result);
+ }
+ };
+ }
+
+ private <V> ResultHandler<V> wrap(final Context c, final ResultHandler<V> handler) {
+ return new ResultHandler<V>() {
+ @Override
+ public void handleError(final ResourceException error) {
+ c.close();
+ handler.handleError(error);
+ }
+
+ @Override
+ public void handleResult(final V result) {
+ c.close();
+ handler.handleResult(result);
+ }
+ };
}
private Context wrap(final ServerContext context) {
diff --git a/opendj-sdk/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/ReferenceAttributeMapper.java b/opendj-sdk/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/ReferenceAttributeMapper.java
index 7b52566..132e76e 100644
--- a/opendj-sdk/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/ReferenceAttributeMapper.java
+++ b/opendj-sdk/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/ReferenceAttributeMapper.java
@@ -15,20 +15,23 @@
*/
package org.forgerock.opendj.rest2ldap;
-import static org.forgerock.opendj.ldap.Filter.alwaysFalse;
+import static org.forgerock.opendj.ldap.ErrorResultException.newErrorResult;
import static org.forgerock.opendj.rest2ldap.Utils.accumulate;
import static org.forgerock.opendj.rest2ldap.Utils.adapt;
+import static org.forgerock.opendj.rest2ldap.Utils.ensureNotNull;
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;
import java.util.Set;
import org.forgerock.json.fluent.JsonPointer;
import org.forgerock.json.fluent.JsonValue;
+import org.forgerock.json.resource.ResourceException;
import org.forgerock.json.resource.ResultHandler;
import org.forgerock.opendj.ldap.Attribute;
import org.forgerock.opendj.ldap.AttributeDescription;
@@ -37,23 +40,38 @@
import org.forgerock.opendj.ldap.ErrorResultException;
import org.forgerock.opendj.ldap.Filter;
import org.forgerock.opendj.ldap.Function;
+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;
+import org.forgerock.opendj.ldap.responses.SearchResultReference;
/**
* An attribute mapper which provides a mapping from a JSON value to a single DN
* valued LDAP attribute.
*/
public final class ReferenceAttributeMapper extends AttributeMapper {
+ /**
+ * The maximum number of candidate references to allow in search filters.
+ */
+ private static final int SEARCH_MAX_CANDIDATES = 1000;
+ 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 SearchScope scope = SearchScope.WHOLE_SUBTREE;
private WritabilityPolicy writabilityPolicy = READ_WRITE;
- ReferenceAttributeMapper(final AttributeDescription ldapAttributeName,
+ ReferenceAttributeMapper(final AttributeDescription ldapAttributeName, final DN baseDN,
final AttributeMapper mapper) {
this.ldapAttributeName = ldapAttributeName;
+ this.baseDN = baseDN;
this.mapper = mapper;
}
@@ -80,6 +98,47 @@
}
/**
+ * Sets the filter which should be used when searching for referenced LDAP
+ * entries. The default is {@code (objectClass=*)}.
+ *
+ * @param filter
+ * The filter which should be used when searching for referenced
+ * LDAP entries.
+ * @return This attribute mapper.
+ */
+ public ReferenceAttributeMapper searchFilter(final Filter filter) {
+ this.filter = ensureNotNull(filter);
+ return this;
+ }
+
+ /**
+ * Sets the filter which should be used when searching for referenced LDAP
+ * entries. The default is {@code (objectClass=*)}.
+ *
+ * @param filter
+ * The filter which should be used when searching for referenced
+ * LDAP entries.
+ * @return This attribute mapper.
+ */
+ public ReferenceAttributeMapper searchFilter(final String filter) {
+ return searchFilter(Filter.valueOf(filter));
+ }
+
+ /**
+ * Sets the search scope which should be used when searching for referenced
+ * LDAP entries. The default is {@link SearchScope#WHOLE_SUBTREE}.
+ *
+ * @param scope
+ * The search scope which should be used when searching for
+ * referenced LDAP entries.
+ * @return This attribute mapper.
+ */
+ public ReferenceAttributeMapper searchScope(final SearchScope scope) {
+ this.scope = ensureNotNull(scope);
+ return this;
+ }
+
+ /**
* Indicates whether or not the LDAP attribute supports updates. The default
* is {@link WritabilityPolicy#READ_WRITE}.
*
@@ -101,10 +160,62 @@
@Override
void getLDAPFilter(final Context c, final FilterType type, final JsonPointer jsonAttribute,
final String operator, final Object valueAssertion, final ResultHandler<Filter> h) {
- // TODO: only presence and equality matching will be supported. Equality matching will
- // only work for the primary key (whatever that is) by performing a reverse look up to
- // convert the primary key to a DN.
- h.handleResult(alwaysFalse());
+ // 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>();
+
+ @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 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
@@ -116,7 +227,7 @@
try {
final DN dn = attribute.parse().usingSchema(c.getConfig().schema()).asDN();
readEntry(c, dn, h);
- } catch (Exception ex) {
+ } catch (final Exception ex) {
// The LDAP attribute could not be decoded.
h.handleError(adapt(ex));
}
@@ -146,7 +257,7 @@
for (final DN dn : dns) {
readEntry(c, dn, handler);
}
- } catch (Exception ex) {
+ } catch (final Exception ex) {
// The LDAP attribute could not be decoded.
h.handleError(adapt(ex));
}
diff --git a/opendj-sdk/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Rest2LDAP.java b/opendj-sdk/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Rest2LDAP.java
index 6a9d558..9964e7f 100644
--- a/opendj-sdk/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Rest2LDAP.java
+++ b/opendj-sdk/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Rest2LDAP.java
@@ -184,6 +184,7 @@
* },
* "manager" : { "reference" : {
* "ldapAttribute" : "manager",
+ * "baseDN" : "ou=people,dc=example,dc=com",
* "mapper" : { "object" : {
* "id" : { "simple" : { "ldapAttribute" : "uid", "isSingleValued" : true } },
* "displayName" : { "simple" : { "ldapAttribute" : "cn", "isSingleValued" : true } }
@@ -367,14 +368,18 @@
final JsonValue config = mapper.get("reference");
final AttributeDescription ldapAttribute =
ad(config.get("ldapAttribute").required().asString());
+ final DN baseDN = DN.valueOf(config.get("baseDN").required().asString(), schema);
final AttributeMapper m = configureMapper(config.get("mapper").required());
- final ReferenceAttributeMapper r = reference(ldapAttribute, m);
+ final ReferenceAttributeMapper r = reference(ldapAttribute, baseDN, m);
if (config.get("isRequired").defaultTo(false).asBoolean()) {
r.isRequired();
}
if (config.get("isSingleValued").defaultTo(false).asBoolean()) {
r.isSingleValued();
}
+ if (config.isDefined("searchFilter")) {
+ r.searchFilter(config.get("searchFilter").asString());
+ }
r.writability(parseWritability(mapper, config));
return r;
} else if (mapper.isDefined("object")) {
@@ -644,13 +649,13 @@
}
public static ReferenceAttributeMapper reference(final AttributeDescription attribute,
- final AttributeMapper mapper) {
- return new ReferenceAttributeMapper(attribute, mapper);
+ final DN baseDN, final AttributeMapper mapper) {
+ return new ReferenceAttributeMapper(attribute, baseDN, mapper);
}
- public static ReferenceAttributeMapper reference(final String attribute,
+ public static ReferenceAttributeMapper reference(final String attribute, final String baseDN,
final AttributeMapper mapper) {
- return reference(AttributeDescription.valueOf(attribute), mapper);
+ return reference(AttributeDescription.valueOf(attribute), DN.valueOf(baseDN), mapper);
}
public static SimpleAttributeMapper simple(final AttributeDescription attribute) {
--
Gitblit v1.10.0