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"); } } } 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 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()); opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/ReferenceAttributeMapper.java
New file @@ -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); } }); } } 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); } 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 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) {