From 32271a6f82042a2d83c7292002cd09f6f56b4e50 Mon Sep 17 00:00:00 2001
From: Jean-Noël Rouvignac <jean-noel.rouvignac@forgerock.com>
Date: Thu, 22 Sep 2016 08:43:15 +0000
Subject: [PATCH] OPENDJ-3246 Support polymorphism in CREST API and OpenAPI descriptors

---
 opendj-bom/pom.xml                                                                               |    2 
 opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/ResourceTypePropertyMapper.java    |    2 
 opendj-rest2ldap/src/test/java/org/forgerock/opendj/rest2ldap/Rest2LdapJsonConfiguratorTest.java |    1 
 opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Resource.java                      |   81 +++++++++++++++++++++++++++++++++-------
 4 files changed, 70 insertions(+), 16 deletions(-)

diff --git a/opendj-bom/pom.xml b/opendj-bom/pom.xml
index 074b7f7..3c28663 100644
--- a/opendj-bom/pom.xml
+++ b/opendj-bom/pom.xml
@@ -45,7 +45,7 @@
             <dependency>
                 <groupId>org.forgerock.commons</groupId>
                 <artifactId>commons-bom</artifactId>
-                <version>21.0.0-alpha-12</version>
+                <version>21.0.0-SNAPSHOT</version>
                 <scope>import</scope>
                 <type>pom</type>
             </dependency>
diff --git a/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Resource.java b/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Resource.java
index 1e6dffb..616cdfd 100644
--- a/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Resource.java
+++ b/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Resource.java
@@ -513,7 +513,9 @@
      * @return a new api description that describes a single instance resource.
      */
     ApiDescription instanceApi(boolean isReadOnly) {
-        if (allProperties.isEmpty()) {
+        if (allProperties.isEmpty() && superType == null && subTypes.isEmpty()) {
+            // It is not used in the api description
+            // so do not generate anything for this resource
             return null;
         }
 
@@ -536,7 +538,8 @@
         return ApiDescription.apiDescription()
                       .id("unused").version("unused")
                       .definitions(definitions())
-                      .paths(getPaths())
+                      .services(services(resource))
+                      .paths(paths())
                       .errors(errors())
                       .build();
     }
@@ -576,15 +579,19 @@
         return ApiDescription.apiDescription()
                              .id("unused").version("unused")
                              .definitions(definitions())
-                             .services(Services.services()
-                                               .put(id, resource.build())
-                                               .build())
-                             .paths(getPaths())
+                             .services(services(resource))
+                             .paths(paths())
                              .errors(errors())
                              .build();
     }
 
-    private Paths getPaths() {
+    private Services services(org.forgerock.api.models.Resource.Builder resource) {
+        return Services.services()
+                       .put(id, resource.build())
+                       .build();
+    }
+
+    private Paths paths() {
         return Paths.paths()
                      // do not put anything in the path to avoid unfortunate string concatenation
                      // also use UNVERSIONED and rely on the router to stamp the version
@@ -594,13 +601,34 @@
 
     private Definitions definitions() {
         final Definitions.Builder definitions = Definitions.definitions();
-        definitions.put(id, buildJsonSchema());
-        for (Resource subType : subTypes) {
-            definitions.put(subType.id, subType.buildJsonSchema());
+        for (Resource res : collectTypeHierarchy(this)) {
+            definitions.put(res.id, res.buildJsonSchema());
         }
         return definitions.build();
     }
 
+    private static Iterable<Resource> collectTypeHierarchy(Resource currentType) {
+        final List<Resource> typeHierarchy = new ArrayList<>();
+
+        Resource ancestorType = currentType;
+        while (ancestorType.superType != null) {
+            ancestorType = ancestorType.superType;
+            typeHierarchy.add(ancestorType);
+        }
+
+        typeHierarchy.add(currentType);
+
+        addSubTypes(typeHierarchy, currentType);
+        return typeHierarchy;
+    }
+
+    private static void addSubTypes(final List<Resource> typeHierarchy, Resource res) {
+        for (Resource subType : res.subTypes) {
+            typeHierarchy.add(subType);
+            addSubTypes(typeHierarchy, subType);
+        }
+    }
+
     private LocalizableString toLS(LocalizableMessage msg) {
         if (msg != null) {
             // FIXME this code does cannot work today because LocalizableMessage.getKey() does not exist
@@ -782,7 +810,7 @@
                     field("name", "New Password"),
                     field("description", "New password as a UTF-8 string."),
                     field("format", "password")))))));
-        return Schema.schema().schema(jsonSchema).build();
+        return schema(jsonSchema);
     }
 
     private static org.forgerock.api.models.Action resetPasswordAction() {
@@ -814,13 +842,13 @@
                 field("generatedPassword", object(
                     field("type", "string"),
                     field("description", "Generated password to communicate to the user.")))))));
-        return Schema.schema().schema(jsonSchema).build();
+        return schema(jsonSchema);
     }
 
     private Schema buildJsonSchema() {
         final List<String> requiredFields = new ArrayList<>();
         JsonValue properties = json(JsonValue.object());
-        for (Map.Entry<String, PropertyMapper> prop : allProperties.entrySet()) {
+        for (Map.Entry<String, PropertyMapper> prop : declaredProperties.entrySet()) {
             final String propertyName = prop.getKey();
             final PropertyMapper mapper = prop.getValue();
             if (mapper.isRequired()) {
@@ -833,13 +861,34 @@
         }
 
         final JsonValue jsonSchema = json(object(field("type", "object")));
+        final String discriminator = getDiscriminator();
+        if (discriminator != null) {
+            jsonSchema.put("discriminator", discriminator);
+        }
         if (!requiredFields.isEmpty()) {
             jsonSchema.put("required", requiredFields);
         }
         if (properties.size() > 0) {
             jsonSchema.put("properties", properties);
         }
-        return Schema.schema().schema(jsonSchema).build();
+
+        if (superType != null) {
+            return schema(json(object(
+                field("allOf", array(
+                    object(field("$ref", "#/definitions/" + superType.id)),
+                    jsonSchema)))));
+        }
+        return schema(jsonSchema);
+    }
+
+    private String getDiscriminator() {
+        if (resourceTypeProperty != null) {
+            // Subtypes inherit the resourceTypeProperty from their parent.
+            // The discriminator must only be output for the type that defined it.
+            final String propertyName = resourceTypeProperty.leaf();
+            return declaredProperties.containsKey(propertyName) ? propertyName : null;
+        }
+        return null;
     }
 
     private Errors errors() {
@@ -876,6 +925,10 @@
         return Schema.schema().reference(ref(referenceValue)).build();
     }
 
+    private static org.forgerock.api.models.Schema schema(JsonValue jsonSchema) {
+        return Schema.schema().schema(jsonSchema).build();
+    }
+
     static Reference ref(String referenceValue) {
         return Reference.reference().value(referenceValue).build();
     }
diff --git a/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/ResourceTypePropertyMapper.java b/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/ResourceTypePropertyMapper.java
index d667f91..15e0587 100644
--- a/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/ResourceTypePropertyMapper.java
+++ b/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/ResourceTypePropertyMapper.java
@@ -60,7 +60,7 @@
 
     @Override
     boolean isRequired() {
-        return false;
+        return true;
     }
 
     @Override
diff --git a/opendj-rest2ldap/src/test/java/org/forgerock/opendj/rest2ldap/Rest2LdapJsonConfiguratorTest.java b/opendj-rest2ldap/src/test/java/org/forgerock/opendj/rest2ldap/Rest2LdapJsonConfiguratorTest.java
index 238f4a6..2690bd4 100644
--- a/opendj-rest2ldap/src/test/java/org/forgerock/opendj/rest2ldap/Rest2LdapJsonConfiguratorTest.java
+++ b/opendj-rest2ldap/src/test/java/org/forgerock/opendj/rest2ldap/Rest2LdapJsonConfiguratorTest.java
@@ -69,6 +69,7 @@
         assertThat(api.getVersion()).isEqualTo(VERSION);
         assertThat(api.getPaths().getNames()).containsOnly("/api/users", "/api/groups");
         assertThat(api.getDefinitions().getNames()).containsOnly(
+            "frapi:opendj:rest2ldap:object:1.0",
             "frapi:opendj:rest2ldap:group:1.0",
             "frapi:opendj:rest2ldap:user:1.0",
             "frapi:opendj:rest2ldap:posixUser:1.0");

--
Gitblit v1.10.0