From ae048917cee151bc66adbfca71aff3f0c630dc97 Mon Sep 17 00:00:00 2001
From: Matthew Swift <matthew.swift@forgerock.com>
Date: Tue, 05 Mar 2013 16:05:52 +0000
Subject: [PATCH] Partial fix for OPENDJ-699: Implement DN reference mapping
---
opendj-sdk/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/SimpleAttributeMapper.java | 7
opendj-sdk/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Context.java | 39 ++-
opendj-sdk/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Rest2LDAP.java | 109 ++++++---
opendj-sdk/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Utils.java | 79 +++++-
opendj-sdk/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/LDAPCollectionResourceProvider.java | 194 ++++++-----------
opendj-sdk/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/ReferenceAttributeMapper.java | 177 ++++++++++++++++
opendj-sdk/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/ObjectAttributeMapper.java | 10
7 files changed, 415 insertions(+), 200 deletions(-)
diff --git a/opendj-sdk/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Context.java b/opendj-sdk/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Context.java
index 6e75dde..2a65484 100644
--- a/opendj-sdk/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Context.java
+++ b/opendj-sdk/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Context.java
@@ -15,13 +15,18 @@
*/
package org.forgerock.opendj.rest2ldap;
+import java.io.Closeable;
+import java.util.concurrent.atomic.AtomicReference;
+
import org.forgerock.json.resource.ServerContext;
+import org.forgerock.opendj.ldap.Connection;
/**
* Common context information passed to containers and mappers.
*/
-final class Context {
+final class Context implements Closeable {
private final Config config;
+ private final AtomicReference<Connection> connection = new AtomicReference<Connection>();
private final ServerContext context;
Context(final Config config, final ServerContext context) {
@@ -30,20 +35,32 @@
}
/**
- * Returns the common configuration options.
- *
- * @return The common configuration options.
+ * {@inheritDoc}
*/
- public Config getConfig() {
+ @Override
+ public void close() {
+ final Connection c = connection.getAndSet(null);
+ if (c != null) {
+ c.close();
+ }
+ }
+
+ Config getConfig() {
return config;
}
- /**
- * Returns the commons REST server context passed in with the REST request.
- *
- * @return The commons REST server context passed in with the REST request.
- */
- public ServerContext getServerContext() {
+ Connection getConnection() {
+ return connection.get();
+ }
+
+ ServerContext getServerContext() {
return context;
}
+
+ void setConnection(final Connection connection) {
+ if (!this.connection.compareAndSet(null, connection)) {
+ throw new IllegalStateException("LDAP connection obtained multiple times");
+ }
+ }
+
}
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 fd6742e..05e7764 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
@@ -19,6 +19,7 @@
import static org.forgerock.opendj.ldap.Filter.alwaysTrue;
import static org.forgerock.opendj.rest2ldap.ReadOnUpdatePolicy.CONTROLS;
import static org.forgerock.opendj.rest2ldap.Utils.accumulate;
+import static org.forgerock.opendj.rest2ldap.Utils.adapt;
import static org.forgerock.opendj.rest2ldap.Utils.toFilter;
import static org.forgerock.opendj.rest2ldap.Utils.transform;
@@ -52,25 +53,17 @@
import org.forgerock.json.resource.ServerContext;
import org.forgerock.json.resource.UncategorizedException;
import org.forgerock.json.resource.UpdateRequest;
-import org.forgerock.opendj.ldap.AssertionFailureException;
import org.forgerock.opendj.ldap.Attribute;
-import org.forgerock.opendj.ldap.AuthenticationException;
-import org.forgerock.opendj.ldap.AuthorizationException;
import org.forgerock.opendj.ldap.Connection;
-import org.forgerock.opendj.ldap.ConnectionException;
import org.forgerock.opendj.ldap.ConnectionFactory;
import org.forgerock.opendj.ldap.DN;
import org.forgerock.opendj.ldap.DecodeException;
import org.forgerock.opendj.ldap.Entry;
-import org.forgerock.opendj.ldap.EntryNotFoundException;
import org.forgerock.opendj.ldap.ErrorResultException;
import org.forgerock.opendj.ldap.Filter;
import org.forgerock.opendj.ldap.Function;
-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.TimeoutResultException;
import org.forgerock.opendj.ldap.controls.PostReadRequestControl;
import org.forgerock.opendj.ldap.controls.PostReadResponseControl;
import org.forgerock.opendj.ldap.controls.PreReadResponseControl;
@@ -87,38 +80,51 @@
* 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);
+ }
- private abstract class AbstractRequestCompletionHandler
- <R, H extends org.forgerock.opendj.ldap.ResultHandler<? super R>>
- implements org.forgerock.opendj.ldap.ResultHandler<R> {
- final Connection connection;
- final H resultHandler;
+ @Override
+ public void handleResult(V result) {
+ c.close();
+ handler.handleResult(result);
+ }
+ };
+ }
- AbstractRequestCompletionHandler(final Connection connection, final H resultHandler) {
- this.connection = connection;
- this.resultHandler = resultHandler;
- }
+ 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 final void handleErrorResult(final ErrorResultException error) {
- connection.close();
- resultHandler.handleErrorResult(error);
- }
+ @Override
+ public boolean handleResource(Resource resource) {
+ return handler.handleResource(resource);
+ }
- @Override
- public final void handleResult(final R result) {
- connection.close();
- resultHandler.handleResult(result);
- }
-
+ @Override
+ public void handleResult(QueryResult result) {
+ c.close();
+ handler.handleResult(result);
+ }
+ };
}
private abstract class ConnectionCompletionHandler<R> implements
org.forgerock.opendj.ldap.ResultHandler<Connection> {
private final org.forgerock.opendj.ldap.ResultHandler<? super R> resultHandler;
+ private final Context c;
- ConnectionCompletionHandler(
+ ConnectionCompletionHandler(final Context c,
final org.forgerock.opendj.ldap.ResultHandler<? super R> resultHandler) {
+ this.c = c;
this.resultHandler = resultHandler;
}
@@ -128,36 +134,12 @@
}
@Override
- public abstract void handleResult(Connection connection);
-
- }
-
- private final class RequestCompletionHandler<R> extends
- AbstractRequestCompletionHandler<R, org.forgerock.opendj.ldap.ResultHandler<? super R>> {
- RequestCompletionHandler(final Connection connection,
- final org.forgerock.opendj.ldap.ResultHandler<? super R> resultHandler) {
- super(connection, resultHandler);
- }
- }
-
- private final class SearchRequestCompletionHandler extends
- AbstractRequestCompletionHandler<Result, SearchResultHandler> implements
- SearchResultHandler {
-
- SearchRequestCompletionHandler(final Connection connection,
- final SearchResultHandler resultHandler) {
- super(connection, resultHandler);
+ public final void handleResult(Connection connection) {
+ c.setConnection(connection);
+ chain();
}
- @Override
- public final boolean handleEntry(final SearchResultEntry entry) {
- return resultHandler.handleEntry(entry);
- }
-
- @Override
- public final boolean handleReference(final SearchResultReference reference) {
- return resultHandler.handleReference(reference);
- }
+ abstract void chain();
}
@@ -201,10 +183,12 @@
public void createInstance(final ServerContext context, final CreateRequest request,
final ResultHandler<Resource> handler) {
final Context c = wrap(context);
+ final ResultHandler<Resource> h = wrap(c, handler);
+
attributeMapper.toLDAP(c, request.getContent(), new ResultHandler<List<Attribute>>() {
@Override
public void handleError(final ResourceException error) {
- handler.handleError(error);
+ h.handleError(error);
}
@Override
@@ -220,14 +204,14 @@
nameStrategy.setResourceId(c, getBaseDN(c), request.getNewResourceId(),
addRequest);
} catch (ResourceException e) {
- handler.handleError(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, handler);
+ applyUpdate(c, addRequest, h);
}
});
}
@@ -248,6 +232,7 @@
public void queryCollection(final ServerContext context, final QueryRequest request,
final QueryResultHandler handler) {
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() {
@@ -280,7 +265,7 @@
@Override
public void handleResult(final JsonValue result) {
final Resource resource = new Resource(id, revision, result);
- handler.handleResource(resource);
+ h.handleResource(resource);
pendingResourceCount.decrementAndGet();
completeIfNecessary();
}
@@ -293,7 +278,7 @@
@Override
public void handleErrorResult(final ErrorResultException error) {
- pendingResult.compareAndSet(null, adaptErrorResult(error));
+ pendingResult.compareAndSet(null, adapt(error));
completeIfNecessary();
}
@@ -319,9 +304,9 @@
final ResourceException result = pendingResult.get();
if (result != null && resultSent.compareAndSet(false, true)) {
if (result == SUCCESS) {
- handler.handleResult(new QueryResult());
+ h.handleResult(new QueryResult());
} else {
- handler.handleError(result);
+ h.handleError(result);
}
}
}
@@ -332,23 +317,20 @@
final ResultHandler<Filter> filterHandler = new ResultHandler<Filter>() {
@Override
public void handleError(final ResourceException error) {
- handler.handleError(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()) {
- handler.handleResult(new QueryResult());
+ h.handleResult(new QueryResult());
} else {
final ConnectionCompletionHandler<Result> outerHandler =
- new ConnectionCompletionHandler<Result>(searchHandler) {
+ new ConnectionCompletionHandler<Result>(c, searchHandler) {
@Override
- public void handleResult(final Connection connection) {
- final SearchRequestCompletionHandler innerHandler =
- new SearchRequestCompletionHandler(connection,
- searchHandler);
+ void chain() {
final String[] attributes =
getLDAPAttributes(c, request.getFieldFilters());
final SearchRequest request =
@@ -357,7 +339,7 @@
.alwaysTrue() ? Filter
.objectClassPresent() : ldapFilter,
attributes);
- connection.searchAsync(request, null, innerHandler);
+ c.getConnection().searchAsync(request, null, searchHandler);
}
};
@@ -374,36 +356,33 @@
public void readInstance(final ServerContext context, final String resourceId,
final ReadRequest request, final ResultHandler<Resource> handler) {
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) {
- handler.handleError(adaptErrorResult(error));
+ h.handleError(adapt(error));
}
@Override
public void handleResult(final SearchResultEntry entry) {
- adaptEntry(c, entry, handler);
+ adaptEntry(c, entry, h);
}
};
// The handler which will be invoked
final ConnectionCompletionHandler<SearchResultEntry> outerHandler =
- new ConnectionCompletionHandler<SearchResultEntry>(searchHandler) {
-
+ new ConnectionCompletionHandler<SearchResultEntry>(c, searchHandler) {
@Override
- public void handleResult(final Connection connection) {
- final RequestCompletionHandler<SearchResultEntry> innerHandler =
- new RequestCompletionHandler<SearchResultEntry>(connection,
- searchHandler);
+ void chain() {
final String[] attributes = getLDAPAttributes(c, request.getFieldFilters());
final SearchRequest request =
nameStrategy.createSearchRequest(c, getBaseDN(c), resourceId)
.addAttribute(attributes);
- connection.searchSingleEntryAsync(request, innerHandler);
+ c.getConnection().searchSingleEntryAsync(request, searchHandler);
}
};
@@ -429,56 +408,15 @@
}, handler));
}
- /**
- * Adapts an LDAP result code to a resource exception.
- *
- * @param error
- * The LDAP error that should be adapted.
- * @return The equivalent resource exception.
- */
- private ResourceException adaptErrorResult(final ErrorResultException error) {
- int resourceResultCode;
- try {
- throw error;
- } catch (final AssertionFailureException e) {
- resourceResultCode = ResourceException.VERSION_MISMATCH;
- } catch (final AuthenticationException e) {
- resourceResultCode = 401;
- } catch (final AuthorizationException e) {
- resourceResultCode = ResourceException.FORBIDDEN;
- } catch (final ConnectionException e) {
- resourceResultCode = ResourceException.UNAVAILABLE;
- } catch (final EntryNotFoundException e) {
- resourceResultCode = ResourceException.NOT_FOUND;
- } catch (final MultipleEntriesFoundException e) {
- resourceResultCode = ResourceException.INTERNAL_ERROR;
- } catch (final TimeoutResultException e) {
- resourceResultCode = 408;
- } catch (final ErrorResultException e) {
- final ResultCode rc = e.getResult().getResultCode();
- if (rc.equals(ResultCode.ADMIN_LIMIT_EXCEEDED)) {
- resourceResultCode = 413; // Request Entity Too Large
- } else if (rc.equals(ResultCode.SIZE_LIMIT_EXCEEDED)) {
- resourceResultCode = 413; // Request Entity Too Large
- } else {
- resourceResultCode = ResourceException.INTERNAL_ERROR;
- }
- }
- return ResourceException.getException(resourceResultCode, error.getMessage(), error);
- }
-
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>(resultHandler) {
-
+ new ConnectionCompletionHandler<Result>(c, resultHandler) {
@Override
- public void handleResult(final Connection connection) {
- final RequestCompletionHandler<Result> innerHandler =
- new RequestCompletionHandler<Result>(connection, resultHandler);
- connection.applyChangeAsync(request, null, innerHandler);
+ void chain() {
+ c.getConnection().applyChangeAsync(request, null, resultHandler);
}
};
factory.getConnectionAsync(outerHandler);
@@ -536,7 +474,7 @@
final Iterator<Filter> i = value.iterator();
while (i.hasNext()) {
final Filter f = i.next();
- if (f == null || f == alwaysFalse()) {
+ if (f == alwaysFalse()) {
return alwaysFalse();
} else if (f == alwaysTrue()) {
i.remove();
@@ -653,7 +591,7 @@
final Iterator<Filter> i = value.iterator();
while (i.hasNext()) {
final Filter f = i.next();
- if (f == null || f == alwaysFalse()) {
+ if (f == alwaysFalse()) {
i.remove();
} else if (f == alwaysTrue()) {
return alwaysTrue();
@@ -702,7 +640,7 @@
new org.forgerock.opendj.ldap.ResultHandler<Result>() {
@Override
public void handleErrorResult(final ErrorResultException error) {
- handler.handleError(adaptErrorResult(error));
+ handler.handleError(adapt(error));
}
@Override
diff --git a/opendj-sdk/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/ObjectAttributeMapper.java b/opendj-sdk/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/ObjectAttributeMapper.java
index 818bd11..0251974 100644
--- a/opendj-sdk/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/ObjectAttributeMapper.java
+++ b/opendj-sdk/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/ObjectAttributeMapper.java
@@ -106,10 +106,6 @@
}
}
- boolean isEmpty() {
- return mappings.isEmpty();
- }
-
@Override
void toJSON(final Context c, final Entry e, final ResultHandler<JsonValue> h) {
// Use an accumulator which will aggregate the results from the subordinate mappers into
@@ -143,7 +139,8 @@
@Override
public Map.Entry<String, JsonValue> apply(final JsonValue value,
final Void p) {
- return new SimpleImmutableEntry<String, JsonValue>(mapping.name, value);
+ return value != null ? new SimpleImmutableEntry<String, JsonValue>(
+ mapping.name, value) : null;
}
}, handler));
}
@@ -179,8 +176,7 @@
case 0:
return Collections.emptyList();
case 1:
- return value.get(0) != null ? value.get(0) : Collections
- .<Attribute> emptyList();
+ return value.get(0);
default:
final List<Attribute> attributes =
new ArrayList<Attribute>(value.size());
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
new file mode 100644
index 0000000..02b6be7
--- /dev/null
+++ b/opendj-sdk/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/ReferenceAttributeMapper.java
@@ -0,0 +1,177 @@
+/*
+ * The contents of this file are subject to the terms of the Common Development and
+ * Distribution License (the License). You may not use this file except in compliance with the
+ * License.
+ *
+ * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
+ * specific language governing permission and limitations under the License.
+ *
+ * When distributing Covered Software, include this CDDL Header Notice in each file and include
+ * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
+ * Header, with the fields enclosed by brackets [] replaced by your own identifying
+ * information: "Portions Copyright [year] [name of copyright owner]".
+ *
+ * Copyright 2012-2013 ForgeRock AS.
+ */
+package org.forgerock.opendj.rest2ldap;
+
+import static org.forgerock.opendj.ldap.Filter.alwaysFalse;
+import static org.forgerock.opendj.rest2ldap.Utils.accumulate;
+import static org.forgerock.opendj.rest2ldap.Utils.adapt;
+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.List;
+import java.util.Set;
+
+import org.forgerock.json.fluent.JsonPointer;
+import org.forgerock.json.fluent.JsonValue;
+import org.forgerock.json.resource.ResultHandler;
+import org.forgerock.opendj.ldap.Attribute;
+import org.forgerock.opendj.ldap.AttributeDescription;
+import org.forgerock.opendj.ldap.DN;
+import org.forgerock.opendj.ldap.Entry;
+import org.forgerock.opendj.ldap.ErrorResultException;
+import org.forgerock.opendj.ldap.Filter;
+import org.forgerock.opendj.ldap.Function;
+import org.forgerock.opendj.ldap.responses.SearchResultEntry;
+
+/**
+ * An attribute mapper which provides a mapping from a JSON value to a single DN
+ * valued LDAP attribute.
+ */
+public final class ReferenceAttributeMapper extends AttributeMapper {
+
+ private boolean isRequired = false;
+ private boolean isSingleValued = false;
+ private final AttributeDescription ldapAttributeName;
+ private final AttributeMapper mapper;
+ private WritabilityPolicy writabilityPolicy = READ_WRITE;
+
+ ReferenceAttributeMapper(final AttributeDescription ldapAttributeName,
+ final AttributeMapper mapper) {
+ this.ldapAttributeName = ldapAttributeName;
+ 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;
+ }
+
+ /**
+ * 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) {
+ // 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());
+ }
+
+ @Override
+ void toJSON(final Context c, final Entry e, final ResultHandler<JsonValue> h) {
+ final Attribute attribute = e.getAttribute(ldapAttributeName);
+ if (attribute == null) {
+ h.handleResult(null);
+ } else if (attributeIsSingleValued()) {
+ final DN dn = attribute.parse().usingSchema(c.getConfig().schema()).asDN();
+ readEntry(c, dn, h);
+ } else {
+ 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);
+ }
+ }
+ }
+
+ @Override
+ void toLDAP(final Context c, final JsonValue v, final ResultHandler<List<Attribute>> h) {
+ // TODO:
+ h.handleResult(Collections.<Attribute> emptyList());
+ }
+
+ private boolean attributeIsRequired() {
+ return isRequired;
+ }
+
+ private boolean attributeIsSingleValued() {
+ return isSingleValued || ldapAttributeName.getAttributeType().isSingleValue();
+ }
+
+ 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);
+ c.getConnection().readEntryAsync(dn, requestedLDAPAttributes,
+ new org.forgerock.opendj.ldap.ResultHandler<SearchResultEntry>() {
+
+ @Override
+ public void handleErrorResult(final ErrorResultException error) {
+ handler.handleError(adapt(error));
+ }
+
+ @Override
+ public void handleResult(final SearchResultEntry result) {
+ mapper.toJSON(c, result, handler);
+ }
+ });
+ }
+
+}
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 97826f5..75ccee5 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
@@ -112,7 +112,7 @@
* configuration. See
* {@link Rest2LDAP#configureConnectionFactory(JsonValue)} for a
* detailed specification of the JSON configuration.
- *
+ *
* @param configuration
* The JSON configuration.
* @return A reference to this builder.
@@ -129,16 +129,16 @@
* configuration. The caller is still required to set the connection
* factory. The configuration should look like this, excluding the
* C-like comments:
- *
+ *
* <pre>
* {
* // The base DN beneath which LDAP entries are to be found.
* "baseDN" : "ou=people,dc=example,dc=com",
- *
+ *
* // The mechanism which should be used for read resources during updates, must be
* // one of "disabled", "controls", or "search".
* "readOnUpdatePolicy" : "controls",
- *
+ *
* // Additional LDAP attributes which should be included with entries during add (create) operations.
* "additionalLDAPAttributes" : [
* {
@@ -149,28 +149,28 @@
* ]
* }
* ],
- *
+ *
* // The strategy which should be used for deriving LDAP entry names from JSON resources.
* "namingStrategy" : {
* // Option 1) the RDN and resource ID are both derived from a single user attribute in the entry.
* "strategy" : "clientDNNaming",
* "dnAttribute" : "uid"
- *
+ *
* // Option 2) the RDN and resource ID are derived from separate user attributes in the entry.
* "strategy" : "clientNaming",
* "dnAttribute" : "uid",
* "idAttribute" : "mail"
- *
+ *
* // Option 3) the RDN and is derived from a user attribute and the resource ID from an operational
* // attribute in the entry.
* "strategy" : "serverNaming",
* "dnAttribute" : "uid",
* "idAttribute" : "entryUUID"
* },
- *
+ *
* // The attribute which will be used for performing MVCC.
* "etagAttribute" : "etag",
- *
+ *
* // The JSON to LDAP attribute mappings.
* "attributes" : {
* "schemas" : { "constant" : [ "urn:scim:schemas:core:1.0" ] },
@@ -182,11 +182,18 @@
* "givenName" : { "simple" : { "ldapAttribute" : "givenName", "isSingleValued" : true } },
* "familyName" : { "simple" : { "ldapAttribute" : "sn", "isSingleValued" : true, "isRequired" : true } },
* },
+ * "manager" : { "reference" : {
+ * "ldapAttribute" : "manager",
+ * "mapping" : { "object" : {
+ * "id" : { "simple" : { "ldapAttribute" : "uid", "isSingleValued" : true } },
+ * "displayName" : { "simple" : { "ldapAttribute" : "cn", "isSingleValued" : true } }
+ * } }
+ * },
* ...
* }
* }
* </pre>
- *
+ *
* @param configuration
* The JSON configuration.
* @return A reference to this builder.
@@ -248,7 +255,7 @@
/**
* Sets the policy which should be used in order to read an entry before
* it is deleted, or after it is added or modified.
- *
+ *
* @param policy
* The policy which should be used in order to read an entry
* before it is deleted, or after it is added or modified.
@@ -262,7 +269,7 @@
/**
* Sets the schema which should be used when attribute types and
* controls.
- *
+ *
* @param schema
* The schema which should be used when attribute types and
* controls.
@@ -354,25 +361,22 @@
if (config.get("isSingleValued").defaultTo(false).asBoolean()) {
s.isSingleValued();
}
- if (config.isDefined("writability")) {
- final String writability = config.get("writability").asString();
- if (writability.equalsIgnoreCase("readOnly")) {
- s.writability(WritabilityPolicy.READ_ONLY);
- } else if (writability.equalsIgnoreCase("readOnlyDiscardWrites")) {
- s.writability(WritabilityPolicy.READ_ONLY_DISCARD_WRITES);
- } else if (writability.equalsIgnoreCase("createOnly")) {
- s.writability(WritabilityPolicy.CREATE_ONLY);
- } else if (writability.equalsIgnoreCase("createOnlyDiscardWrites")) {
- s.writability(WritabilityPolicy.CREATE_ONLY_DISCARD_WRITES);
- } else if (writability.equalsIgnoreCase("readWrite")) {
- s.writability(WritabilityPolicy.READ_WRITE);
- } else {
- throw new JsonValueException(mapper,
- "Illegal writability: must be one of readOnly, readOnlyDiscardWrites, "
- + "createOnly, createOnlyDiscardWrites, or readWrite");
- }
- }
+ s.writability(parseWritability(mapper, config));
return s;
+ } else if (mapper.isDefined("reference")) {
+ final JsonValue config = mapper.get("reference");
+ final AttributeDescription ldapAttribute =
+ ad(config.get("ldapAttribute").required().asString());
+ final AttributeMapper m = configureMapper(config.get("mapper").required());
+ final ReferenceAttributeMapper r = reference(ldapAttribute, m);
+ if (config.get("isRequired").defaultTo(false).asBoolean()) {
+ r.isRequired();
+ }
+ if (config.get("isSingleValued").defaultTo(false).asBoolean()) {
+ r.isSingleValued();
+ }
+ r.writability(parseWritability(mapper, config));
+ return r;
} else if (mapper.isDefined("object")) {
return configureObjectMapper(mapper.get("object"));
} else {
@@ -388,6 +392,29 @@
}
return object;
}
+
+ private WritabilityPolicy parseWritability(final JsonValue mapper, final JsonValue config) {
+ if (config.isDefined("writability")) {
+ final String writability = config.get("writability").asString();
+ if (writability.equalsIgnoreCase("readOnly")) {
+ return WritabilityPolicy.READ_ONLY;
+ } else if (writability.equalsIgnoreCase("readOnlyDiscardWrites")) {
+ return WritabilityPolicy.READ_ONLY_DISCARD_WRITES;
+ } else if (writability.equalsIgnoreCase("createOnly")) {
+ return WritabilityPolicy.CREATE_ONLY;
+ } else if (writability.equalsIgnoreCase("createOnlyDiscardWrites")) {
+ return WritabilityPolicy.CREATE_ONLY_DISCARD_WRITES;
+ } else if (writability.equalsIgnoreCase("readWrite")) {
+ return WritabilityPolicy.READ_WRITE;
+ } else {
+ throw new JsonValueException(mapper,
+ "Illegal writability: must be one of readOnly, readOnlyDiscardWrites, "
+ + "createOnly, createOnlyDiscardWrites, or readWrite");
+ }
+ } else {
+ return WritabilityPolicy.READ_WRITE;
+ }
+ }
}
private static final class AttributeMVCCStrategy extends MVCCStrategy {
@@ -507,7 +534,7 @@
/**
* Creates a new connection factory using the provided JSON configuration.
* The configuration should look like this, excluding the C-like comments:
- *
+ *
* <pre>
* {
* // The primary data center, must contain at least one LDAP server.
@@ -521,7 +548,7 @@
* "port" : 389
* },
* ],
- *
+ *
* // The optional secondary (fail-over) data center.
* "secondaryLDAPServers" : [
* {
@@ -533,18 +560,18 @@
* "port" : 389
* },
* ],
- *
+ *
* // Connection pool configuration.
* "connectionPoolSize" : 10,
* "heartBeatIntervalSeconds" : 30,
- *
+ *
* // SSL/TLS configuration (optional and TBD).
* "useSSL" : {
* // Elect to use StartTLS instead of SSL.
* "useStartTLS" : true,
* ...
* },
- *
+ *
* // Authentication configuration (optional and TBD).
* "authentication" : {
* "bindDN" : "cn=directory manager",
@@ -552,7 +579,7 @@
* },
* }
* </pre>
- *
+ *
* @param configuration
* The JSON configuration.
* @return A new connection factory using the provided JSON configuration.
@@ -616,6 +643,16 @@
return new ObjectAttributeMapper();
}
+ public static ReferenceAttributeMapper reference(final AttributeDescription attribute,
+ final AttributeMapper mapper) {
+ return new ReferenceAttributeMapper(attribute, mapper);
+ }
+
+ public static ReferenceAttributeMapper reference(final String attribute,
+ final AttributeMapper mapper) {
+ return reference(AttributeDescription.valueOf(attribute), mapper);
+ }
+
public static SimpleAttributeMapper simple(final AttributeDescription attribute) {
return new SimpleAttributeMapper(attribute);
}
diff --git a/opendj-sdk/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/SimpleAttributeMapper.java b/opendj-sdk/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/SimpleAttributeMapper.java
index 2597572..c014547 100644
--- a/opendj-sdk/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/SimpleAttributeMapper.java
+++ b/opendj-sdk/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/SimpleAttributeMapper.java
@@ -193,12 +193,13 @@
final Function<ByteString, ?, Void> f =
decoder == null ? fixedFunction(byteStringToJson(), ldapAttributeName) : decoder;
final Object value;
- if (isSingleValued || ldapAttributeName.getAttributeType().isSingleValue()) {
+ if (attributeIsSingleValued()) {
value = e.parseAttribute(ldapAttributeName).as(f, defaultJSONValue);
} else {
- value = e.parseAttribute(ldapAttributeName).asSetOf(f, defaultJSONValues);
+ final Set<Object> s = e.parseAttribute(ldapAttributeName).asSetOf(f, defaultJSONValues);
+ value = s.isEmpty() ? null : s;
}
- h.handleResult(new JsonValue(value));
+ h.handleResult(value != null ? new JsonValue(value) : null);
}
@Override
diff --git a/opendj-sdk/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Utils.java b/opendj-sdk/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Utils.java
index 9c7ef7a..ce51dcc 100644
--- a/opendj-sdk/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Utils.java
+++ b/opendj-sdk/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Utils.java
@@ -36,13 +36,22 @@
import org.forgerock.json.resource.ResourceException;
import org.forgerock.json.resource.ResultHandler;
+import org.forgerock.opendj.ldap.AssertionFailureException;
import org.forgerock.opendj.ldap.Attribute;
import org.forgerock.opendj.ldap.AttributeDescription;
+import org.forgerock.opendj.ldap.AuthenticationException;
+import org.forgerock.opendj.ldap.AuthorizationException;
import org.forgerock.opendj.ldap.ByteString;
+import org.forgerock.opendj.ldap.ConnectionException;
+import org.forgerock.opendj.ldap.EntryNotFoundException;
+import org.forgerock.opendj.ldap.ErrorResultException;
import org.forgerock.opendj.ldap.Filter;
import org.forgerock.opendj.ldap.Function;
import org.forgerock.opendj.ldap.GeneralizedTime;
import org.forgerock.opendj.ldap.LinkedAttribute;
+import org.forgerock.opendj.ldap.MultipleEntriesFoundException;
+import org.forgerock.opendj.ldap.ResultCode;
+import org.forgerock.opendj.ldap.TimeoutResultException;
import org.forgerock.opendj.ldap.schema.Syntax;
/**
@@ -82,8 +91,10 @@
*/
@Override
public void handleResult(final V result) {
- synchronized (results) {
- results.add(result);
+ if (result != null) {
+ synchronized (results) {
+ results.add(result);
+ }
}
if (latch.decrementAndGet() == 0) {
handler.handleResult(results);
@@ -92,22 +103,22 @@
}
- private static final Function<ByteString, String, Void> BYTESTRING_TO_BASE64 =
- new Function<ByteString, String, Void>() {
- @Override
- public String apply(ByteString value, Void p) {
- return value.toBase64String();
- }
- };
-
private static final Function<Object, ByteString, Void> BASE64_TO_BYTESTRING =
new Function<Object, ByteString, Void>() {
@Override
- public ByteString apply(Object value, Void p) {
+ public ByteString apply(final Object value, final Void p) {
return ByteString.valueOfBase64(String.valueOf(value));
}
};
+ private static final Function<ByteString, String, Void> BYTESTRING_TO_BASE64 =
+ new Function<ByteString, String, Void>() {
+ @Override
+ public String apply(final ByteString value, final Void p) {
+ return value.toBase64String();
+ }
+ };
+
private static final Function<ByteString, Object, AttributeDescription> BYTESTRING_TO_JSON =
new Function<ByteString, Object, AttributeDescription>() {
@Override
@@ -149,6 +160,44 @@
return new AccumulatingResultHandler<V>(size, handler);
}
+ /**
+ * Adapts an LDAP result code to a resource exception.
+ *
+ * @param error
+ * The LDAP error that should be adapted.
+ * @return The equivalent resource exception.
+ */
+ static ResourceException adapt(final ErrorResultException error) {
+ int resourceResultCode;
+ try {
+ throw error;
+ } catch (final AssertionFailureException e) {
+ resourceResultCode = ResourceException.VERSION_MISMATCH;
+ } catch (final AuthenticationException e) {
+ resourceResultCode = 401;
+ } catch (final AuthorizationException e) {
+ resourceResultCode = ResourceException.FORBIDDEN;
+ } catch (final ConnectionException e) {
+ resourceResultCode = ResourceException.UNAVAILABLE;
+ } catch (final EntryNotFoundException e) {
+ resourceResultCode = ResourceException.NOT_FOUND;
+ } catch (final MultipleEntriesFoundException e) {
+ resourceResultCode = ResourceException.INTERNAL_ERROR;
+ } catch (final TimeoutResultException e) {
+ resourceResultCode = 408;
+ } catch (final ErrorResultException e) {
+ final ResultCode rc = e.getResult().getResultCode();
+ if (rc.equals(ResultCode.ADMIN_LIMIT_EXCEEDED)) {
+ resourceResultCode = 413; // Request Entity Too Large
+ } else if (rc.equals(ResultCode.SIZE_LIMIT_EXCEEDED)) {
+ resourceResultCode = 413; // Request Entity Too Large
+ } else {
+ resourceResultCode = ResourceException.INTERNAL_ERROR;
+ }
+ }
+ return ResourceException.getException(resourceResultCode, error.getMessage(), error);
+ }
+
static Object attributeToJson(final Attribute a) {
final Function<ByteString, Object, Void> f =
fixedFunction(BYTESTRING_TO_JSON, a.getAttributeDescription());
@@ -157,16 +206,16 @@
return isSingleValued ? a.parse().as(f) : asList(a.parse().asSetOf(f));
}
- static Function<ByteString, Object, AttributeDescription> byteStringToJson() {
- return BYTESTRING_TO_JSON;
+ static Function<Object, ByteString, Void> base64ToByteString() {
+ return BASE64_TO_BYTESTRING;
}
static Function<ByteString, String, Void> byteStringToBase64() {
return BYTESTRING_TO_BASE64;
}
- static Function<Object, ByteString, Void> base64ToByteString() {
- return BASE64_TO_BYTESTRING;
+ static Function<ByteString, Object, AttributeDescription> byteStringToJson() {
+ return BYTESTRING_TO_JSON;
}
static <T> T ensureNotNull(final T object) {
--
Gitblit v1.10.0