| | |
| | | * 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. |
| | | * Copyright 2012-2014 ForgeRock AS. |
| | | */ |
| | | package org.forgerock.opendj.rest2ldap; |
| | | |
| | | import static org.forgerock.opendj.ldap.ErrorResultException.newErrorResult; |
| | | import static org.forgerock.opendj.ldap.requests.Requests.newSearchRequest; |
| | | import static org.forgerock.opendj.rest2ldap.Rest2LDAP.asResourceException; |
| | | import static org.forgerock.opendj.rest2ldap.Utils.accumulate; |
| | | import static org.forgerock.opendj.rest2ldap.Utils.ensureNotNull; |
| | | import static org.forgerock.opendj.rest2ldap.Utils.i18n; |
| | | import static org.forgerock.opendj.rest2ldap.Utils.transform; |
| | | |
| | | import java.util.ArrayList; |
| | | import java.util.LinkedHashSet; |
| | | import java.util.LinkedList; |
| | |
| | | import org.forgerock.opendj.ldap.responses.Result; |
| | | import org.forgerock.opendj.ldap.responses.SearchResultEntry; |
| | | import org.forgerock.opendj.ldap.responses.SearchResultReference; |
| | | import org.forgerock.util.promise.FailureHandler; |
| | | import org.forgerock.util.promise.SuccessHandler; |
| | | |
| | | import static org.forgerock.opendj.ldap.ErrorResultException.*; |
| | | import static org.forgerock.opendj.ldap.requests.Requests.*; |
| | | import static org.forgerock.opendj.rest2ldap.Rest2LDAP.*; |
| | | import static org.forgerock.opendj.rest2ldap.Utils.*; |
| | | |
| | | /** |
| | | * An attribute mapper which provides a mapping from a JSON value to a single DN |
| | | * valued LDAP attribute. |
| | | */ |
| | | public final class ReferenceAttributeMapper extends |
| | | AbstractLDAPAttributeMapper<ReferenceAttributeMapper> { |
| | | public final class ReferenceAttributeMapper extends AbstractLDAPAttributeMapper<ReferenceAttributeMapper> { |
| | | /** |
| | | * The maximum number of candidate references to allow in search filters. |
| | | */ |
| | |
| | | private SearchScope scope = SearchScope.WHOLE_SUBTREE; |
| | | |
| | | ReferenceAttributeMapper(final AttributeDescription ldapAttributeName, final DN baseDN, |
| | | final AttributeDescription primaryKey, final AttributeMapper mapper) { |
| | | final AttributeDescription primaryKey, final AttributeMapper mapper) { |
| | | super(ldapAttributeName); |
| | | this.baseDN = baseDN; |
| | | this.primaryKey = primaryKey; |
| | |
| | | } |
| | | |
| | | @Override |
| | | void getLDAPFilter(final Context c, final JsonPointer path, final JsonPointer subPath, |
| | | final FilterType type, final String operator, final Object valueAssertion, |
| | | final ResultHandler<Filter> h) { |
| | | void getLDAPFilter(final Context c, final JsonPointer path, final JsonPointer subPath, final FilterType type, |
| | | final String operator, final Object valueAssertion, final ResultHandler<Filter> h) { |
| | | // Construct a filter which can be used to find referenced resources. |
| | | mapper.getLDAPFilter(c, path, subPath, type, operator, valueAssertion, |
| | | new ResultHandler<Filter>() { |
| | | mapper.getLDAPFilter(c, path, subPath, type, operator, valueAssertion, new ResultHandler<Filter>() { |
| | | @Override |
| | | public void handleError(final ResourceException error) { |
| | | h.handleError(error); // Propagate. |
| | | } |
| | | |
| | | @Override |
| | | public void handleResult(final Filter result) { |
| | | // Search for all referenced entries and construct a filter. |
| | | final SearchRequest request = createSearchRequest(result); |
| | | final List<Filter> subFilters = new LinkedList<Filter>(); |
| | | |
| | | final FailureHandler<ErrorResultException> failureHandler = |
| | | new FailureHandler<ErrorResultException>() { |
| | | @Override |
| | | public void handleError(ErrorResultException error) { |
| | | h.handleError(asResourceException(error)); // Propagate. |
| | | } |
| | | }; |
| | | |
| | | c.getConnection().searchAsync(request, new SearchResultHandler() { |
| | | @Override |
| | | public void handleError(final ResourceException error) { |
| | | h.handleError(error); // Propagate. |
| | | public boolean handleEntry(final SearchResultEntry entry) { |
| | | if (subFilters.size() < SEARCH_MAX_CANDIDATES) { |
| | | subFilters.add(Filter.equality(ldapAttributeName.toString(), entry.getName())); |
| | | return true; |
| | | } else { |
| | | // No point in continuing - maximum candidates reached. |
| | | return false; |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | public void handleResult(final Filter result) { |
| | | // Search for all referenced entries and construct a filter. |
| | | final SearchRequest request = createSearchRequest(result); |
| | | c.getConnection().searchAsync(request, null, new SearchResultHandler() { |
| | | final List<Filter> subFilters = new LinkedList<Filter>(); |
| | | |
| | | @Override |
| | | public boolean 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(asResourceException(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)); |
| | | } |
| | | } |
| | | }); |
| | | public boolean handleReference(final SearchResultReference reference) { |
| | | // Ignore references. |
| | | return true; |
| | | } |
| | | }); |
| | | }).onSuccess(new SuccessHandler<Result>() { |
| | | @Override |
| | | public void handleResult(Result result) { |
| | | if (subFilters.size() >= SEARCH_MAX_CANDIDATES) { |
| | | failureHandler.handleError(newErrorResult(ResultCode.ADMIN_LIMIT_EXCEEDED)); |
| | | } else if (subFilters.size() == 1) { |
| | | h.handleResult(subFilters.get(0)); |
| | | } else { |
| | | h.handleResult(Filter.or(subFilters)); |
| | | } |
| | | } |
| | | }).onFailure(failureHandler); |
| | | } |
| | | }); |
| | | } |
| | | |
| | | @Override |
| | | void getNewLDAPAttributes(final Context c, final JsonPointer path, |
| | | final List<Object> newValues, final ResultHandler<Attribute> h) { |
| | | void getNewLDAPAttributes(final Context c, final JsonPointer path, final List<Object> newValues, |
| | | final ResultHandler<Attribute> h) { |
| | | /* |
| | | * For each value use the subordinate mapper to obtain the LDAP primary |
| | | * key, the perform a search for each one to find the corresponding |
| | |
| | | */ |
| | | final Attribute newLDAPAttribute = new LinkedAttribute(ldapAttributeName); |
| | | final AtomicInteger pendingSearches = new AtomicInteger(newValues.size()); |
| | | final AtomicReference<ResourceException> exception = |
| | | new AtomicReference<ResourceException>(); |
| | | final AtomicReference<ResourceException> exception = new AtomicReference<ResourceException>(); |
| | | |
| | | for (final Object value : newValues) { |
| | | mapper.create(c, path, new JsonValue(value), new ResultHandler<List<Attribute>>() { |
| | |
| | | |
| | | if (primaryKeyAttribute == null || primaryKeyAttribute.isEmpty()) { |
| | | h.handleError(new BadRequestException(i18n( |
| | | "The request cannot be processed because the reference " |
| | | + "field '%s' contains a value which does not contain " |
| | | + "a primary key", path))); |
| | | "The request cannot be processed because the reference " |
| | | + "field '%s' contains a value which does not contain " + "a primary key", path))); |
| | | return; |
| | | } |
| | | |
| | | if (primaryKeyAttribute.size() > 1) { |
| | | h.handleError(new BadRequestException(i18n( |
| | | "The request cannot be processed because the reference " |
| | | + "field '%s' contains a value which contains multiple " |
| | | + "primary keys", path))); |
| | | "The request cannot be processed because the reference " |
| | | + "field '%s' contains a value which contains multiple " + "primary keys", path))); |
| | | return; |
| | | } |
| | | |
| | |
| | | final ByteString primaryKeyValue = primaryKeyAttribute.firstValue(); |
| | | final Filter filter = Filter.equality(primaryKey.toString(), primaryKeyValue); |
| | | final SearchRequest search = createSearchRequest(filter); |
| | | c.getConnection().searchSingleEntryAsync(search, |
| | | new org.forgerock.opendj.ldap.ResultHandler<SearchResultEntry>() { |
| | | c.getConnection().searchSingleEntryAsync(search).onSuccess(new SuccessHandler<SearchResultEntry>() { |
| | | @Override |
| | | public void handleResult(final SearchResultEntry result) { |
| | | synchronized (newLDAPAttribute) { |
| | | newLDAPAttribute.add(result.getName()); |
| | | } |
| | | completeIfNecessary(); |
| | | } |
| | | }).onFailure(new FailureHandler<ErrorResultException>() { |
| | | @Override |
| | | public void handleError(final ErrorResultException error) { |
| | | ResourceException re; |
| | | try { |
| | | throw error; |
| | | } catch (final EntryNotFoundException e) { |
| | | re = |
| | | new BadRequestException(i18n("The request cannot be processed " |
| | | + "because the resource '%s' " + "referenced in field '%s' does " |
| | | + "not exist", primaryKeyValue.toString(), path)); |
| | | } catch (final MultipleEntriesFoundException e) { |
| | | re = |
| | | new BadRequestException(i18n( |
| | | "The request cannot be processed " + "because the resource '%s' " |
| | | + "referenced in field '%s' is " + "ambiguous", |
| | | primaryKeyValue.toString(), path)); |
| | | } catch (final ErrorResultException e) { |
| | | re = asResourceException(e); |
| | | } |
| | | exception.compareAndSet(null, re); |
| | | completeIfNecessary(); |
| | | } |
| | | |
| | | @Override |
| | | public void handleErrorResult(final ErrorResultException error) { |
| | | ResourceException re; |
| | | try { |
| | | throw error; |
| | | } catch (final EntryNotFoundException e) { |
| | | re = |
| | | new BadRequestException(i18n( |
| | | "The request cannot be processed " |
| | | + "because the resource '%s' " |
| | | + "referenced in field '%s' does " |
| | | + "not exist", primaryKeyValue |
| | | .toString(), path)); |
| | | } catch (final MultipleEntriesFoundException e) { |
| | | re = |
| | | new BadRequestException(i18n( |
| | | "The request cannot be processed " |
| | | + "because the resource '%s' " |
| | | + "referenced in field '%s' is " |
| | | + "ambiguous", primaryKeyValue |
| | | .toString(), path)); |
| | | } catch (final ErrorResultException e) { |
| | | re = asResourceException(e); |
| | | } |
| | | exception.compareAndSet(null, re); |
| | | completeIfNecessary(); |
| | | } |
| | | |
| | | @Override |
| | | public void handleResult(final SearchResultEntry result) { |
| | | synchronized (newLDAPAttribute) { |
| | | newLDAPAttribute.add(result.getName()); |
| | | } |
| | | completeIfNecessary(); |
| | | } |
| | | }); |
| | | }); |
| | | } |
| | | |
| | | private void completeIfNecessary() { |
| | |
| | | } |
| | | |
| | | @Override |
| | | void read(final Context c, final JsonPointer path, final Entry e, |
| | | final ResultHandler<JsonValue> h) { |
| | | void read(final Context c, final JsonPointer path, final Entry e, final ResultHandler<JsonValue> h) { |
| | | final Attribute attribute = e.getAttribute(ldapAttributeName); |
| | | if (attribute == null || attribute.isEmpty()) { |
| | | h.handleResult(null); |
| | |
| | | } |
| | | } else { |
| | | try { |
| | | final Set<DN> dns = |
| | | attribute.parse().usingSchema(c.getConfig().schema()).asSetOfDN(); |
| | | 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)); |
| | | accumulate(dns.size(), transform(new Function<List<JsonValue>, JsonValue, Void>() { |
| | | @Override |
| | | public JsonValue apply(final List<JsonValue> value, final Void p) { |
| | | if (value.isEmpty()) { |
| | | /* |
| | | * No values, so omit the entire JSON object |
| | | * from the resource. |
| | | */ |
| | | return null; |
| | | } else { |
| | | // Combine values into a single JSON array. |
| | | final List<Object> result = new ArrayList<Object>(value.size()); |
| | | for (final JsonValue e : value) { |
| | | result.add(e.getObject()); |
| | | } |
| | | return new JsonValue(result); |
| | | } |
| | | } |
| | | }, h)); |
| | | for (final DN dn : dns) { |
| | | readEntry(c, path, dn, handler); |
| | | } |
| | |
| | | } |
| | | |
| | | private void readEntry(final Context c, final JsonPointer path, final DN dn, |
| | | final ResultHandler<JsonValue> handler) { |
| | | final ResultHandler<JsonValue> handler) { |
| | | final Set<String> requestedLDAPAttributes = new LinkedHashSet<String>(); |
| | | mapper.getLDAPAttributes(c, path, new JsonPointer(), requestedLDAPAttributes); |
| | | c.getConnection().readEntryAsync(dn, requestedLDAPAttributes, |
| | | new org.forgerock.opendj.ldap.ResultHandler<SearchResultEntry>() { |
| | | |
| | | c.getConnection().readEntryAsync(dn, requestedLDAPAttributes) |
| | | .onSuccess(new SuccessHandler<SearchResultEntry>() { |
| | | @Override |
| | | public void handleErrorResult(final ErrorResultException error) { |
| | | public void handleResult(final SearchResultEntry result) { |
| | | mapper.read(c, path, result, handler); |
| | | } |
| | | }).onFailure(new FailureHandler<ErrorResultException>() { |
| | | @Override |
| | | public void handleError(final ErrorResultException error) { |
| | | if (!(error instanceof EntryNotFoundException)) { |
| | | handler.handleError(asResourceException(error)); |
| | | } else { |
| | |
| | | handler.handleResult(null); |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | public void handleResult(final SearchResultEntry result) { |
| | | mapper.read(c, path, result, handler); |
| | | } |
| | | }); |
| | | } |
| | | |