From 4a164f90a4bab55c79314343d6127570a8fbed10 Mon Sep 17 00:00:00 2001
From: Jean-Noël Rouvignac <jean-noel.rouvignac@forgerock.com>
Date: Mon, 19 Sep 2016 09:18:00 +0000
Subject: [PATCH] OPENDJ-3250 Return the CREST descriptor over REST for config endpoint

---
 opendj-server-legacy/src/test/java/org/forgerock/opendj/rest2ldap/AdminEndpointTestCase.java     |  124 +++++++++++++++++++++++++++++++
 opendj-server-legacy/src/main/java/org/opends/server/protocols/http/rest2ldap/AdminEndpoint.java |   47 ++++++++++-
 opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Resource.java                      |   27 ++++++
 3 files changed, 192 insertions(+), 6 deletions(-)

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 07421a0..9aa6c0a 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
@@ -60,6 +60,7 @@
 import org.forgerock.api.models.Services;
 import org.forgerock.api.models.Update;
 import org.forgerock.http.ApiProducer;
+import org.forgerock.i18n.LocalizableMessage;
 import org.forgerock.i18n.LocalizedIllegalArgumentException;
 import org.forgerock.json.JsonPointer;
 import org.forgerock.json.JsonValue;
@@ -128,12 +129,23 @@
     private volatile Boolean hasSubTypesWithSubResources = null;
     /** The set of actions supported by this resource and its sub-types. */
     private final Set<Action> supportedActions = new HashSet<>();
+    private LocalizableMessage description;
 
     Resource(final String id) {
         this.id = id;
     }
 
     /**
+     * Sets the description of this resource.
+     *
+     * @param description
+     *          the description of this resource
+     */
+    public void description(LocalizableMessage description) {
+        this.description = description;
+    }
+
+    /**
      * Returns the resource ID of this resource.
      *
      * @return The resource ID of this resource.
@@ -507,6 +519,8 @@
 
         org.forgerock.api.models.Resource.Builder resource = org.forgerock.api.models.Resource.
             resource()
+            .title(id)
+            .description(toLS(description))
             .resourceSchema(schemaRef("#/definitions/" + id))
             .mvccSupported(isMvccSupported());
 
@@ -522,6 +536,7 @@
         return ApiDescription.apiDescription()
                       .id("unused").version("unused")
                       .definitions(definitions())
+                      .paths(getPaths())
                       .errors(errors())
                       .build();
     }
@@ -536,6 +551,8 @@
     ApiDescription collectionApi(boolean isReadOnly) {
         org.forgerock.api.models.Resource.Builder resource = org.forgerock.api.models.Resource.
             resource()
+            .title(id)
+            .description(toLS(description))
             .resourceSchema(schemaRef("#/definitions/" + id))
             .mvccSupported(isMvccSupported());
 
@@ -584,6 +601,16 @@
         return definitions.build();
     }
 
+    private LocalizableString toLS(LocalizableMessage msg) {
+        if (msg != null) {
+            if (msg.resourceName() != null) {
+                return new LocalizableString("i18n:" + msg.resourceName() + "#" /* TODO + msg.getKey() */);
+            }
+            return new LocalizableString(msg.toString());
+        }
+        return null;
+    }
+
     /**
      * Returns the api description that describes a resource with sub resources.
      *
diff --git a/opendj-server-legacy/src/main/java/org/opends/server/protocols/http/rest2ldap/AdminEndpoint.java b/opendj-server-legacy/src/main/java/org/opends/server/protocols/http/rest2ldap/AdminEndpoint.java
index 7085ae5..2519aaa 100644
--- a/opendj-server-legacy/src/main/java/org/opends/server/protocols/http/rest2ldap/AdminEndpoint.java
+++ b/opendj-server-legacy/src/main/java/org/opends/server/protocols/http/rest2ldap/AdminEndpoint.java
@@ -12,14 +12,12 @@
  * information: "Portions copyright [year] [name of copyright owner]".
  *
  * Copyright 2016 ForgeRock AS.
- *
  */
 package org.opends.server.protocols.http.rest2ldap;
 
 import static org.forgerock.http.routing.RouteMatchers.newResourceApiVersionBehaviourManager;
 import static org.forgerock.http.routing.Version.version;
 import static org.forgerock.json.resource.RouteMatchers.resourceApiVersionContextFilter;
-import static org.forgerock.json.resource.http.CrestHttp.newHttpHandler;
 import static org.forgerock.opendj.ldap.schema.CoreSchema.getBooleanSyntax;
 import static org.forgerock.opendj.ldap.schema.CoreSchema.getIntegerSyntax;
 import static org.forgerock.opendj.rest2ldap.Rest2Ldap.*;
@@ -43,11 +41,15 @@
 import org.forgerock.http.routing.Version;
 import org.forgerock.json.JsonPointer;
 import org.forgerock.json.resource.BadRequestException;
+import org.forgerock.json.resource.ConnectionFactory;
+import org.forgerock.json.resource.CrestApplication;
 import org.forgerock.json.resource.FilterChain;
 import org.forgerock.json.resource.Request;
 import org.forgerock.json.resource.RequestHandler;
 import org.forgerock.json.resource.ResourceException;
+import org.forgerock.json.resource.Resources;
 import org.forgerock.json.resource.Router;
+import org.forgerock.json.resource.http.CrestHttp;
 import org.forgerock.opendj.config.AbstractManagedObjectDefinition;
 import org.forgerock.opendj.config.AggregationPropertyDefinition;
 import org.forgerock.opendj.config.DefaultBehaviorProvider;
@@ -84,6 +86,7 @@
 import org.opends.server.api.HttpEndpoint;
 import org.opends.server.core.ServerContext;
 import org.opends.server.types.InitializationException;
+import org.opends.server.util.BuildVersion;
 
 /**
  * An HTTP endpoint providing access to the server's monitoring backend (cn=monitor) and its configuration (cn=config).
@@ -115,9 +118,7 @@
     return new AdminHttpApplication();
   }
 
-  /**
-   * Specialized {@link HttpApplication} using internal connections to this local LDAP server.
-   */
+  /** Specialized {@link HttpApplication} using internal connections to this local LDAP server. */
   private final class AdminHttpApplication implements HttpApplication
   {
     private LDAPProfile ldapProfile = LDAPProfile.getInstance();
@@ -125,6 +126,11 @@
     @Override
     public Handler start() throws HttpApplicationException
     {
+      return newHttpHandler(startRequestHandler());
+    }
+
+    FilterChain startRequestHandler() throws HttpApplicationException
+    {
       final Map<String, Resource> resources = new HashMap<>();
 
       // Define the entry point to the admin API.
@@ -196,12 +202,41 @@
       // FIXME: Disable the warning header for now due to CREST-389 / CREST-390.
       final ResourceApiVersionBehaviourManager behaviourManager = newResourceApiVersionBehaviourManager();
       behaviourManager.setWarningEnabled(false);
-      return newHttpHandler(new FilterChain(versionRouter, resourceApiVersionContextFilter(behaviourManager)));
+      return new FilterChain(versionRouter, resourceApiVersionContextFilter(behaviourManager));
+    }
+
+    private Handler newHttpHandler(RequestHandler handler)
+    {
+      final org.forgerock.json.resource.ConnectionFactory factory = Resources.newInternalConnectionFactory(handler);
+      return CrestHttp.newHttpHandler(new CrestApplication()
+      {
+        @Override
+        public ConnectionFactory getConnectionFactory()
+        {
+          return factory;
+        }
+
+        @Override
+        public String getApiId()
+        {
+          return "frapi:opendj:admin";
+        }
+
+        @Override
+        public String getApiVersion()
+        {
+          return BuildVersion.binaryVersion().toStringNoRevision();
+        }
+      });
     }
 
     private Resource buildResource(final AbstractManagedObjectDefinition<?, ?> mod)
     {
       final Resource resource = resource(mod.getName());
+      if (!mod.isTop())
+      {
+        resource.description(mod.getSynopsis());
+      }
       configureResourceProperties(mod, resource);
       configureResourceSubResources(mod, resource, false);
       return resource;
diff --git a/opendj-server-legacy/src/test/java/org/forgerock/opendj/rest2ldap/AdminEndpointTestCase.java b/opendj-server-legacy/src/test/java/org/forgerock/opendj/rest2ldap/AdminEndpointTestCase.java
new file mode 100644
index 0000000..ee7d8c1
--- /dev/null
+++ b/opendj-server-legacy/src/test/java/org/forgerock/opendj/rest2ldap/AdminEndpointTestCase.java
@@ -0,0 +1,124 @@
+/*
+ * 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]".
+ *
+ * Portions Copyright 2016 ForgeRock AS.
+ */
+package org.forgerock.opendj.rest2ldap;
+
+import static org.assertj.core.api.Assertions.*;
+import static org.forgerock.http.util.Json.*;
+import static org.forgerock.json.resource.Requests.*;
+import static org.forgerock.json.resource.ResourcePath.*;
+import static org.forgerock.opendj.rest2ldap.Rest2Ldap.*;
+import static org.forgerock.util.Options.*;
+
+import java.io.StringReader;
+import java.lang.reflect.Method;
+import java.util.Collections;
+
+import org.forgerock.api.CrestApiProducer;
+import org.forgerock.api.models.ApiDescription;
+import org.forgerock.http.HttpApplication;
+import org.forgerock.http.routing.UriRouterContext;
+import org.forgerock.http.util.Json;
+import org.forgerock.json.JsonValue;
+import org.forgerock.json.resource.FilterChain;
+import org.forgerock.json.resource.Request;
+import org.forgerock.services.context.Context;
+import org.forgerock.services.context.RootContext;
+import org.opends.server.DirectoryServerTestCase;
+import org.opends.server.TestCaseUtils;
+import org.opends.server.core.DirectoryServer;
+import org.opends.server.protocols.http.rest2ldap.AdminEndpoint;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.ObjectWriter;
+
+@SuppressWarnings("javadoc")
+public class AdminEndpointTestCase extends DirectoryServerTestCase
+{
+  private static final String ID = "frapi:opendj:admin";
+  private static final String VERSION = "4.0.0";
+
+  @BeforeClass
+  public void startServer() throws Exception
+  {
+    TestCaseUtils.startServer();
+  }
+
+  @Test
+  public void testApiDescriptionGeneration() throws Exception
+  {
+    FilterChain endpointHandler = configureEndpoint();
+    final ApiDescription api = requestApi(endpointHandler, "admin/config");
+    assertThat(api).isNotNull();
+
+    // Ensure we can can pretty print and parse back the generated api description
+    parseJson(prettyPrint(api));
+
+    assertThat(api.getId()).isEqualTo(ID + ":1.0");
+    assertThat(api.getVersion()).isEqualTo(VERSION);
+    assertThat(api.getPaths().getNames().size()).isGreaterThan(20);
+    assertThat(api.getDefinitions().getNames().size()).isGreaterThan(150);
+  }
+
+  private FilterChain configureEndpoint() throws Exception
+  {
+    AdminEndpoint adminEndpoint = new AdminEndpoint(null, DirectoryServer.getInstance().getServerContext());
+    HttpApplication httpApp = adminEndpoint.newHttpApplication();
+    FilterChain handler = startRequestHandler(httpApp);
+    handler.api(new CrestApiProducer(ID, VERSION));
+    return handler;
+  }
+
+  private FilterChain startRequestHandler(HttpApplication httpApp) throws Exception
+  {
+    Method m = httpApp.getClass().getDeclaredMethod("startRequestHandler");
+    m.setAccessible(true);
+    FilterChain handler = (FilterChain) m.invoke(httpApp);
+    return handler;
+  }
+
+  private ApiDescription requestApi(final FilterChain filterChain, String uriPath)
+  {
+    Context context = newRouterContext(uriPath);
+    Request request = newApiRequest(resourcePath(uriPath));
+    return filterChain.handleApiRequest(context, request);
+  }
+
+  private Context newRouterContext(final String uriPath)
+  {
+    Context ctx = new RootContext();
+    ctx = new Rest2LdapContext(ctx, rest2Ldap(defaultOptions()));
+    ctx = new UriRouterContext(ctx, null, uriPath, Collections.<String, String> emptyMap());
+    return ctx;
+  }
+
+  private String prettyPrint(Object o) throws Exception
+  {
+    final ObjectMapper objectMapper =
+        new ObjectMapper().registerModules(new Json.LocalizableStringModule(), new Json.JsonValueModule());
+    final ObjectWriter writer = objectMapper.writer().withDefaultPrettyPrinter();
+    return writer.writeValueAsString(o);
+  }
+
+  static JsonValue parseJson(final String json) throws Exception
+  {
+    try (StringReader r = new StringReader(json))
+    {
+      return new JsonValue(readJsonLenient(r));
+    }
+  }
+}

--
Gitblit v1.10.0