From 7c8ad397660416252204e0a4c0232471534efcc6 Mon Sep 17 00:00:00 2001
From: Jean-Noël Rouvignac <jean-noel.rouvignac@forgerock.com>
Date: Fri, 23 Sep 2016 21:41:17 +0000
Subject: [PATCH] OPENDJ-3246 Better JSON schema: set type, format and description for properties

---
 opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/SimplePropertyMapper.java |  103 ++++++++++++++++++++++++++++++++++++++++-----------
 1 files changed, 80 insertions(+), 23 deletions(-)

diff --git a/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/SimplePropertyMapper.java b/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/SimplePropertyMapper.java
index c78d863..c705ca7 100644
--- a/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/SimplePropertyMapper.java
+++ b/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/SimplePropertyMapper.java
@@ -31,16 +31,15 @@
 import org.forgerock.opendj.ldap.Entry;
 import org.forgerock.opendj.ldap.Filter;
 import org.forgerock.opendj.ldap.schema.AttributeType;
-import org.forgerock.opendj.ldap.schema.CoreSchema;
+import org.forgerock.opendj.ldap.schema.Syntax;
 import org.forgerock.services.context.Context;
 import org.forgerock.util.Function;
 import org.forgerock.util.promise.Promise;
 
 import static java.util.Collections.*;
-
 import static org.forgerock.json.JsonValue.*;
 import static org.forgerock.opendj.ldap.Filter.*;
-import static org.forgerock.opendj.rest2ldap.Rest2Ldap.asResourceException;
+import static org.forgerock.opendj.ldap.schema.CoreSchema.*;
 import static org.forgerock.opendj.rest2ldap.Utils.*;
 import static org.forgerock.util.promise.Promises.newResultPromise;
 
@@ -48,6 +47,7 @@
 public final class SimplePropertyMapper extends AbstractLdapPropertyMapper<SimplePropertyMapper> {
     private Function<ByteString, ?, ? extends Exception> decoder;
     private Function<Object, ByteString, ? extends Exception> encoder;
+    private JsonValue jsonSchema;
 
     SimplePropertyMapper(final AttributeDescription ldapAttributeName) {
         super(ldapAttributeName);
@@ -129,6 +129,20 @@
         return this;
     }
 
+    /**
+     * Sets the JSON schema corresponding to this simple property mapper. If not {@code null},
+     * it will be returned by {@link #toJsonSchema()}, otherwise a default JSON schema will be
+     * automatically generated with the information available in this property mapper.
+     *
+     * @param jsonSchema
+     *          the JSON schema corresponding to this simple property mapper. Can be {@code null}
+     * @return This property mapper.
+     */
+    public SimplePropertyMapper jsonSchema(JsonValue jsonSchema) {
+        this.jsonSchema = jsonSchema;
+        return this;
+    }
+
     @Override
     public String toString() {
         return "simple(" + ldapAttributeName + ")";
@@ -188,7 +202,7 @@
             }
         } catch (final Exception ex) {
             // The LDAP attribute could not be decoded.
-            return asResourceException(ex).asPromise();
+            return Rest2Ldap.asResourceException(ex).asPromise();
         }
     }
 
@@ -202,37 +216,80 @@
 
     @Override
     JsonValue toJsonSchema() {
+        return this.jsonSchema != null ? this.jsonSchema : toJsonSchema0();
+    }
+
+    private JsonValue toJsonSchema0() {
         final AttributeType attrType = ldapAttributeName.getAttributeType();
 
-        final JsonValue jsonSchema = json(object(field("type", toJsonSchemaType(attrType))));
+        final JsonValue jsonSchema;
+        if (isMultiValued()) {
+            jsonSchema = json(object(
+                field("type", "array"),
+                // LDAP has set semantics => all items are unique
+                field("uniqueItems", true),
+                field("items", itemsSchema(attrType))));
+        } else {
+            jsonSchema = itemsSchema(attrType);
+        }
+
         final String description = attrType.getDescription();
         if (description != null && !"".equals(description)) {
             jsonSchema.put("title", description);
         }
-
         putWritabilityProperties(jsonSchema);
         return jsonSchema;
     }
 
-    private static String toJsonSchemaType(AttributeType attrType) {
+    private JsonValue itemsSchema(final AttributeType attrType) {
+        final JsonValue itemsSchema = json(object());
+        putTypeAndFormat(itemsSchema, attrType);
+        return itemsSchema;
+    }
+
+    /**
+     * Puts the type and format corresponding to the provided attribute type on the provided JSON
+     * schema.
+     *
+     * @param jsonSchema
+     *          the JSON schema where to put the type and format
+     * @param attrType
+     *          the attribute type for which to infer JSON the type and format
+     * @see <a href=
+     *      "https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#data-types">
+     *      OpenAPI Specification 2.0</a>
+     * @see <a href="https://tools.ietf.org/html/draft-fge-json-schema-validation-00#section-7.3">
+     *      draft-fge-json-schema-validation-00 - Semantic validation with "format" - Defined
+     *      attributes</a>
+     */
+    public static void putTypeAndFormat(JsonValue jsonSchema, AttributeType attrType) {
         if (attrType.isPlaceHolder()) {
-            return "string";
+            jsonSchema.put("type", "string");
+            return;
         }
-        // TODO JNR cannot use switch + SchemaConstants.SYNTAX_DIRECTORY_STRING_OID
-        // because the class is not public
-        // this is not nice :(
-        // TODO JNR not so sure about these mappings
-        final String oid = attrType.getSyntax().getOID();
-        if (CoreSchema.getDirectoryStringSyntax().getOID().equals(oid)
-                || CoreSchema.getOctetStringSyntax().getOID().equals(oid)) {
-            return "string";
-        } else if (CoreSchema.getBooleanSyntax().getOID().equals(oid)) {
-            return "boolean";
-        } else if (CoreSchema.getIntegerSyntax().getOID().equals(oid)) {
-            return "integer";
-        } else if (CoreSchema.getNumericStringSyntax().getOID().equals(oid)) {
-            return "number";
+
+        final Syntax syntax = attrType.getSyntax();
+        if (attrType.hasName("userPassword")) {
+            jsonSchema.put("type", "string");
+            jsonSchema.put("format", "password");
+        } else if (attrType.hasName("mail")) {
+            jsonSchema.put("type", "string");
+            jsonSchema.put("format", "email");
+        } else if (syntax.equals(getBooleanSyntax())) {
+            jsonSchema.put("type", "boolean");
+        } else if (syntax.equals(getNumericStringSyntax())) {
+            // credit card numbers are numeric strings whose leading zeros are significant
+            jsonSchema.put("type", "string");
+        } else if (syntax.equals(getIntegerSyntax())) {
+            jsonSchema.put("type", "integer");
+        } else if (syntax.equals(getGeneralizedTimeSyntax())) {
+            jsonSchema.put("type", "string");
+            jsonSchema.put("format", "date-time");
+        } else if (!syntax.isHumanReadable()) {
+            jsonSchema.put("type", "string");
+            jsonSchema.put("format", "byte");
+        } else {
+            jsonSchema.put("type", "string");
         }
-        return "string";
     }
 }

--
Gitblit v1.10.0