Partial fix for OPENDJ-758 : Implement configurable update policy for simple and default mappers
In addition, simplify attribute mapping APIs.
4 files deleted
2 files added
10 files modified
| | |
| | | package org.forgerock.opendj.rest2ldap; |
| | | |
| | | import java.util.List; |
| | | import java.util.Map; |
| | | import java.util.Set; |
| | | |
| | | import org.forgerock.json.fluent.JsonPointer; |
| | |
| | | |
| | | /** |
| | | * Adds the names of the LDAP attributes required by this attribute mapper |
| | | * which are associated with the provided resource attribute to the provided |
| | | * set. |
| | | * to the provided set. |
| | | * <p> |
| | | * Implementations should only add the names of attributes found in the LDAP |
| | | * entry directly associated with the resource. |
| | |
| | | * @param c |
| | | * The context. |
| | | * @param jsonAttribute |
| | | * The name of the resource attribute requested by the client. |
| | | * The name of the requested sub-attribute within this mapper or |
| | | * root if all attributes associated with this mapper have been |
| | | * requested. |
| | | * @param ldapAttributes |
| | | * The set into which the required LDAP attribute names should be |
| | | * put. |
| | |
| | | * filter representation, invoking a completion handler once the |
| | | * transformation has completed. |
| | | * <p> |
| | | * If this attribute mapper is not responsible for mapping the provided JSON |
| | | * attribute then the result handler's {@link ResultHandler#handleResult |
| | | * handleResult} method must be invoked with the value {@code null}. If this |
| | | * attribute mapper is responsible for mapping the JSON attribute, but an |
| | | * error occurred while constructing the LDAP filter, then the result |
| | | * If an error occurred while constructing the LDAP filter, then the result |
| | | * handler's {@link ResultHandler#handleError handleError} method must be |
| | | * invoked with an appropriate exception indicating the problem which |
| | | * occurred. |
| | |
| | | * @param type |
| | | * The type of REST comparison filter. |
| | | * @param jsonAttribute |
| | | * The name of the resource attribute to be filtered. |
| | | * The name of the targeted sub-attribute within this mapper or |
| | | * root if all attributes associated with this mapper have been |
| | | * targeted by the filter. |
| | | * @param operator |
| | | * The name of the extended operator to use for the comparison, |
| | | * or {@code null} if {@code type} is not |
| | |
| | | String operator, Object valueAssertion, ResultHandler<Filter> h); |
| | | |
| | | /** |
| | | * Transforms attributes contained in the provided LDAP entry to JSON |
| | | * content, invoking a completion handler once the transformation has |
| | | * completed. |
| | | * Maps one or more LDAP attributes to their JSON representation, invoking a |
| | | * completion handler once the transformation has completed. |
| | | * <p> |
| | | * This method is invoked whenever an LDAP entry is converted to a REST |
| | | * resource, i.e. when responding to read, query, create, put, or patch |
| | | * requests. |
| | | * <p> |
| | | * If the LDAP attributes are not present in the entry, perhaps because they |
| | | * are optional, then implementations should invoke the result handler's |
| | | * {@link ResultHandler#handleResult handleResult} method with a result of |
| | | * {@code null}. If the LDAP attributes cannot be mapped for any other |
| | | * reason, perhaps because they are required but missing, or they contain |
| | | * unexpected content, then the result handler's |
| | | * {@link ResultHandler#handleError handleError} method must be invoked with |
| | | * an appropriate exception indicating the problem which occurred. |
| | | * |
| | | * @param c |
| | | * The context. |
| | |
| | | * @param h |
| | | * The result handler. |
| | | */ |
| | | abstract void toJSON(Context c, Entry e, ResultHandler<Map<String, Object>> h); |
| | | abstract void toJSON(Context c, Entry e, ResultHandler<JsonValue> h); |
| | | |
| | | /** |
| | | * Transforms JSON content in the provided JSON value to LDAP attributes, |
| | | * invoking a completion handler once the transformation has completed. |
| | | * Maps a JSON value to one or more LDAP attributes, invoking a completion |
| | | * handler once the transformation has completed. |
| | | * <p> |
| | | * This method is invoked whenever a REST resource is converted to an LDAP |
| | | * entry or LDAP modification, i.e. when performing create, put, or patch |
| | | * requests. |
| | | * <p> |
| | | * If the JSON value corresponding to this mapper is not present in the |
| | | * resource then this method will be invoked with a value of {@code null}. |
| | | * It is the responsibility of the mapper implementation to take appropriate |
| | | * action in this case, perhaps by substituting default LDAP values, or by |
| | | * rejecting the update by invoking the result handler's |
| | | * {@link ResultHandler#handleError handleError} method. |
| | | * |
| | | * @param c |
| | | * The context. |
| | | * @param v |
| | | * The JSON value to be converted to LDAP attributes. |
| | | * The JSON value to be converted to LDAP attributes, which may |
| | | * be {@code null} indicating that the JSON value was not present |
| | | * in the resource. |
| | | * @param h |
| | | * The result handler. |
| | | */ |
| | |
| | | final class Config { |
| | | |
| | | private final Filter falseFilter; |
| | | private final Schema schema; |
| | | private final ReadOnUpdatePolicy readOnUpdatePolicy; |
| | | private final Filter trueFilter; |
| | | private final DecodeOptions options; |
| | | private final ReadOnUpdatePolicy readOnUpdatePolicy; |
| | | private final Schema schema; |
| | | private final Filter trueFilter; |
| | | |
| | | Config(final Filter trueFilter, final Filter falseFilter, |
| | | final ReadOnUpdatePolicy readOnUpdatePolicy, final Schema schema) { |
| | |
| | | } |
| | | |
| | | /** |
| | | * Returns the schema which should be used when attribute types and |
| | | * controls. |
| | | * |
| | | * @return The schema which should be used when attribute types and |
| | | * controls. |
| | | */ |
| | | public Schema schema() { |
| | | return schema; |
| | | } |
| | | |
| | | /** |
| | | * Returns the decoding options which should be used when decoding controls |
| | | * in responses. |
| | | * |
| | |
| | | } |
| | | |
| | | /** |
| | | * Returns the schema which should be used when attribute types and |
| | | * controls. |
| | | * |
| | | * @return The schema which should be used when attribute types and |
| | | * controls. |
| | | */ |
| | | public Schema schema() { |
| | | return schema; |
| | | } |
| | | |
| | | /** |
| | | * {@inheritDoc} |
| | | */ |
| | | @Override |
| | |
| | | |
| | | import java.util.Collections; |
| | | import java.util.List; |
| | | import java.util.Map; |
| | | import java.util.Set; |
| | | |
| | | import org.forgerock.json.fluent.JsonPointer; |
| | |
| | | * An attribute mapper which maps a single JSON attribute to a fixed value. |
| | | */ |
| | | final class JSONConstantAttributeMapper extends AttributeMapper { |
| | | private final String jsonAttributeName; |
| | | private final Object jsonAttributeValue; |
| | | private final JsonValue value; |
| | | |
| | | JSONConstantAttributeMapper(final String attributeName, final Object attributeValue) { |
| | | this.jsonAttributeName = attributeName; |
| | | this.jsonAttributeValue = attributeValue; |
| | | JSONConstantAttributeMapper(final Object value) { |
| | | this.value = new JsonValue(value); |
| | | } |
| | | |
| | | @Override |
| | |
| | | @Override |
| | | void getLDAPFilter(final Context c, final FilterType type, final JsonPointer jsonAttribute, |
| | | final String operator, final Object valueAssertion, final ResultHandler<Filter> h) { |
| | | if (jsonAttribute.size() == 1 && jsonAttribute.get(0).equalsIgnoreCase(jsonAttributeName)) { |
| | | final Filter filter; |
| | | if (type == FilterType.PRESENT) { |
| | | filter = c.getConfig().trueFilter(); |
| | | } else if (jsonAttributeValue instanceof String && valueAssertion instanceof String) { |
| | | final String v1 = toLowerCase((String) jsonAttributeValue); |
| | | final String v2 = toLowerCase((String) valueAssertion); |
| | | switch (type) { |
| | | case CONTAINS: |
| | | filter = |
| | | v1.contains(v2) ? c.getConfig().trueFilter() : c.getConfig() |
| | | .falseFilter(); |
| | | break; |
| | | case STARTS_WITH: |
| | | filter = |
| | | v1.startsWith(v2) ? c.getConfig().trueFilter() : c.getConfig() |
| | | .falseFilter(); |
| | | break; |
| | | default: |
| | | filter = compare(c, type, v1, v2); |
| | | break; |
| | | } |
| | | } else if (jsonAttributeValue instanceof Number && valueAssertion instanceof Number) { |
| | | final Double v1 = ((Number) jsonAttributeValue).doubleValue(); |
| | | final Double v2 = ((Number) valueAssertion).doubleValue(); |
| | | final Filter filter; |
| | | final JsonValue subValue = value.get(jsonAttribute); |
| | | if (subValue == null) { |
| | | filter = c.getConfig().falseFilter(); |
| | | } else if (type == FilterType.PRESENT) { |
| | | filter = c.getConfig().trueFilter(); |
| | | } else if (value.isString() && valueAssertion instanceof String) { |
| | | final String v1 = toLowerCase(value.asString()); |
| | | final String v2 = toLowerCase((String) valueAssertion); |
| | | switch (type) { |
| | | case CONTAINS: |
| | | filter = v1.contains(v2) ? c.getConfig().trueFilter() : c.getConfig().falseFilter(); |
| | | break; |
| | | case STARTS_WITH: |
| | | filter = |
| | | v1.startsWith(v2) ? c.getConfig().trueFilter() : c.getConfig() |
| | | .falseFilter(); |
| | | break; |
| | | default: |
| | | filter = compare(c, type, v1, v2); |
| | | } else if (jsonAttributeValue instanceof Boolean && valueAssertion instanceof Boolean) { |
| | | final Boolean v1 = (Boolean) jsonAttributeValue; |
| | | final Boolean v2 = (Boolean) valueAssertion; |
| | | filter = compare(c, type, v1, v2); |
| | | } else { |
| | | // This attribute mapper is a candidate but it does not match. |
| | | filter = c.getConfig().falseFilter(); |
| | | break; |
| | | } |
| | | h.handleResult(filter); |
| | | } else if (value.isNumber() && valueAssertion instanceof Number) { |
| | | final Double v1 = value.asDouble(); |
| | | final Double v2 = ((Number) valueAssertion).doubleValue(); |
| | | filter = compare(c, type, v1, v2); |
| | | } else if (value.isBoolean() && valueAssertion instanceof Boolean) { |
| | | final Boolean v1 = value.asBoolean(); |
| | | final Boolean v2 = (Boolean) valueAssertion; |
| | | filter = compare(c, type, v1, v2); |
| | | } else { |
| | | // This attribute mapper cannot handle the provided filter component. |
| | | h.handleResult(null); |
| | | // This attribute mapper is a candidate but it does not match. |
| | | filter = c.getConfig().falseFilter(); |
| | | } |
| | | h.handleResult(filter); |
| | | } |
| | | |
| | | @Override |
| | | void toJSON(final Context c, final Entry e, final ResultHandler<Map<String, Object>> h) { |
| | | // FIXME: how do we know if the user requested it??? |
| | | h.handleResult(Collections.singletonMap(jsonAttributeName, jsonAttributeValue)); |
| | | |
| | | void toJSON(final Context c, final Entry e, final ResultHandler<JsonValue> h) { |
| | | h.handleResult(value.copy()); |
| | | } |
| | | |
| | | @Override |
| | | void toLDAP(final Context c, final JsonValue v, final ResultHandler<List<Attribute>> h) { |
| | | h.handleResult(Collections.<Attribute>emptyList()); |
| | | // FIXME: should we check if the provided value matches the constant? |
| | | h.handleResult(Collections.<Attribute> emptyList()); |
| | | } |
| | | |
| | | private <T extends Comparable<T>> Filter compare(final Context c, final FilterType type, |
| | |
| | | import java.util.Iterator; |
| | | import java.util.LinkedHashSet; |
| | | import java.util.List; |
| | | import java.util.Map; |
| | | import java.util.Set; |
| | | import java.util.concurrent.atomic.AtomicBoolean; |
| | | import java.util.concurrent.atomic.AtomicInteger; |
| | |
| | | */ |
| | | final class LDAPCollectionResourceProvider implements CollectionResourceProvider { |
| | | |
| | | private abstract class AbstractRequestCompletionHandler<R, |
| | | H extends org.forgerock.opendj.ldap.ResultHandler<? super R>> |
| | | 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; |
| | |
| | | // 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; |
| | |
| | | |
| | | LDAPCollectionResourceProvider(final DN baseDN, final AttributeMapper mapper, |
| | | final ConnectionFactory factory, final NameStrategy nameStrategy, |
| | | final MVCCStrategy mvccStrategy, final Config config) { |
| | | final MVCCStrategy mvccStrategy, final Config config, |
| | | final List<Attribute> additionalLDAPAttributes) { |
| | | this.baseDN = baseDN; |
| | | this.attributeMapper = mapper; |
| | | this.factory = factory; |
| | | this.config = config; |
| | | this.nameStrategy = nameStrategy; |
| | | this.mvccStrategy = mvccStrategy; |
| | | this.additionalLDAPAttributes = additionalLDAPAttributes; |
| | | } |
| | | |
| | | @Override |
| | |
| | | @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); |
| | | } |
| | | nameStrategy.setResourceId(c, getBaseDN(c), request.getNewResourceId(), addRequest); |
| | | try { |
| | | nameStrategy.setResourceId(c, getBaseDN(c), request.getNewResourceId(), |
| | | addRequest); |
| | | } catch (ResourceException e) { |
| | | handler.handleError(e); |
| | | return; |
| | | } |
| | | if (config.readOnUpdatePolicy() == USE_READ_ENTRY_CONTROLS) { |
| | | final String[] attributes = getLDAPAttributes(c, request.getFieldFilters()); |
| | | addRequest.addControl(PostReadRequestControl.newControl(false, attributes)); |
| | |
| | | |
| | | final String id = nameStrategy.getResourceId(c, entry); |
| | | final String revision = mvccStrategy.getRevisionFromEntry(c, entry); |
| | | final ResultHandler<Map<String, Object>> mapHandler = |
| | | new ResultHandler<Map<String, Object>>() { |
| | | @Override |
| | | public void handleError(final ResourceException e) { |
| | | pendingResult.compareAndSet(null, e); |
| | | pendingResourceCount.decrementAndGet(); |
| | | completeIfNecessary(); |
| | | } |
| | | 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 Map<String, Object> result) { |
| | | final Resource resource = |
| | | new Resource(id, revision, new JsonValue(result)); |
| | | handler.handleResource(resource); |
| | | pendingResourceCount.decrementAndGet(); |
| | | completeIfNecessary(); |
| | | } |
| | | }; |
| | | @Override |
| | | public void handleResult(final JsonValue result) { |
| | | final Resource resource = new Resource(id, revision, result); |
| | | handler.handleResource(resource); |
| | | pendingResourceCount.decrementAndGet(); |
| | | completeIfNecessary(); |
| | | } |
| | | }; |
| | | |
| | | pendingResourceCount.incrementAndGet(); |
| | | attributeMapper.toJSON(c, entry, mapHandler); |
| | |
| | | final ResultHandler<Resource> handler) { |
| | | final String actualResourceId = nameStrategy.getResourceId(c, entry); |
| | | final String revision = mvccStrategy.getRevisionFromEntry(c, entry); |
| | | final ResultHandler<Map<String, Object>> mapHandler = |
| | | new ResultHandler<Map<String, Object>>() { |
| | | @Override |
| | | public void handleError(final ResourceException e) { |
| | | handler.handleError(e); |
| | | } |
| | | |
| | | @Override |
| | | public void handleResult(final Map<String, Object> result) { |
| | | final Resource resource = |
| | | new Resource(actualResourceId, revision, new JsonValue(result)); |
| | | handler.handleResult(resource); |
| | | } |
| | | }; |
| | | attributeMapper.toJSON(c, entry, mapHandler); |
| | | attributeMapper.toJSON(c, entry, transform(new Function<JsonValue, Resource, Void>() { |
| | | @Override |
| | | public Resource apply(final JsonValue value, final Void p) { |
| | | return new Resource(actualResourceId, revision, new JsonValue(value)); |
| | | } |
| | | }, handler)); |
| | | } |
| | | |
| | | /** |
| | |
| | | return ResourceException.getException(resourceResultCode, null, 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) { |
| | | |
| | | @Override |
| | | public void handleResult(final Connection connection) { |
| | | final RequestCompletionHandler<Result> innerHandler = |
| | | new RequestCompletionHandler<Result>(connection, resultHandler); |
| | | connection.applyChangeAsync(request, null, innerHandler); |
| | | } |
| | | }; |
| | | factory.getConnectionAsync(outerHandler); |
| | | } |
| | | |
| | | private DN getBaseDN(final Context context) { |
| | | return baseDN; |
| | | } |
| | |
| | | queryFilter.accept(visitor, h); |
| | | } |
| | | |
| | | private Context wrap(final ServerContext context) { |
| | | return new Context(config, context); |
| | | } |
| | | |
| | | 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. |
| | |
| | | // FIXME: handle USE_SEARCH policy. |
| | | Entry entry; |
| | | try { |
| | | PostReadResponseControl postReadControl = |
| | | final PostReadResponseControl postReadControl = |
| | | result.getControl(PostReadResponseControl.DECODER, config |
| | | .decodeOptions()); |
| | | if (postReadControl != null) { |
| | | entry = postReadControl.getEntry(); |
| | | } else { |
| | | PreReadResponseControl preReadControl = |
| | | final PreReadResponseControl preReadControl = |
| | | result.getControl(PreReadResponseControl.DECODER, config |
| | | .decodeOptions()); |
| | | if (preReadControl != null) { |
| | |
| | | entry = null; |
| | | } |
| | | } |
| | | } catch (DecodeException e) { |
| | | } catch (final DecodeException e) { |
| | | // FIXME: log something? |
| | | entry = null; |
| | | } |
| | |
| | | return resultHandler; |
| | | } |
| | | |
| | | 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) { |
| | | |
| | | @Override |
| | | public void handleResult(final Connection connection) { |
| | | final RequestCompletionHandler<Result> innerHandler = |
| | | new RequestCompletionHandler<Result>(connection, resultHandler); |
| | | connection.applyChangeAsync(request, null, innerHandler); |
| | | } |
| | | }; |
| | | factory.getConnectionAsync(outerHandler); |
| | | private Context wrap(final ServerContext context) { |
| | | return new Context(config, context); |
| | | } |
| | | } |
| | |
| | | } |
| | | |
| | | /** |
| | | * Retrieves the revision value (etag) from the provided LDAP entry. |
| | | * |
| | | * @param c |
| | | * The context. |
| | | * @param entry |
| | | * The LDAP entry. |
| | | * @return The revision value. |
| | | */ |
| | | abstract String getRevisionFromEntry(Context c, Entry entry); |
| | | |
| | | /** |
| | | * Adds the name of any LDAP attribute required by this MVCC strategy to the |
| | | * provided set. |
| | | * |
| | |
| | | */ |
| | | abstract void getLDAPAttributes(Context c, Set<String> ldapAttributes); |
| | | |
| | | /** |
| | | * Retrieves the revision value (etag) from the provided LDAP entry. |
| | | * |
| | | * @param c |
| | | * The context. |
| | | * @param entry |
| | | * The LDAP entry. |
| | | * @return The revision value. |
| | | */ |
| | | abstract String getRevisionFromEntry(Context c, Entry entry); |
| | | |
| | | } |
| | |
| | | |
| | | import java.util.Set; |
| | | |
| | | import org.forgerock.json.resource.ResourceException; |
| | | import org.forgerock.opendj.ldap.DN; |
| | | import org.forgerock.opendj.ldap.Entry; |
| | | import org.forgerock.opendj.ldap.requests.SearchRequest; |
| | |
| | | * @param entry |
| | | * The LDAP entry whose DN and resource ID attributes are to be |
| | | * set. |
| | | * @throws ResourceException |
| | | * If the resource ID cannot be determined. |
| | | */ |
| | | abstract void setResourceId(Context c, DN baseDN, String resourceId, Entry entry); |
| | | abstract void setResourceId(Context c, DN baseDN, String resourceId, Entry entry) |
| | | throws ResourceException; |
| | | |
| | | } |
| New file |
| | |
| | | /* |
| | | * 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.rest2ldap.Utils.accumulate; |
| | | import static org.forgerock.opendj.rest2ldap.Utils.toLowerCase; |
| | | import static org.forgerock.opendj.rest2ldap.Utils.transform; |
| | | |
| | | import java.util.AbstractMap.SimpleImmutableEntry; |
| | | import java.util.ArrayList; |
| | | import java.util.Collections; |
| | | import java.util.LinkedHashMap; |
| | | import java.util.List; |
| | | import java.util.Map; |
| | | import java.util.Set; |
| | | |
| | | import org.forgerock.json.fluent.JsonPointer; |
| | | import org.forgerock.json.fluent.JsonValue; |
| | | import org.forgerock.json.resource.BadRequestException; |
| | | import org.forgerock.json.resource.ResultHandler; |
| | | import org.forgerock.opendj.ldap.Attribute; |
| | | import org.forgerock.opendj.ldap.Entry; |
| | | import org.forgerock.opendj.ldap.Filter; |
| | | import org.forgerock.opendj.ldap.Function; |
| | | |
| | | /** |
| | | * An attribute mapper which maps JSON objects to LDAP attributes. |
| | | */ |
| | | public final class ObjectAttributeMapper extends AttributeMapper { |
| | | |
| | | private static final class Mapping { |
| | | private final AttributeMapper mapper; |
| | | private final String name; |
| | | |
| | | private Mapping(final String name, final AttributeMapper mapper) { |
| | | this.name = name; |
| | | this.mapper = mapper; |
| | | } |
| | | } |
| | | |
| | | private final Map<String, Mapping> mappings = new LinkedHashMap<String, Mapping>(); |
| | | |
| | | ObjectAttributeMapper() { |
| | | // Nothing to do. |
| | | } |
| | | |
| | | /** |
| | | * Creates a mapping for an attribute contained in the JSON object. |
| | | * |
| | | * @param name |
| | | * The name of the JSON attribute to be mapped. |
| | | * @param mapper |
| | | * The attribute mapper responsible for mapping the JSON |
| | | * attribute to LDAP attribute(s). |
| | | * @return A reference to this attribute mapper. |
| | | */ |
| | | public ObjectAttributeMapper attribute(final String name, final AttributeMapper mapper) { |
| | | mappings.put(toLowerCase(name), new Mapping(name, mapper)); |
| | | return this; |
| | | } |
| | | |
| | | @Override |
| | | void getLDAPAttributes(final Context c, final JsonPointer jsonAttribute, |
| | | final Set<String> ldapAttributes) { |
| | | if (jsonAttribute.isEmpty()) { |
| | | // Request all subordinate mappings. |
| | | for (final Mapping mapping : mappings.values()) { |
| | | mapping.mapper.getLDAPAttributes(c, jsonAttribute, ldapAttributes); |
| | | } |
| | | } else { |
| | | // Request single subordinate mapping. |
| | | final Mapping mapping = getMapping(jsonAttribute); |
| | | if (mapping != null) { |
| | | final JsonPointer relativePointer = jsonAttribute.relativePointer(); |
| | | mapping.mapper.getLDAPAttributes(c, relativePointer, ldapAttributes); |
| | | } |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | void getLDAPFilter(final Context c, final FilterType type, final JsonPointer jsonAttribute, |
| | | final String operator, final Object valueAssertion, final ResultHandler<Filter> h) { |
| | | final Mapping mapping = getMapping(jsonAttribute); |
| | | if (mapping != null) { |
| | | final JsonPointer relativePointer = jsonAttribute.relativePointer(); |
| | | mapping.mapper.getLDAPFilter(c, type, relativePointer, operator, valueAssertion, h); |
| | | } else { |
| | | // Either the filter targeted the entire object (i.e. it was "/"), or it targeted |
| | | // an unrecognized attribute within the object. Either way, the filter will |
| | | // never match. |
| | | h.handleResult(c.getConfig().falseFilter()); |
| | | } |
| | | } |
| | | |
| | | 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 |
| | | // a single list. On completion, the accumulator combines the results into a single JSON |
| | | // map object. |
| | | final ResultHandler<Map.Entry<String, JsonValue>> handler = |
| | | accumulate(mappings.size(), transform( |
| | | new Function<List<Map.Entry<String, JsonValue>>, JsonValue, Void>() { |
| | | @Override |
| | | public JsonValue apply(final List<Map.Entry<String, JsonValue>> value, |
| | | final Void p) { |
| | | if (value.isEmpty()) { |
| | | // No subordinate attributes, so omit the entire JSON object |
| | | // from the resource. |
| | | return null; |
| | | } else { |
| | | // Combine the sub-attributes into a single JSON object. |
| | | final Map<String, Object> result = |
| | | new LinkedHashMap<String, Object>(value.size()); |
| | | for (final Map.Entry<String, JsonValue> e : value) { |
| | | result.put(e.getKey(), e.getValue().getObject()); |
| | | } |
| | | return new JsonValue(result); |
| | | } |
| | | } |
| | | }, h)); |
| | | |
| | | for (final Mapping mapping : mappings.values()) { |
| | | mapping.mapper.toJSON(c, e, transform( |
| | | new Function<JsonValue, Map.Entry<String, JsonValue>, Void>() { |
| | | @Override |
| | | public Map.Entry<String, JsonValue> apply(final JsonValue value, |
| | | final Void p) { |
| | | return new SimpleImmutableEntry<String, JsonValue>(mapping.name, value); |
| | | } |
| | | }, handler)); |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | void toLDAP(final Context c, final JsonValue v, final ResultHandler<List<Attribute>> h) { |
| | | // Fail immediately if the JSON value has the wrong type or contains unknown attributes. |
| | | final Map<String, Mapping> missingMappings = new LinkedHashMap<String, Mapping>(mappings); |
| | | if (v != null && !v.isNull()) { |
| | | if (v.isMap()) { |
| | | for (final String attribute : v.asMap().keySet()) { |
| | | if (missingMappings.remove(toLowerCase(attribute)) == null) { |
| | | h.handleError(new BadRequestException("unrecognized attribute '" |
| | | + attribute + "'")); |
| | | return; |
| | | } |
| | | } |
| | | } else { |
| | | h.handleError(new BadRequestException("JSON object expected")); |
| | | return; |
| | | } |
| | | } |
| | | |
| | | // Accumulate the results of the subordinate mappings. |
| | | final ResultHandler<List<Attribute>> handler = |
| | | accumulate(mappings.size(), transform( |
| | | new Function<List<List<Attribute>>, List<Attribute>, Void>() { |
| | | @Override |
| | | public List<Attribute> apply(final List<List<Attribute>> value, |
| | | final Void p) { |
| | | switch (value.size()) { |
| | | case 0: |
| | | return Collections.emptyList(); |
| | | case 1: |
| | | return value.get(0) != null ? value.get(0) : Collections |
| | | .<Attribute> emptyList(); |
| | | default: |
| | | final List<Attribute> attributes = |
| | | new ArrayList<Attribute>(value.size()); |
| | | for (final List<Attribute> a : value) { |
| | | attributes.addAll(a); |
| | | } |
| | | return attributes; |
| | | } |
| | | } |
| | | }, h)); |
| | | |
| | | // Invoke mappings for which there are values provided. |
| | | if (v != null && !v.isNull()) { |
| | | for (final Map.Entry<String, Object> e : v.asMap().entrySet()) { |
| | | final Mapping mapping = getMapping(e.getKey()); |
| | | final JsonValue subValue = new JsonValue(e.getValue()); |
| | | mapping.mapper.toLDAP(c, subValue, handler); |
| | | } |
| | | } |
| | | |
| | | // Invoke mappings for which there were no values provided. |
| | | for (final Mapping mapping : missingMappings.values()) { |
| | | mapping.mapper.toLDAP(c, null, handler); |
| | | } |
| | | } |
| | | |
| | | private Mapping getMapping(final JsonPointer jsonAttribute) { |
| | | return jsonAttribute.isEmpty() ? null : getMapping(jsonAttribute.get(0)); |
| | | } |
| | | |
| | | private Mapping getMapping(final String jsonAttribute) { |
| | | return mappings.get(toLowerCase(jsonAttribute)); |
| | | } |
| | | |
| | | } |
| | |
| | | import static org.forgerock.opendj.rest2ldap.ReadOnUpdatePolicy.USE_READ_ENTRY_CONTROLS; |
| | | import static org.forgerock.opendj.rest2ldap.Utils.ensureNotNull; |
| | | |
| | | import java.util.Arrays; |
| | | import java.util.Collection; |
| | | import java.util.LinkedList; |
| | | import java.util.List; |
| | | import java.util.Set; |
| | | |
| | | import org.forgerock.json.resource.BadRequestException; |
| | | import org.forgerock.json.resource.CollectionResourceProvider; |
| | | import org.forgerock.json.resource.ResourceException; |
| | | import org.forgerock.opendj.ldap.Attribute; |
| | | import org.forgerock.opendj.ldap.AttributeDescription; |
| | | import org.forgerock.opendj.ldap.ByteString; |
| | | import org.forgerock.opendj.ldap.ConnectionFactory; |
| | |
| | | * A builder for incrementally constructing LDAP resource collections. |
| | | */ |
| | | public static final class Builder { |
| | | private final List<Attribute> additionalLDAPAttributes = new LinkedList<Attribute>(); |
| | | private DN baseDN; // TODO: support template variables. |
| | | private ConnectionFactory factory; |
| | | private final Filter falseFilter = Filter.present("1.1"); |
| | | private final List<AttributeMapper> mappers = new LinkedList<AttributeMapper>(); |
| | | private MVCCStrategy mvccStrategy; |
| | | private NameStrategy nameStrategy; |
| | | private ReadOnUpdatePolicy readOnUpdatePolicy = USE_READ_ENTRY_CONTROLS; |
| | | private final ObjectAttributeMapper rootMapper = new ObjectAttributeMapper(); |
| | | private Schema schema = Schema.getDefaultSchema(); |
| | | private Filter trueFilter = Filter.objectClassPresent(); |
| | | |
| | | Builder() { |
| | | useEtagAttribute(); |
| | | useServerEntryUUIDNaming("uid"); |
| | | useClientDNNaming("uid"); |
| | | } |
| | | |
| | | public Builder additionalLDAPAttribute(final Attribute attribute) { |
| | | additionalLDAPAttributes.add(attribute); |
| | | return this; |
| | | } |
| | | |
| | | public Builder additionalLDAPAttribute(final String attribute, final Object... values) { |
| | | additionalLDAPAttributes.add(new LinkedAttribute(attribute, values)); |
| | | return this; |
| | | } |
| | | |
| | | /** |
| | | * Creates a mapping for the named JSON attribute. |
| | | * |
| | | * @param name |
| | | * The name of the JSON attribute to be mapped. |
| | | * @param mapper |
| | | * The attribute mapper responsible for mapping the JSON |
| | | * attribute to LDAP attribute(s). |
| | | * @return A reference to this builder. |
| | | */ |
| | | public Builder attribute(final String name, final AttributeMapper mapper) { |
| | | rootMapper.attribute(name, mapper); |
| | | return this; |
| | | } |
| | | |
| | | public Builder baseDN(final DN dn) { |
| | |
| | | public CollectionResourceProvider build() { |
| | | ensureNotNull(factory); |
| | | ensureNotNull(baseDN); |
| | | if (mappers.isEmpty()) { |
| | | if (rootMapper.isEmpty()) { |
| | | throw new IllegalStateException("No mappings provided"); |
| | | } |
| | | return new LDAPCollectionResourceProvider(baseDN, mapOf(mappers), factory, |
| | | nameStrategy, mvccStrategy, new Config(trueFilter, falseFilter, |
| | | readOnUpdatePolicy, schema)); |
| | | return new LDAPCollectionResourceProvider(baseDN, rootMapper, factory, nameStrategy, |
| | | mvccStrategy, new Config(trueFilter, falseFilter, readOnUpdatePolicy, schema), |
| | | additionalLDAPAttributes); |
| | | } |
| | | |
| | | public Builder factory(final ConnectionFactory factory) { |
| | |
| | | return this; |
| | | } |
| | | |
| | | public Builder map(final AttributeMapper... mappers) { |
| | | ensureNotNull(mappers); |
| | | this.mappers.addAll(Arrays.asList(mappers)); |
| | | return this; |
| | | } |
| | | |
| | | public Builder map(final Collection<AttributeMapper> mappers) { |
| | | ensureNotNull(mappers); |
| | | this.mappers.addAll(mappers); |
| | | return this; |
| | | } |
| | | |
| | | /** |
| | | * Sets the policy which should be used in order to read an entry before |
| | | * it is deleted, or after it is added or modified. |
| | |
| | | |
| | | @Override |
| | | void setResourceId(final Context c, final DN baseDN, final String resourceId, |
| | | final Entry entry) { |
| | | entry.setName(baseDN.child(rdn(resourceId))); |
| | | entry.addAttribute(new LinkedAttribute(attribute, ByteString.valueOf(resourceId))); |
| | | final Entry entry) throws ResourceException { |
| | | if (resourceId != null) { |
| | | entry.setName(baseDN.child(rdn(resourceId))); |
| | | entry.addAttribute(new LinkedAttribute(attribute, ByteString.valueOf(resourceId))); |
| | | } else if (entry.getAttribute(attribute) != null) { |
| | | entry.setName(baseDN.child(rdn(entry.parseAttribute(attribute).asString()))); |
| | | } else { |
| | | throw new BadRequestException("Unable to set the resource ID"); |
| | | } |
| | | } |
| | | |
| | | private RDN rdn(final String resourceId) { |
| | |
| | | return new Builder(); |
| | | } |
| | | |
| | | public static SimpleAttributeMapper map(final AttributeDescription attribute) { |
| | | return map(attribute.toString(), attribute); |
| | | public static AttributeMapper constant(final Object value) { |
| | | return new JSONConstantAttributeMapper(value); |
| | | } |
| | | |
| | | public static SimpleAttributeMapper map(final String attribute) { |
| | | return map(attribute, attribute); |
| | | public static ObjectAttributeMapper object() { |
| | | return new ObjectAttributeMapper(); |
| | | } |
| | | |
| | | public static SimpleAttributeMapper map(final String jsonAttribute, |
| | | final AttributeDescription ldapAttribute) { |
| | | return new SimpleAttributeMapper(jsonAttribute, ldapAttribute); |
| | | public static SimpleAttributeMapper simple(final AttributeDescription attribute) { |
| | | return new SimpleAttributeMapper(attribute); |
| | | } |
| | | |
| | | public static SimpleAttributeMapper map(final String jsonAttribute, final String ldapAttribute) { |
| | | return map(jsonAttribute, AttributeDescription.valueOf(ldapAttribute)); |
| | | } |
| | | |
| | | public static AttributeMapper mapAllExcept(final String... attributes) { |
| | | return new DefaultAttributeMapper().excludeAttribute(attributes); |
| | | } |
| | | |
| | | public static AttributeMapper mapAllOf(final String... attributes) { |
| | | return new DefaultAttributeMapper().includeAttribute(attributes); |
| | | } |
| | | |
| | | public static AttributeMapper mapComplex(final String jsonAttribute, |
| | | final AttributeMapper... mappers) { |
| | | return mapComplex(jsonAttribute, Arrays.asList(mappers)); |
| | | } |
| | | |
| | | public static AttributeMapper mapComplex(final String jsonAttribute, |
| | | final Collection<AttributeMapper> mappers) { |
| | | return new ComplexAttributeMapper(jsonAttribute, mapOf(mappers)); |
| | | } |
| | | |
| | | public static AttributeMapper mapJSONConstant(final String attribute, |
| | | final Object attributeValue) { |
| | | return new JSONConstantAttributeMapper(attribute, attributeValue); |
| | | } |
| | | |
| | | public static AttributeMapper mapLDAPConstant(final AttributeDescription attribute, |
| | | final Object... attributeValues) { |
| | | return new LDAPConstantAttributeMapper(attribute, attributeValues); |
| | | } |
| | | |
| | | public static AttributeMapper mapLDAPConstant(final String attribute, |
| | | final Object... attributeValues) { |
| | | return mapLDAPConstant(AttributeDescription.valueOf(attribute), attributeValues); |
| | | } |
| | | |
| | | private static AttributeMapper mapOf(final Collection<AttributeMapper> mappers) { |
| | | return new CompositeAttributeMapper(mappers); |
| | | public static SimpleAttributeMapper simple(final String attribute) { |
| | | return simple(AttributeDescription.valueOf(attribute)); |
| | | } |
| | | |
| | | private Rest2LDAP() { |
| | |
| | | import static java.util.Collections.emptySet; |
| | | import static java.util.Collections.singleton; |
| | | import static java.util.Collections.singletonList; |
| | | import static java.util.Collections.singletonMap; |
| | | import static org.forgerock.opendj.ldap.Functions.fixedFunction; |
| | | import static org.forgerock.opendj.rest2ldap.Utils.byteStringToJson; |
| | | import static org.forgerock.opendj.rest2ldap.Utils.jsonToAttribute; |
| | | import static org.forgerock.opendj.rest2ldap.Utils.jsonToByteString; |
| | | import static org.forgerock.opendj.rest2ldap.Utils.toFilter; |
| | | import static org.forgerock.opendj.rest2ldap.Utils.toLowerCase; |
| | | import static org.forgerock.opendj.rest2ldap.WritabilityPolicy.READ_ONLY; |
| | | import static org.forgerock.opendj.rest2ldap.WritabilityPolicy.READ_WRITE; |
| | | |
| | | import java.util.Collection; |
| | | import java.util.Collections; |
| | | import java.util.List; |
| | | import java.util.Map; |
| | | import java.util.Set; |
| | | |
| | | import org.forgerock.json.fluent.JsonPointer; |
| | | import org.forgerock.json.fluent.JsonValue; |
| | | import org.forgerock.json.resource.BadRequestException; |
| | | import org.forgerock.json.resource.ResourceException; |
| | | import org.forgerock.json.resource.ResultHandler; |
| | | import org.forgerock.opendj.ldap.Attribute; |
| | | import org.forgerock.opendj.ldap.AttributeDescription; |
| | |
| | | import org.forgerock.opendj.ldap.LinkedAttribute; |
| | | |
| | | /** |
| | | * An attribute mapper which maps a single JSON attribute to a single LDAP |
| | | * attribute. |
| | | * An attribute mapper which provides a simple mapping from a JSON value to a |
| | | * single LDAP attribute. |
| | | */ |
| | | public final class SimpleAttributeMapper extends AttributeMapper { |
| | | |
| | | private Function<ByteString, ?, Void> decoder = null; |
| | | private Object defaultJSONValue = null; |
| | | private Collection<Object> defaultJSONValues = Collections.emptySet(); |
| | | private ByteString defaultLDAPValue = null; |
| | | private Function<Object, ByteString, Void> encoder = null; |
| | | private boolean forceSingleValued = false; |
| | | |
| | | // private boolean isReadOnly = false; |
| | | private final String jsonAttributeName; |
| | | private boolean isIgnoreUpdates = true; |
| | | private boolean isRequired = false; |
| | | private boolean isSingleValued = false; |
| | | private final AttributeDescription ldapAttributeName; |
| | | private final String normalizedJsonAttributeName; |
| | | private WritabilityPolicy writabilityPolicy = READ_WRITE; |
| | | |
| | | /** |
| | | * Creates a new simple attribute mapper which maps a single LDAP attribute |
| | | * to an entry. |
| | | * |
| | | * @param jsonAttributeName |
| | | * The name of the simple JSON attribute. |
| | | * @param ldapAttributeName |
| | | * The name of the LDAP attribute. |
| | | */ |
| | | SimpleAttributeMapper(final String jsonAttributeName, |
| | | final AttributeDescription ldapAttributeName) { |
| | | this.jsonAttributeName = jsonAttributeName; |
| | | SimpleAttributeMapper(final AttributeDescription ldapAttributeName) { |
| | | this.ldapAttributeName = ldapAttributeName; |
| | | this.normalizedJsonAttributeName = toLowerCase(jsonAttributeName); |
| | | } |
| | | |
| | | /** |
| | |
| | | } |
| | | |
| | | /** |
| | | * Prevents the LDAP attribute from being updated. |
| | | * Indicates whether or not an attempt to update the LDAP attribute should |
| | | * be ignored when the update is incompatible with the writability policy. |
| | | * The default is {@code true}. |
| | | * |
| | | * @param readOnly |
| | | * {@code true} if the LDAP attribute is read-only. |
| | | * @param ignore |
| | | * {@code true} an attempt to update the LDAP attribute should be |
| | | * ignored. |
| | | * @return This attribute mapper. |
| | | */ |
| | | public SimpleAttributeMapper readOnly(final boolean readOnly) { |
| | | // TODO: enforcement policy: ignore, warn, or reject. |
| | | // this.isReadOnly = readOnly; |
| | | public SimpleAttributeMapper ignoreUpdates(final boolean ignore) { |
| | | this.isIgnoreUpdates = ignore; |
| | | return this; |
| | | } |
| | | |
| | | /** |
| | | * Indicates that the LDAP attribute is mandatory and must be provided |
| | | * during create requests. The default is {@code false}. |
| | | * |
| | | * @param isRequired |
| | | * {@code true} if the LDAP attribute is mandatory and must be |
| | | * provided during create requests. |
| | | * @return This attribute mapper. |
| | | */ |
| | | public SimpleAttributeMapper required(final boolean isRequired) { |
| | | this.isRequired = isRequired; |
| | | return this; |
| | | } |
| | | |
| | | /** |
| | | * Forces a multi-valued LDAP attribute to be represented as a single-valued |
| | | * JSON value, rather than an array of values. |
| | | * JSON value, rather than an array of values. The default is {@code false}. |
| | | * |
| | | * @param singleValued |
| | | * @param isSingleValued |
| | | * {@code true} if the LDAP attribute should be treated as a |
| | | * single-valued attribute. |
| | | * @return This attribute mapper. |
| | | */ |
| | | public SimpleAttributeMapper singleValued(final boolean singleValued) { |
| | | this.forceSingleValued = singleValued; |
| | | public SimpleAttributeMapper singleValued(final boolean isSingleValued) { |
| | | this.isSingleValued = isSingleValued; |
| | | 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 SimpleAttributeMapper writability(final WritabilityPolicy policy) { |
| | | this.writabilityPolicy = policy; |
| | | return this; |
| | | } |
| | | |
| | | @Override |
| | | void getLDAPAttributes(final Context c, final JsonPointer jsonAttribute, |
| | | final Set<String> ldapAttributes) { |
| | | if (jsonAttribute.isEmpty() || matches(jsonAttribute)) { |
| | | ldapAttributes.add(ldapAttributeName.toString()); |
| | | } |
| | | 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) { |
| | | if (matches(jsonAttribute)) { |
| | | if (jsonAttribute.isEmpty()) { |
| | | h.handleResult(toFilter(c, type, ldapAttributeName.toString(), valueAssertion)); |
| | | } else { |
| | | // This attribute mapper cannot handle the provided filter component. |
| | | h.handleResult(null); |
| | | // This attribute mapper does not support partial filtering. |
| | | h.handleResult(c.getConfig().falseFilter()); |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | void toJSON(final Context c, final Entry e, final ResultHandler<Map<String, Object>> h) { |
| | | void toJSON(final Context c, final Entry e, final ResultHandler<JsonValue> h) { |
| | | final Function<ByteString, ?, Void> f = |
| | | decoder == null ? fixedFunction(byteStringToJson(), ldapAttributeName) : decoder; |
| | | final Object value; |
| | | if (forceSingleValued || ldapAttributeName.getAttributeType().isSingleValue()) { |
| | | if (isSingleValued || ldapAttributeName.getAttributeType().isSingleValue()) { |
| | | value = e.parseAttribute(ldapAttributeName).as(f, defaultJSONValue); |
| | | } else { |
| | | value = e.parseAttribute(ldapAttributeName).asSetOf(f, defaultJSONValues); |
| | | } |
| | | h.handleResult(singletonMap(jsonAttributeName, value)); |
| | | h.handleResult(new JsonValue(value)); |
| | | } |
| | | |
| | | @Override |
| | | void toLDAP(final Context c, final JsonValue v, final ResultHandler<List<Attribute>> h) { |
| | | if (v.isMap()) { |
| | | final Object value = v.get(jsonAttributeName).getObject(); |
| | | try { |
| | | final List<Attribute> result; |
| | | try { |
| | | final List<Attribute> result; |
| | | if (v == null || v.isNull()) { |
| | | if (isRequired()) { |
| | | // FIXME: improve error message. |
| | | throw new BadRequestException("no value provided"); |
| | | } else if (defaultLDAPValue != null) { |
| | | result = |
| | | singletonList((Attribute) new LinkedAttribute(ldapAttributeName, |
| | | defaultLDAPValue)); |
| | | } else { |
| | | result = emptyList(); |
| | | } |
| | | } else if (v.isList() && isSingleValued()) { |
| | | // FIXME: improve error message. |
| | | throw new BadRequestException("expected single value, but got multiple values"); |
| | | } else if (isCreate()) { |
| | | if (isIgnoreUpdates) { |
| | | result = emptyList(); |
| | | } else { |
| | | // FIXME: improve error message. |
| | | throw new BadRequestException("attempted to create a read-only value"); |
| | | } |
| | | } else { |
| | | final Object value = v.getObject(); |
| | | if (value != null) { |
| | | final Function<Object, ByteString, Void> f = |
| | | encoder != null ? encoder : fixedFunction(jsonToByteString(), |
| | |
| | | } else { |
| | | result = emptyList(); |
| | | } |
| | | h.handleResult(result); |
| | | } catch (final Exception e) { |
| | | // FIXME: improve error message. |
| | | h.handleError(new BadRequestException("The field " + jsonAttributeName |
| | | + " is invalid")); |
| | | return; |
| | | } |
| | | } else { |
| | | h.handleResult(Collections.<Attribute> emptyList()); |
| | | h.handleResult(result); |
| | | } catch (final ResourceException e) { |
| | | h.handleError(e); |
| | | } catch (final Exception e) { |
| | | // FIXME: improve error message. |
| | | h.handleError(new BadRequestException(e.getMessage())); |
| | | } |
| | | } |
| | | |
| | | private boolean matches(final JsonPointer jsonAttribute) { |
| | | return !jsonAttribute.isEmpty() |
| | | && toLowerCase(jsonAttribute.get(0)).equals(normalizedJsonAttributeName); |
| | | private boolean isCreate() { |
| | | return writabilityPolicy != READ_ONLY |
| | | && ldapAttributeName.getAttributeType().isNoUserModification(); |
| | | } |
| | | |
| | | private boolean isRequired() { |
| | | return isRequired && defaultJSONValue == null; |
| | | } |
| | | |
| | | private boolean isSingleValued() { |
| | | return isSingleValued || ldapAttributeName.getAttributeType().isSingleValue(); |
| | | } |
| | | |
| | | } |
| | |
| | | } else if (syntax.equals(getGeneralizedTimeSyntax())) { |
| | | return printDateTime(byteStringToGeneralizedTime().apply(value, null) |
| | | .toCalendar()); |
| | | } else if (syntax.isHumanReadable()) { |
| | | return byteStringToString().apply(value, null); |
| | | } else { |
| | | // Base 64 encoded binary. |
| | | return value.toBase64String(); |
| | | return byteStringToString().apply(value, null); |
| | | } |
| | | } |
| | | }; |
| | |
| | | if (syntax.equals(getGeneralizedTimeSyntax())) { |
| | | return ByteString.valueOf(GeneralizedTime.valueOf(parseDateTime(value |
| | | .toString()))); |
| | | } else if (syntax.isHumanReadable()) { |
| | | return ByteString.valueOf(value); |
| | | } else { |
| | | // Base 64 encoded binary. |
| | | return ByteString.valueOfBase64(value.toString()); |
| | | return ByteString.valueOf(value); |
| | | } |
| | | } else { |
| | | throw new IllegalArgumentException("Unrecognized type of JSON value: " |
| New file |
| | |
| | | /* |
| | | * 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 2013 ForgeRock AS. |
| | | */ |
| | | |
| | | package org.forgerock.opendj.rest2ldap; |
| | | |
| | | /** |
| | | * The writability policy determines whether or not an attribute supports |
| | | * updates. |
| | | */ |
| | | public enum WritabilityPolicy { |
| | | /** |
| | | * The attribute may be provided when creating a new resource, but cannot be |
| | | * modified afterwards. |
| | | */ |
| | | CREATE_ONLY, |
| | | |
| | | /** |
| | | * The attribute cannot be provided when creating a new resource, nor |
| | | * modified afterwards. |
| | | */ |
| | | READ_ONLY, |
| | | |
| | | /** |
| | | * The attribute may be provided when creating a new resource, and modified |
| | | * afterwards. |
| | | */ |
| | | READ_WRITE; |
| | | } |
| | |
| | | |
| | | import static org.forgerock.json.resource.Resources.newInternalConnectionFactory; |
| | | import static org.forgerock.opendj.ldap.Connections.newAuthenticatedConnectionFactory; |
| | | import static org.forgerock.opendj.rest2ldap.Rest2LDAP.*; |
| | | import static org.forgerock.opendj.rest2ldap.Rest2LDAP.builder; |
| | | import static org.forgerock.opendj.rest2ldap.Rest2LDAP.constant; |
| | | import static org.forgerock.opendj.rest2ldap.Rest2LDAP.object; |
| | | import static org.forgerock.opendj.rest2ldap.Rest2LDAP.simple; |
| | | import static org.forgerock.opendj.rest2ldap.WritabilityPolicy.CREATE_ONLY; |
| | | import static org.forgerock.opendj.rest2ldap.WritabilityPolicy.READ_ONLY; |
| | | |
| | | import java.util.Arrays; |
| | | import java.util.logging.Logger; |
| | |
| | | import org.forgerock.json.resource.Router; |
| | | import org.forgerock.json.resource.servlet.HttpServlet; |
| | | import org.forgerock.opendj.ldap.ConnectionFactory; |
| | | import org.forgerock.opendj.ldap.Functions; |
| | | import org.forgerock.opendj.ldap.LDAPConnectionFactory; |
| | | import org.forgerock.opendj.ldap.requests.Requests; |
| | | import org.glassfish.grizzly.http.server.HttpServer; |
| | |
| | | |
| | | // Create user resource. |
| | | CollectionResourceProvider users = |
| | | builder().factory(ldapFactory).baseDN("ou=people,dc=example,dc=com").map( |
| | | mapJSONConstant("schemas", Arrays.asList("urn:scim:schemas:core:1.0")), |
| | | map("id", "entryUUID").singleValued(true), |
| | | map("externalId", "uid").singleValued(true), |
| | | map("userName", "mail").singleValued(true), |
| | | map("displayName", "cn").singleValued(true), |
| | | mapComplex("name", map("givenName", "givenName").singleValued(true), map( |
| | | "familyName", "sn").singleValued(true)), |
| | | mapComplex("contactInformation", map("telephoneNumber").decoder( |
| | | Functions.byteStringToString()).encoder( |
| | | Functions.objectToByteString()).singleValued(true), map( |
| | | "emailAddress", "mail").singleValued(true)), |
| | | mapLDAPConstant("objectClass", "top", "person", "organizationalPerson", "inetOrgPerson")) |
| | | .build(); |
| | | builder().factory(ldapFactory).baseDN("ou=people,dc=example,dc=com") |
| | | .attribute("schemas", constant(Arrays.asList("urn:scim:schemas:core:1.0"))) |
| | | .attribute("id", simple("uid").singleValued(true).required(true).writability(CREATE_ONLY)) |
| | | .attribute("rev", simple("etag").singleValued(true).writability(READ_ONLY)) |
| | | .attribute("userName", simple("mail").singleValued(true).writability(READ_ONLY)) |
| | | .attribute("displayName", simple("cn").singleValued(true).required(true)) |
| | | .attribute("name", object() |
| | | .attribute("givenName", simple("givenName").singleValued(true)) |
| | | .attribute("familyName", simple("sn").singleValued(true).required(true))) |
| | | .attribute("contactInformation", object() |
| | | .attribute("telephoneNumber", simple("telephoneNumber").singleValued(true)) |
| | | .attribute("emailAddress", simple("mail").singleValued(true))) |
| | | .additionalLDAPAttribute("objectClass", "top", "person", "organizationalPerson", "inetOrgPerson") |
| | | .build(); |
| | | router.addRoute("/users", users); |
| | | |
| | | // Create group resource. |
| | | CollectionResourceProvider groups = |
| | | builder().factory(ldapFactory).baseDN("ou=groups,dc=example,dc=com").map( |
| | | mapAllOf("cn", "ou", "description", "uniquemember")).build(); |
| | | builder().factory(ldapFactory).baseDN("ou=groups,dc=example,dc=com") |
| | | .attribute("cn", simple("cn").singleValued(true)) |
| | | .attribute("description", simple("description")) |
| | | .attribute("member", simple("uniquemember")) |
| | | .build(); |
| | | router.addRoute("/groups", groups); |
| | | |
| | | final org.forgerock.json.resource.ConnectionFactory resourceFactory = |