From a2bc68638f55ae0ad7b9e3a04c7a3c02d01384f8 Mon Sep 17 00:00:00 2001
From: Matthew Swift <matthew.swift@forgerock.com>
Date: Wed, 13 Feb 2013 23:44:11 +0000
Subject: [PATCH] Partial fix for OPENDJ-758 : Implement configurable update policy for simple and default mappers
---
opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/WritabilityPolicy.java | 41 ++
opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Rest2LDAP.java | 121 +++----
opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/ObjectAttributeMapper.java | 217 ++++++++++++
opendj3/opendj-rest2ldap/src/test/java/org/forgerock/opendj/rest2ldap/Example.java | 43 +-
opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Config.java | 28
/dev/null | 74 ----
opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/SimpleAttributeMapper.java | 156 +++++---
opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Utils.java | 10
opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/AttributeMapper.java | 49 +-
opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/MVCCStrategy.java | 22
opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/JSONConstantAttributeMapper.java | 84 ++--
opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/LDAPCollectionResourceProvider.java | 118 +++---
opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/NameStrategy.java | 6
13 files changed, 594 insertions(+), 375 deletions(-)
diff --git a/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/AttributeMapper.java b/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/AttributeMapper.java
index cdb9c5c..70f699a 100644
--- a/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/AttributeMapper.java
+++ b/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/AttributeMapper.java
@@ -16,7 +16,6 @@
package org.forgerock.opendj.rest2ldap;
import java.util.List;
-import java.util.Map;
import java.util.Set;
import org.forgerock.json.fluent.JsonPointer;
@@ -42,8 +41,7 @@
/**
* 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.
@@ -51,7 +49,9 @@
* @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.
@@ -63,11 +63,7 @@
* 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.
@@ -77,7 +73,9 @@
* @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
@@ -92,13 +90,21 @@
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.
@@ -107,20 +113,29 @@
* @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.
*/
diff --git a/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/ComplexAttributeMapper.java b/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/ComplexAttributeMapper.java
deleted file mode 100644
index a1ad982..0000000
--- a/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/ComplexAttributeMapper.java
+++ /dev/null
@@ -1,114 +0,0 @@
-/*
- * 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.toLowerCase;
-
-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.ResourceException;
-import org.forgerock.json.resource.ResultHandler;
-import org.forgerock.opendj.ldap.Attribute;
-import org.forgerock.opendj.ldap.Entry;
-import org.forgerock.opendj.ldap.Filter;
-
-/**
- * An attribute mapper which maps a single JSON attribute to the result of
- * another attribute mapper.
- */
-final class ComplexAttributeMapper extends AttributeMapper {
-
- private final String jsonAttributeName;
- private final AttributeMapper mapper;
- private final String normalizedJsonAttributeName;
-
- /**
- * Creates a new complex attribute mapper which will wrap the results of the
- * provided mapper as a complex JSON object.
- *
- * @param jsonAttributeName
- * The name of the complex attribute.
- * @param mapper
- * The mapper which should be used to provide the contents of the
- * complex attribute.
- */
- ComplexAttributeMapper(final String jsonAttributeName, final AttributeMapper mapper) {
- this.jsonAttributeName = jsonAttributeName;
- this.mapper = mapper;
- this.normalizedJsonAttributeName = toLowerCase(jsonAttributeName);
- }
-
- @Override
- void getLDAPAttributes(final Context c, final JsonPointer jsonAttribute,
- final Set<String> ldapAttributes) {
- if (jsonAttribute.isEmpty() || matches(jsonAttribute)) {
- final JsonPointer relativePointer = jsonAttribute.relativePointer();
- 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) {
- if (matches(jsonAttribute)) {
- final JsonPointer relativePointer = jsonAttribute.relativePointer();
- mapper.getLDAPFilter(c, type, relativePointer, operator, valueAssertion, h);
- } else {
- // This attribute mapper cannot handle the provided filter component.
- h.handleResult(null);
- }
- }
-
- @Override
- void toJSON(final Context c, final Entry e, final ResultHandler<Map<String, Object>> h) {
- final ResultHandler<Map<String, Object>> wrapper =
- new ResultHandler<Map<String, Object>>() {
-
- @Override
- public void handleError(final ResourceException e) {
- h.handleError(e);
- }
-
- @Override
- public void handleResult(final Map<String, Object> result) {
- final Map<String, Object> complexResult =
- Collections.singletonMap(jsonAttributeName, (Object) result);
- h.handleResult(complexResult);
- }
- };
- mapper.toJSON(c, e, wrapper);
- }
-
- @Override
- void toLDAP(final Context c, final JsonValue v, final ResultHandler<List<Attribute>> h) {
- if (v.isDefined(jsonAttributeName)) {
- mapper.toLDAP(c, v.get(jsonAttributeName), h);
- } else {
- mapper.toLDAP(c, new JsonValue(Collections.emptyMap()), h);
- }
- }
-
- private boolean matches(final JsonPointer jsonAttribute) {
- return !jsonAttribute.isEmpty()
- && toLowerCase(jsonAttribute.get(0)).equals(normalizedJsonAttributeName);
- }
-
-}
diff --git a/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/CompositeAttributeMapper.java b/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/CompositeAttributeMapper.java
deleted file mode 100644
index cf77ea8..0000000
--- a/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/CompositeAttributeMapper.java
+++ /dev/null
@@ -1,189 +0,0 @@
-/*
- * 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.transform;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.Iterator;
-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.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 combines the results of a set of subordinate
- * attribute mappers into a single JSON object.
- */
-final class CompositeAttributeMapper extends AttributeMapper {
- private final List<AttributeMapper> attributeMappers;
-
- /**
- * Creates a new composite attribute mapper.
- */
- CompositeAttributeMapper(final Collection<AttributeMapper> mappers) {
- this.attributeMappers = new ArrayList<AttributeMapper>(mappers);
- }
-
- @Override
- void getLDAPAttributes(final Context c, final JsonPointer jsonAttribute,
- final Set<String> ldapAttributes) {
- for (final AttributeMapper attribute : attributeMappers) {
- attribute.getLDAPAttributes(c, jsonAttribute, ldapAttributes);
- }
- }
-
- @Override
- void getLDAPFilter(final Context c, final FilterType type, final JsonPointer jsonAttribute,
- final String operator, final Object valueAssertion, final ResultHandler<Filter> h) {
- final ResultHandler<Filter> handler =
- accumulate(attributeMappers.size(), transform(
- new Function<List<Filter>, Filter, Void>() {
- @Override
- public Filter apply(final List<Filter> value, final Void p) {
- // Remove unmapped filters and combine using logical-OR.
- final Iterator<Filter> i = value.iterator();
- while (i.hasNext()) {
- final Filter f = i.next();
- if (f == null) {
- // No mapping so remove.
- i.remove();
- } else if (f == c.getConfig().falseFilter()) {
- return c.getConfig().falseFilter();
- } else if (f == c.getConfig().trueFilter()) {
- return c.getConfig().trueFilter();
- }
- }
- switch (value.size()) {
- case 0:
- // No mappings found.
- return null;
- case 1:
- return value.get(0);
- default:
- return Filter.or(value);
- }
- }
- }, h));
- for (final AttributeMapper subMapper : attributeMappers) {
- subMapper.getLDAPFilter(c, type, jsonAttribute, operator, valueAssertion, handler);
- }
- }
-
- @Override
- void toJSON(final Context c, final Entry e, final ResultHandler<Map<String, Object>> h) {
- final ResultHandler<Map<String, Object>> handler =
- accumulate(attributeMappers.size(), transform(
- new Function<List<Map<String, Object>>, Map<String, Object>, Void>() {
- @Override
- public Map<String, Object> apply(final List<Map<String, Object>> value,
- final Void p) {
- switch (value.size()) {
- case 0:
- return Collections.<String, Object> emptyMap();
- case 1:
- return value.get(0) != null ? value.get(0) : Collections
- .<String, Object> emptyMap();
- default:
- return mergeJsonValues(value,
- new LinkedHashMap<String, Object>());
- }
- }
- }, h));
- for (final AttributeMapper mapper : attributeMappers) {
- mapper.toJSON(c, e, handler);
- }
- }
-
- @Override
- void toLDAP(final Context c, final JsonValue v, final ResultHandler<List<Attribute>> h) {
- final ResultHandler<List<Attribute>> handler =
- accumulate(attributeMappers.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:
- List<Attribute> attributes =
- new ArrayList<Attribute>(value.size());
- for (List<Attribute> a : value) {
- attributes.addAll(a);
- }
- return attributes;
- }
- }
- }, h));
- for (final AttributeMapper mapper : attributeMappers) {
- mapper.toLDAP(c, v, handler);
- }
- }
-
- @SuppressWarnings("unchecked")
- private void mergeJsonValue(final Map<String, Object> srcValue,
- final Map<String, Object> dstValue) {
- for (final Map.Entry<String, Object> record : srcValue.entrySet()) {
- final String key = record.getKey();
- final Object newValue = record.getValue();
- Object existingValue = dstValue.get(key);
- if (existingValue == null) {
- // Value is new, so just add it.
- dstValue.put(key, newValue);
- } else if ((existingValue instanceof Map) && (newValue instanceof Map)) {
- // Merge two maps - create a new Map, in case the existing one
- // is unmodifiable.
- existingValue =
- new LinkedHashMap<String, Object>((Map<String, Object>) existingValue);
- mergeJsonValue((Map<String, Object>) newValue, (Map<String, Object>) existingValue);
- } else if ((existingValue instanceof List) && (newValue instanceof List)) {
- // Merge two lists- create a new List, in case the existing one
- // is unmodifiable.
- final List<Object> tmp = new ArrayList<Object>((List<Object>) existingValue);
- tmp.addAll((List<Object>) newValue);
- existingValue = tmp;
- }
-
- // Replace the existing value.
- dstValue.put(key, newValue);
- }
- }
-
- private Map<String, Object> mergeJsonValues(final List<Map<String, Object>> srcValues,
- final Map<String, Object> dstValue) {
- for (final Map<String, Object> value : srcValues) {
- if (value != null) {
- mergeJsonValue(value, dstValue);
- }
- }
- return dstValue;
- }
-}
diff --git a/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Config.java b/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Config.java
index 53076d2..e5a8b1b 100644
--- a/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Config.java
+++ b/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Config.java
@@ -25,10 +25,10 @@
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) {
@@ -40,17 +40,6 @@
}
/**
- * 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.
*
@@ -83,6 +72,17 @@
}
/**
+ * 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
diff --git a/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/DefaultAttributeMapper.java b/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/DefaultAttributeMapper.java
deleted file mode 100644
index 96b68f7..0000000
--- a/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/DefaultAttributeMapper.java
+++ /dev/null
@@ -1,172 +0,0 @@
-/*
- * 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.attributeToJson;
-import static org.forgerock.opendj.rest2ldap.Utils.getAttributeName;
-import static org.forgerock.opendj.rest2ldap.Utils.jsonToAttribute;
-import static org.forgerock.opendj.rest2ldap.Utils.toFilter;
-import static org.forgerock.opendj.rest2ldap.Utils.toLowerCase;
-
-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.AttributeDescription;
-import org.forgerock.opendj.ldap.Entry;
-import org.forgerock.opendj.ldap.Filter;
-
-/**
- * An attribute mapper that directly maps a configurable selection of attributes
- * to and from LDAP without any transformation.
- */
-final class DefaultAttributeMapper extends AttributeMapper {
- // All user attributes by default.
- private final Map<String, String> excludedAttributes = new LinkedHashMap<String, String>();
- private final Map<String, String> includedAttributes = new LinkedHashMap<String, String>();
-
- /**
- * Creates a new default attribute mapper which will map all user attributes
- * to JSON by default.
- */
- DefaultAttributeMapper() {
- // No implementation required.
- }
-
- /**
- * Excludes one or more LDAP attributes from this mapping.
- *
- * @param attributes
- * The attributes to be excluded.
- * @return This attribute mapper.
- */
- DefaultAttributeMapper excludeAttribute(final String... attributes) {
- for (final String attribute : attributes) {
- excludedAttributes.put(toLowerCase(attribute), attribute);
- }
- return this;
- }
-
- @Override
- void getLDAPAttributes(final Context c, final JsonPointer jsonAttribute,
- final Set<String> ldapAttributes) {
- switch (jsonAttribute.size()) {
- case 0:
- // Requested everything.
- if (!includedAttributes.isEmpty()) {
- ldapAttributes.addAll(includedAttributes.values());
- } else {
- // All user attributes.
- ldapAttributes.add("*");
- }
- break;
- default:
- final String name = jsonAttribute.get(0);
- if (isIncludedAttribute(name)) {
- ldapAttributes.add(name);
- }
- break;
- }
- }
-
- @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 && isIncludedAttribute(jsonAttribute.get(0))) {
- h.handleResult(toFilter(c, type, jsonAttribute.get(0), valueAssertion));
- } else {
- // This attribute mapper cannot handle the provided filter component.
- h.handleResult(null);
- }
- }
-
- /**
- * Includes one or more LDAP attributes in this mapping.
- *
- * @param attributes
- * The attributes to be included.
- * @return This attribute mapper.
- */
- DefaultAttributeMapper includeAttribute(final String... attributes) {
- for (final String attribute : attributes) {
- includedAttributes.put(toLowerCase(attribute), attribute);
- }
- return this;
- }
-
- @Override
- void toJSON(final Context c, final Entry e, final ResultHandler<Map<String, Object>> h) {
- // FIXME: this will leave out attributes which were not included in the LDAP entry. We should
- // ensure that JSON attributes are present, even if they are null or an empty array.
- final Map<String, Object> result = new LinkedHashMap<String, Object>(e.getAttributeCount());
- for (final Attribute a : e.getAllAttributes()) {
- final String name = getAttributeName(a);
- if (isIncludedAttribute(name)) {
- result.put(name, attributeToJson(a));
- }
- }
- h.handleResult(result);
- }
-
- @Override
- void toLDAP(final Context c, final JsonValue v, final ResultHandler<List<Attribute>> h) {
- if (v.isMap()) {
- final List<Attribute> result = new ArrayList<Attribute>(v.size());
- for (final Map.Entry<String, Object> field : v.asMap().entrySet()) {
- if (!isIncludedAttribute(field.getKey())) {
- continue;
- }
- try {
- final AttributeDescription ad =
- AttributeDescription.valueOf(field.getKey(), c.getConfig().schema());
- result.add(jsonToAttribute(field.getValue(), ad));
- } catch (final Exception e) {
- // FIXME: improve error message.
- h.handleError(new BadRequestException("The field " + field.getKey()
- + " is invalid"));
- return;
- }
- }
- h.handleResult(result);
- } else {
- h.handleResult(Collections.<Attribute> emptyList());
- }
- }
-
- private boolean isIncludedAttribute(final String name) {
- final String lowerName = toLowerCase(name);
-
- // Ignore the requested attribute if it has been excluded.
- if (excludedAttributes.containsKey(lowerName)) {
- return false;
- }
-
- // Include all attributes by default.
- if (includedAttributes.isEmpty() || includedAttributes.containsKey(lowerName)) {
- return true;
- }
-
- return false;
- }
-}
diff --git a/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/JSONConstantAttributeMapper.java b/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/JSONConstantAttributeMapper.java
index fac2ae1..8c0c88d 100644
--- a/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/JSONConstantAttributeMapper.java
+++ b/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/JSONConstantAttributeMapper.java
@@ -19,7 +19,6 @@
import java.util.Collections;
import java.util.List;
-import java.util.Map;
import java.util.Set;
import org.forgerock.json.fluent.JsonPointer;
@@ -33,12 +32,10 @@
* 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
@@ -50,57 +47,52 @@
@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,
diff --git a/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/LDAPCollectionResourceProvider.java b/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/LDAPCollectionResourceProvider.java
index c0c4cd2..ffda24d 100644
--- a/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/LDAPCollectionResourceProvider.java
+++ b/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/LDAPCollectionResourceProvider.java
@@ -24,7 +24,6 @@
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;
@@ -85,8 +84,8 @@
*/
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;
@@ -161,6 +160,7 @@
// 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;
@@ -170,13 +170,15 @@
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
@@ -204,10 +206,19 @@
@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));
@@ -254,24 +265,22 @@
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);
@@ -406,21 +415,12 @@
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));
}
/**
@@ -454,6 +454,23 @@
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;
}
@@ -675,10 +692,6 @@
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.
@@ -694,13 +707,13 @@
// 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) {
@@ -709,7 +722,7 @@
entry = null;
}
}
- } catch (DecodeException e) {
+ } catch (final DecodeException e) {
// FIXME: log something?
entry = null;
}
@@ -726,20 +739,7 @@
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);
}
}
diff --git a/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/LDAPConstantAttributeMapper.java b/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/LDAPConstantAttributeMapper.java
deleted file mode 100644
index e1b5ade..0000000
--- a/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/LDAPConstantAttributeMapper.java
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * 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 java.util.Collections.singletonList;
-import static org.forgerock.opendj.ldap.Attributes.singletonAttribute;
-
-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.ResultHandler;
-import org.forgerock.opendj.ldap.Attribute;
-import org.forgerock.opendj.ldap.AttributeDescription;
-import org.forgerock.opendj.ldap.Entry;
-import org.forgerock.opendj.ldap.Filter;
-import org.forgerock.opendj.ldap.LinkedAttribute;
-
-/**
- * An attribute mapper which maps a single LDAP attribute to a fixed value.
- */
-final class LDAPConstantAttributeMapper extends AttributeMapper {
- private final List<Attribute> attributes;
-
- LDAPConstantAttributeMapper(final AttributeDescription attributeName,
- final Object... attributeValues) {
- if (attributeValues.length == 1) {
- attributes = singletonList(singletonAttribute(attributeName, attributeValues[0]));
- } else {
- attributes =
- singletonList((Attribute) new LinkedAttribute(attributeName, attributeValues));
- }
- }
-
- @Override
- void getLDAPAttributes(final Context c, final JsonPointer jsonAttribute,
- final Set<String> ldapAttributes) {
- // Nothing to do.
- }
-
- @Override
- void getLDAPFilter(final Context c, final FilterType type, final JsonPointer jsonAttribute,
- final String operator, final Object valueAssertion, final ResultHandler<Filter> h) {
- // This attribute mapper cannot handle the provided filter component.
- h.handleResult(null);
- }
-
- @Override
- void toJSON(final Context c, final Entry e, final ResultHandler<Map<String, Object>> h) {
- h.handleResult(Collections.<String, Object> emptyMap());
- }
-
- @Override
- void toLDAP(final Context c, final JsonValue v, final ResultHandler<List<Attribute>> h) {
- h.handleResult(attributes);
- }
-
-}
diff --git a/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/MVCCStrategy.java b/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/MVCCStrategy.java
index 944bce5..5e33383 100644
--- a/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/MVCCStrategy.java
+++ b/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/MVCCStrategy.java
@@ -35,17 +35,6 @@
}
/**
- * 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.
*
@@ -57,4 +46,15 @@
*/
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);
+
}
diff --git a/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/NameStrategy.java b/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/NameStrategy.java
index f8e9ad6..323f962 100644
--- a/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/NameStrategy.java
+++ b/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/NameStrategy.java
@@ -18,6 +18,7 @@
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;
@@ -89,7 +90,10 @@
* @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;
}
diff --git a/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/ObjectAttributeMapper.java b/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/ObjectAttributeMapper.java
new file mode 100644
index 0000000..69d421e
--- /dev/null
+++ b/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/ObjectAttributeMapper.java
@@ -0,0 +1,217 @@
+/*
+ * 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));
+ }
+
+}
diff --git a/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Rest2LDAP.java b/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Rest2LDAP.java
index 843e895..fa82214 100644
--- a/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Rest2LDAP.java
+++ b/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Rest2LDAP.java
@@ -21,13 +21,14 @@
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;
@@ -52,19 +53,45 @@
* 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) {
@@ -82,12 +109,12 @@
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) {
@@ -109,18 +136,6 @@
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.
@@ -303,9 +318,15 @@
@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) {
@@ -318,58 +339,20 @@
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() {
diff --git a/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/SimpleAttributeMapper.java b/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/SimpleAttributeMapper.java
index a8375e3..9546963 100644
--- a/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/SimpleAttributeMapper.java
+++ b/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/SimpleAttributeMapper.java
@@ -19,23 +19,23 @@
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;
@@ -46,37 +46,23 @@
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);
}
/**
@@ -133,70 +119,118 @@
}
/**
- * 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(),
@@ -209,21 +243,27 @@
} 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();
}
}
diff --git a/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Utils.java b/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Utils.java
index e230bf4..559c92f 100644
--- a/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Utils.java
+++ b/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Utils.java
@@ -103,11 +103,8 @@
} 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);
}
}
};
@@ -121,11 +118,8 @@
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: "
diff --git a/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/WritabilityPolicy.java b/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/WritabilityPolicy.java
new file mode 100644
index 0000000..2b062bd
--- /dev/null
+++ b/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/WritabilityPolicy.java
@@ -0,0 +1,41 @@
+/*
+ * 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;
+}
diff --git a/opendj3/opendj-rest2ldap/src/test/java/org/forgerock/opendj/rest2ldap/Example.java b/opendj3/opendj-rest2ldap/src/test/java/org/forgerock/opendj/rest2ldap/Example.java
index 8a042c8..070a2f5 100644
--- a/opendj3/opendj-rest2ldap/src/test/java/org/forgerock/opendj/rest2ldap/Example.java
+++ b/opendj3/opendj-rest2ldap/src/test/java/org/forgerock/opendj/rest2ldap/Example.java
@@ -18,7 +18,12 @@
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;
@@ -27,7 +32,6 @@
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;
@@ -62,26 +66,29 @@
// 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 =
--
Gitblit v1.10.0