opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/DefaultAttributeMapper.java
@@ -17,11 +17,11 @@ 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.Collection; import java.util.Collections; import java.util.LinkedHashMap; import java.util.List; @@ -34,7 +34,6 @@ import org.forgerock.json.resource.ResultHandler; import org.forgerock.opendj.ldap.Attribute; import org.forgerock.opendj.ldap.AttributeDescription; import org.forgerock.opendj.ldap.Attributes; import org.forgerock.opendj.ldap.Entry; import org.forgerock.opendj.ldap.Filter; @@ -138,38 +137,16 @@ if (!isIncludedAttribute(field.getKey())) { continue; } final AttributeDescription ad; try { ad = AttributeDescription.valueOf(field.getKey(), c.getConfig().schema()); 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; } final Object value = field.getValue(); if (isJSONPrimitive(value)) { result.add(Attributes.singletonAttribute(ad, value)); } else if (value instanceof Collection<?>) { final Attribute a = c.getConfig().decodeOptions().getAttributeFactory().newAttribute(ad); for (final Object o : (Collection<?>) value) { if (isJSONPrimitive(o)) { a.add(o); } else { // FIXME: improve error message. h.handleError(new BadRequestException("The field " + field.getKey() + " is invalid")); return; } } result.add(a); } else { // FIXME: improve error message. h.handleError(new BadRequestException("The field " + field.getKey() + " is invalid")); return; } } h.handleResult(result); } else { @@ -192,8 +169,4 @@ return false; } private boolean isJSONPrimitive(final Object value) { return value instanceof String || value instanceof Boolean || value instanceof Number; } } opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/LDAPConstantAttributeMapper.java
@@ -15,6 +15,7 @@ */ package org.forgerock.opendj.rest2ldap; import static java.util.Collections.singletonList; import static org.forgerock.opendj.ldap.Attributes.singletonAttribute; import java.util.Collections; @@ -29,6 +30,7 @@ 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. @@ -37,8 +39,16 @@ private final List<Attribute> attributes; LDAPConstantAttributeMapper(final AttributeDescription attributeName, final Object attributeValue) { attributes = Collections.singletonList(singletonAttribute(attributeName, attributeValue)); final Object... attributeValues) { if (attributeValues.length == 1) { attributes = singletonList(singletonAttribute(attributeName, attributeValues[0])); } else { Attribute attribute = new LinkedAttribute(attributeName); for (Object o : attributeValues) { attribute.add(o); } attributes = singletonList(attribute); } } @Override opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Rest2LDAP.java
@@ -359,13 +359,13 @@ } public static AttributeMapper mapLDAPConstant(final AttributeDescription attribute, final Object attributeValue) { return new LDAPConstantAttributeMapper(attribute, attributeValue); final Object... attributeValues) { return new LDAPConstantAttributeMapper(attribute, attributeValues); } public static AttributeMapper mapLDAPConstant(final String attribute, final Object attributeValue) { return mapLDAPConstant(AttributeDescription.valueOf(attribute), attributeValue); final Object... attributeValues) { return mapLDAPConstant(AttributeDescription.valueOf(attribute), attributeValues); } private static AttributeMapper mapOf(final Collection<AttributeMapper> mappers) { opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/SimpleAttributeMapper.java
@@ -15,7 +15,15 @@ */ package org.forgerock.opendj.rest2ldap; import static java.util.Collections.emptyList; 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; @@ -27,6 +35,7 @@ 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; @@ -34,7 +43,7 @@ import org.forgerock.opendj.ldap.Entry; import org.forgerock.opendj.ldap.Filter; import org.forgerock.opendj.ldap.Function; import org.forgerock.opendj.ldap.Functions; import org.forgerock.opendj.ldap.LinkedAttribute; /** * An attribute mapper which maps a single JSON attribute to a single LDAP @@ -43,8 +52,10 @@ public final class SimpleAttributeMapper extends AttributeMapper { private Function<ByteString, ?, Void> decoder = null; private Object defaultValue = null; private Collection<Object> defaultValues = Collections.emptySet(); 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; @@ -90,9 +101,34 @@ * @return This attribute mapper. */ public SimpleAttributeMapper defaultJSONValue(final Object defaultValue) { this.defaultValue = defaultValue; this.defaultValues = defaultValue != null ? Collections.singleton(defaultValue) : Collections.emptySet(); this.defaultJSONValue = defaultValue; this.defaultJSONValues = defaultValue != null ? singleton(defaultValue) : emptySet(); return this; } /** * Sets the default LDAP value which should be substituted when the JSON * attribute is not found in the JSON value. * * @param defaultValue * The default LDAP value. * @return This attribute mapper. */ public SimpleAttributeMapper defaultLDAPValue(final Object defaultValue) { this.defaultLDAPValue = defaultValue != null ? ByteString.valueOf(defaultValue) : null; return this; } /** * Sets the encoder which will be used for converting JSON values to LDAP * attribute values. * * @param f * The function to use for encoding LDAP attribute values. * @return This attribute mapper. */ public SimpleAttributeMapper encoder(final Function<Object, ByteString, Void> f) { this.encoder = f; return this; } @@ -145,21 +181,44 @@ @Override void toJSON(final Context c, final Entry e, final ResultHandler<Map<String, Object>> h) { final Function<ByteString, ?, Void> f = decoder == null ? Functions.fixedFunction(byteStringToJson(), ldapAttributeName) : decoder; decoder == null ? fixedFunction(byteStringToJson(), ldapAttributeName) : decoder; final Object value; if (forceSingleValued || ldapAttributeName.getAttributeType().isSingleValue()) { value = e.parseAttribute(ldapAttributeName).as(f, defaultValue); value = e.parseAttribute(ldapAttributeName).as(f, defaultJSONValue); } else { value = e.parseAttribute(ldapAttributeName).asSetOf(f, defaultValues); value = e.parseAttribute(ldapAttributeName).asSetOf(f, defaultJSONValues); } h.handleResult(Collections.singletonMap(jsonAttributeName, value)); h.handleResult(singletonMap(jsonAttributeName, value)); } @Override void toLDAP(final Context c, final JsonValue v, final ResultHandler<List<Attribute>> h) { // TODO Auto-generated method stub if (v.isMap()) { final Object value = v.get(jsonAttributeName).getObject(); try { final List<Attribute> result; if (value != null) { final Function<Object, ByteString, Void> f = encoder != null ? encoder : fixedFunction(jsonToByteString(), ldapAttributeName); result = singletonList(jsonToAttribute(value, ldapAttributeName, f)); } else if (defaultLDAPValue != null) { result = singletonList((Attribute) new LinkedAttribute(ldapAttributeName, defaultLDAPValue)); } 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()); } } private boolean matches(final JsonPointer jsonAttribute) { opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Utils.java
@@ -15,6 +15,13 @@ */ package org.forgerock.opendj.rest2ldap; import static javax.xml.bind.DatatypeConverter.parseDateTime; import static javax.xml.bind.DatatypeConverter.printDateTime; import static org.forgerock.opendj.ldap.Functions.byteStringToBoolean; import static org.forgerock.opendj.ldap.Functions.byteStringToGeneralizedTime; import static org.forgerock.opendj.ldap.Functions.byteStringToLong; import static org.forgerock.opendj.ldap.Functions.byteStringToString; import static org.forgerock.opendj.ldap.Functions.fixedFunction; import static org.forgerock.opendj.ldap.schema.CoreSchema.getBooleanSyntax; import static org.forgerock.opendj.ldap.schema.CoreSchema.getGeneralizedTimeSyntax; import static org.forgerock.opendj.ldap.schema.CoreSchema.getIntegerSyntax; @@ -26,8 +33,6 @@ import java.util.Locale; import java.util.concurrent.atomic.AtomicInteger; import javax.xml.bind.DatatypeConverter; import org.forgerock.json.resource.ResourceException; import org.forgerock.json.resource.ResultHandler; import org.forgerock.opendj.ldap.Attribute; @@ -35,7 +40,8 @@ import org.forgerock.opendj.ldap.ByteString; import org.forgerock.opendj.ldap.Filter; import org.forgerock.opendj.ldap.Function; import org.forgerock.opendj.ldap.Functions; import org.forgerock.opendj.ldap.GeneralizedTime; import org.forgerock.opendj.ldap.LinkedAttribute; import org.forgerock.opendj.ldap.schema.Syntax; /** @@ -85,24 +91,45 @@ } // @Checkstyle:off private static final Function<ByteString, Object, AttributeDescription> BYTESTRING_TO_JSON = new Function<ByteString, Object, AttributeDescription>() { @Override public Object apply(final ByteString value, final AttributeDescription ad) { final Syntax syntax = ad.getAttributeType().getSyntax(); if (syntax.equals(getBooleanSyntax())) { return Functions.byteStringToBoolean().apply(value, null); return byteStringToBoolean().apply(value, null); } else if (syntax.equals(getIntegerSyntax())) { return Functions.byteStringToLong().apply(value, null); return byteStringToLong().apply(value, null); } else if (syntax.equals(getGeneralizedTimeSyntax())) { return DatatypeConverter.printDateTime(Functions .byteStringToGeneralizedTime().apply(value, null).toCalendar()); return printDateTime(byteStringToGeneralizedTime().apply(value, null) .toCalendar()); } else if (syntax.isHumanReadable()) { return Functions.byteStringToString().apply(value, null); return byteStringToString().apply(value, null); } else { // Base 64 encoded binary. return DatatypeConverter.printBase64Binary(value.toByteArray()); return value.toBase64String(); } } }; private static final Function<Object, ByteString, AttributeDescription> JSON_TO_BYTESTRING = new Function<Object, ByteString, AttributeDescription>() { @Override public ByteString apply(final Object value, final AttributeDescription ad) { if (isJSONPrimitive(value)) { final Syntax syntax = ad.getAttributeType().getSyntax(); 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()); } } else { throw new IllegalArgumentException("Unrecognized type of JSON value: " + value.getClass().getName()); } } }; @@ -113,14 +140,12 @@ static Object attributeToJson(final Attribute a) { final Function<ByteString, Object, Void> f = Functions.fixedFunction(BYTESTRING_TO_JSON, a.getAttributeDescription()); fixedFunction(BYTESTRING_TO_JSON, a.getAttributeDescription()); final boolean isSingleValued = a.getAttributeDescription().getAttributeType().isSingleValue(); return isSingleValued ? a.parse().as(f) : asList(a.parse().asSetOf(f)); } // @Checkstyle:on static Function<ByteString, Object, AttributeDescription> byteStringToJson() { return BYTESTRING_TO_JSON; } @@ -143,6 +168,34 @@ return a.getAttributeDescription().withoutOption("binary").toString(); } static boolean isJSONPrimitive(final Object value) { return value instanceof String || value instanceof Boolean || value instanceof Number; } static Attribute jsonToAttribute(final Object value, final AttributeDescription ad) { return jsonToAttribute(value, ad, fixedFunction(jsonToByteString(), ad)); } static Attribute jsonToAttribute(final Object value, final AttributeDescription ad, final Function<Object, ByteString, Void> f) { if (isJSONPrimitive(value)) { return new LinkedAttribute(ad, f.apply(value, null)); } else if (value instanceof Collection<?>) { final Attribute a = new LinkedAttribute(ad); for (final Object o : (Collection<?>) value) { a.add(f.apply(o, null)); } return a; } else { throw new IllegalArgumentException("Unrecognized type of JSON value: " + value.getClass().getName()); } } static Function<Object, ByteString, AttributeDescription> jsonToByteString() { return JSON_TO_BYTESTRING; } static Filter toFilter(final Context c, final FilterType type, final String ldapAttribute, final Object valueAssertion) { final String v = String.valueOf(valueAssertion); opendj3/opendj-rest2ldap/src/test/java/org/forgerock/opendj/rest2ldap/Example.java
@@ -18,11 +18,9 @@ import static org.forgerock.json.resource.Resources.newInternalConnectionFactory; import static org.forgerock.opendj.ldap.Connections.newAuthenticatedConnectionFactory; import static org.forgerock.opendj.rest2ldap.Rest2LDAP.builder; import static org.forgerock.opendj.rest2ldap.Rest2LDAP.map; import static org.forgerock.opendj.rest2ldap.Rest2LDAP.mapAllOf; import static org.forgerock.opendj.rest2ldap.Rest2LDAP.mapComplex; import static org.forgerock.opendj.rest2ldap.Rest2LDAP.*; import java.util.Arrays; import java.util.logging.Logger; import org.forgerock.json.resource.CollectionResourceProvider; @@ -65,12 +63,19 @@ // 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), mapAllOf("uid", "isMemberOf", "modifyTimestamp"), mapComplex("name", mapAllOf("cn", "sn", "givenName")), 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()).singleValued(true), map( "emailAddress", "mail").singleValued(true))).build(); Functions.byteStringToString()).encoder( Functions.objectToByteString()).singleValued(true), map( "emailAddress", "mail").singleValued(true)), mapLDAPConstant("objectClass", "top", "person", "organizationalPerson", "inetOrgPerson")) .build(); router.addRoute("/users", users); // Create group resource.