From d28b40a9868880bfeb999250eb7ca6fc731e6a87 Mon Sep 17 00:00:00 2001
From: Matthew Swift <matthew.swift@forgerock.com>
Date: Tue, 28 Jun 2016 21:19:00 +0000
Subject: [PATCH] OPENDJ-3186 Improve API version support in admin and rest2ldap endpoints

---
 opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Rest2LdapJsonConfigurator.java         |   24 +++++++-
 opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/SubResourceCollection.java             |   10 ++-
 opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Rest2LdapHttpApplication.java          |    5 +
 opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/SubResourceSingleton.java              |   10 ++-
 opendj-rest2ldap/src/main/resources/org/forgerock/opendj/rest2ldap/rest2ldap.properties              |    2 
 opendj-server-legacy/src/main/java/org/opends/server/protocols/http/rest2ldap/Rest2LdapEndpoint.java |    6 +
 opendj-server-legacy/src/messages/org/opends/messages/config.properties                              |    2 
 opendj-server-legacy/src/main/java/org/opends/server/protocols/http/rest2ldap/AdminEndpoint.java     |   29 +++++++++
 opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/ReadOnlyRequestHandler.java            |    7 ++
 opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/AbstractRequestHandler.java            |   39 ++++++++----
 10 files changed, 106 insertions(+), 28 deletions(-)

diff --git a/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/AbstractRequestHandler.java b/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/AbstractRequestHandler.java
index b0f427b..338a155 100644
--- a/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/AbstractRequestHandler.java
+++ b/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/AbstractRequestHandler.java
@@ -25,6 +25,7 @@
 import org.forgerock.json.resource.QueryResourceHandler;
 import org.forgerock.json.resource.QueryResponse;
 import org.forgerock.json.resource.ReadRequest;
+import org.forgerock.json.resource.Request;
 import org.forgerock.json.resource.RequestHandler;
 import org.forgerock.json.resource.ResourceException;
 import org.forgerock.json.resource.ResourceResponse;
@@ -34,51 +35,63 @@
 
 /**
  * An abstract base class from which request handlers may be easily implemented. The default implementation of each
- * method is to return the {@link ResourceException} passed in during construction.
+ * method is to invoke the {@link #handleRequest(Context, Request)} method.
  */
-abstract class AbstractRequestHandler implements RequestHandler {
-    private final ResourceException defaultErrorResponse;
-
-    AbstractRequestHandler(final ResourceException defaultErrorResponse) {
-        this.defaultErrorResponse = defaultErrorResponse;
+public abstract class AbstractRequestHandler implements RequestHandler {
+    /** Creates a new {@code AbstractRequestHandler}. */
+    protected AbstractRequestHandler() {
+        // Nothing to do.
     }
 
     @Override
     public Promise<ActionResponse, ResourceException> handleAction(final Context context, final ActionRequest request) {
-        return defaultErrorResponse.asPromise();
+        return handleRequest(context, request);
     }
 
     @Override
     public Promise<ResourceResponse, ResourceException> handleCreate(final Context context,
                                                                      final CreateRequest request) {
-        return defaultErrorResponse.asPromise();
+        return handleRequest(context, request);
     }
 
     @Override
     public Promise<ResourceResponse, ResourceException> handleDelete(final Context context,
                                                                      final DeleteRequest request) {
-        return defaultErrorResponse.asPromise();
+        return handleRequest(context, request);
     }
 
     @Override
     public Promise<ResourceResponse, ResourceException> handlePatch(final Context context, final PatchRequest request) {
-        return defaultErrorResponse.asPromise();
+        return handleRequest(context, request);
     }
 
     @Override
     public Promise<QueryResponse, ResourceException> handleQuery(final Context context, final QueryRequest request,
                                                                  final QueryResourceHandler handler) {
-        return defaultErrorResponse.asPromise();
+        return handleRequest(context, request);
     }
 
     @Override
     public Promise<ResourceResponse, ResourceException> handleRead(final Context context, final ReadRequest request) {
-        return defaultErrorResponse.asPromise();
+        return handleRequest(context, request);
     }
 
     @Override
     public Promise<ResourceResponse, ResourceException> handleUpdate(final Context context,
                                                                      final UpdateRequest request) {
-        return defaultErrorResponse.asPromise();
+        return handleRequest(context, request);
     }
+
+    /**
+     * Implement this method in order to provide a default behavior when processing requests.
+     *
+     * @param <V>
+     *         The type of response.
+     * @param context
+     *         The request context.
+     * @param request
+     *         The request.
+     * @return A {@code Promise} containing the result of the operation.
+     */
+    protected abstract <V> Promise<V, ResourceException> handleRequest(final Context context, final Request request);
 }
diff --git a/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/ReadOnlyRequestHandler.java b/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/ReadOnlyRequestHandler.java
index b7cc7d3..1c16427 100644
--- a/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/ReadOnlyRequestHandler.java
+++ b/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/ReadOnlyRequestHandler.java
@@ -23,6 +23,7 @@
 import org.forgerock.json.resource.QueryResourceHandler;
 import org.forgerock.json.resource.QueryResponse;
 import org.forgerock.json.resource.ReadRequest;
+import org.forgerock.json.resource.Request;
 import org.forgerock.json.resource.RequestHandler;
 import org.forgerock.json.resource.ResourceException;
 import org.forgerock.json.resource.ResourceResponse;
@@ -36,7 +37,6 @@
     private final RequestHandler delegate;
 
     ReadOnlyRequestHandler(final RequestHandler delegate) {
-        super(new BadRequestException(ERR_READ_ONLY_ENDPOINT.get().toString()));
         this.delegate = delegate;
     }
 
@@ -51,4 +51,9 @@
             final Context context, final ReadRequest request) {
         return delegate.handleRead(context, request);
     }
+
+    @Override
+    protected <V> Promise<V, ResourceException> handleRequest(final Context context, final Request request) {
+        return new BadRequestException(ERR_READ_ONLY_ENDPOINT.get().toString()).asPromise();
+    }
 }
diff --git a/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Rest2LdapHttpApplication.java b/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Rest2LdapHttpApplication.java
index 068b638..dc54f07 100644
--- a/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Rest2LdapHttpApplication.java
+++ b/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Rest2LdapHttpApplication.java
@@ -18,6 +18,8 @@
 
 import static org.forgerock.http.handler.HttpClientHandler.OPTION_KEY_MANAGERS;
 import static org.forgerock.http.handler.HttpClientHandler.OPTION_TRUST_MANAGERS;
+import static org.forgerock.http.routing.RouteMatchers.newResourceApiVersionBehaviourManager;
+import static org.forgerock.http.routing.RouteMatchers.resourceApiVersionContextFilter;
 import static org.forgerock.json.JsonValueFunctions.duration;
 import static org.forgerock.json.JsonValueFunctions.enumConstant;
 import static org.forgerock.json.JsonValueFunctions.setOf;
@@ -201,7 +203,8 @@
             final Filter authorizationFilter = buildAuthorizationFilter(config.get("authorization").required());
             return Handlers.chainOf(newHttpHandler(configureRest2Ldap(configDirectory)),
                                     new ErrorLoggerFilter(),
-                                    authorizationFilter);
+                                    authorizationFilter,
+                                    resourceApiVersionContextFilter(newResourceApiVersionBehaviourManager()));
         } catch (final Exception e) {
             final LocalizableMessage errorMsg = ERR_FAIL_PARSE_CONFIGURATION.get(e.getLocalizedMessage());
             logger.error(errorMsg, e);
diff --git a/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Rest2LdapJsonConfigurator.java b/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Rest2LdapJsonConfigurator.java
index f67ee7d..182bd30 100644
--- a/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Rest2LdapJsonConfigurator.java
+++ b/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Rest2LdapJsonConfigurator.java
@@ -40,6 +40,7 @@
 import static org.forgerock.opendj.rest2ldap.Rest2Ldap.*;
 import static org.forgerock.opendj.rest2ldap.Rest2ldapMessages.*;
 import static org.forgerock.opendj.rest2ldap.Utils.newJsonValueException;
+import static org.forgerock.util.Utils.joinAsString;
 import static org.forgerock.util.time.Duration.duration;
 
 import java.io.BufferedReader;
@@ -64,14 +65,19 @@
 import org.forgerock.i18n.LocalizedIllegalArgumentException;
 import org.forgerock.i18n.slf4j.LocalizedLogger;
 import org.forgerock.json.JsonValue;
+import org.forgerock.json.resource.BadRequestException;
+import org.forgerock.json.resource.Request;
 import org.forgerock.json.resource.RequestHandler;
+import org.forgerock.json.resource.ResourceException;
 import org.forgerock.json.resource.Router;
 import org.forgerock.opendj.ldap.ConnectionFactory;
 import org.forgerock.opendj.ldap.LDAPConnectionFactory;
 import org.forgerock.opendj.ldap.SSLContextBuilder;
 import org.forgerock.opendj.ldap.requests.BindRequest;
 import org.forgerock.opendj.ldap.requests.Requests;
+import org.forgerock.services.context.Context;
 import org.forgerock.util.Options;
+import org.forgerock.util.promise.Promise;
 import org.forgerock.util.time.Duration;
 
 /** Provides core factory methods and builders for constructing Rest2Ldap endpoints from JSON configuration. */
@@ -209,7 +215,6 @@
      */
     public static Router configureEndpoint(final File endpointDirectory, final Options options) throws IOException {
         final Router versionRouter = new Router();
-
         final File[] endpointVersions = endpointDirectory.listFiles(new FileFilter() {
             @Override
             public boolean accept(final File pathname) {
@@ -221,6 +226,8 @@
             throw new LocalizedIllegalArgumentException(ERR_INVALID_ENDPOINT_DIRECTORY.get(endpointDirectory));
         }
 
+        final List<String> supportedVersions = new ArrayList<>();
+        boolean hasWildCardVersion = false;
         for (final File endpointVersion : endpointVersions) {
             final JsonValue mappingConfig = readJson(endpointVersion);
             final String version = mappingConfig.get("version").defaultTo("*").asString();
@@ -234,13 +241,24 @@
 
             if (version.equals("*")) {
                 versionRouter.setDefaultRoute(handler);
+                hasWildCardVersion = true;
             } else {
                 versionRouter.addRoute(version(version), handler);
+                supportedVersions.add(version);
             }
-
             logger.debug(INFO_REST2LDAP_CREATING_ENDPOINT.get(endpointDirectory.getName(), version));
         }
-
+        if (!hasWildCardVersion) {
+            versionRouter.setDefaultRoute(new AbstractRequestHandler() {
+                @Override
+                protected <V> Promise<V, ResourceException> handleRequest(Context context, Request request) {
+                    final String message = ERR_BAD_API_RESOURCE_VERSION.get(request.getResourceVersion(),
+                                                                            joinAsString(", ", supportedVersions))
+                                                                       .toString();
+                    return new BadRequestException(message).asPromise();
+                }
+            });
+        }
         return versionRouter;
     }
 
diff --git a/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/SubResourceCollection.java b/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/SubResourceCollection.java
index b420b0d..0684f57 100644
--- a/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/SubResourceCollection.java
+++ b/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/SubResourceCollection.java
@@ -41,6 +41,7 @@
 import org.forgerock.json.resource.QueryResourceHandler;
 import org.forgerock.json.resource.QueryResponse;
 import org.forgerock.json.resource.ReadRequest;
+import org.forgerock.json.resource.Request;
 import org.forgerock.json.resource.RequestHandler;
 import org.forgerock.json.resource.ResourceException;
 import org.forgerock.json.resource.ResourceResponse;
@@ -356,10 +357,6 @@
      * URL template /collection/{id} then this handler processes requests against /collection.
      */
     private final class CollectionHandler extends AbstractRequestHandler {
-        private CollectionHandler() {
-            super(new BadRequestException(ERR_UNSUPPORTED_REQUEST_AGAINST_COLLECTION.get().toString()));
-        }
-
         @Override
         public Promise<ActionResponse, ResourceException> handleAction(final Context context,
                                                                        final ActionRequest request) {
@@ -377,6 +374,11 @@
                                                                      final QueryResourceHandler handler) {
             return collection(context).query(context, request, handler);
         }
+
+        @Override
+        protected <V> Promise<V, ResourceException> handleRequest(final Context context, final Request request) {
+            return new BadRequestException(ERR_UNSUPPORTED_REQUEST_AGAINST_COLLECTION.get().toString()).asPromise();
+        }
     }
 
     /**
diff --git a/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/SubResourceSingleton.java b/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/SubResourceSingleton.java
index d0dd95c..a90ea8b 100644
--- a/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/SubResourceSingleton.java
+++ b/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/SubResourceSingleton.java
@@ -35,6 +35,7 @@
 import org.forgerock.json.resource.QueryResourceHandler;
 import org.forgerock.json.resource.QueryResponse;
 import org.forgerock.json.resource.ReadRequest;
+import org.forgerock.json.resource.Request;
 import org.forgerock.json.resource.RequestHandler;
 import org.forgerock.json.resource.ResourceException;
 import org.forgerock.json.resource.ResourceResponse;
@@ -148,10 +149,6 @@
      * both a singleton and also a collection of {child}.
      */
     private final class InstanceHandler extends AbstractRequestHandler {
-        private InstanceHandler() {
-            super(new BadRequestException(ERR_UNSUPPORTED_REQUEST_AGAINST_SINGLETON.get().toString()));
-        }
-
         @Override
         public Promise<ActionResponse, ResourceException> handleAction(final Context context,
                                                                        final ActionRequest request) {
@@ -200,6 +197,11 @@
             return singleton(context).update(context, null, request);
         }
 
+        @Override
+        protected <V> Promise<V, ResourceException> handleRequest(final Context context, final Request request) {
+            return new BadRequestException(ERR_UNSUPPORTED_REQUEST_AGAINST_SINGLETON.get().toString()).asPromise();
+        }
+
         private <T> Function<ResourceException, T, ResourceException> convert404To400() {
             return SubResource.convert404To400(ERR_UNSUPPORTED_REQUEST_AGAINST_SINGLETON.get());
         }
diff --git a/opendj-rest2ldap/src/main/resources/org/forgerock/opendj/rest2ldap/rest2ldap.properties b/opendj-rest2ldap/src/main/resources/org/forgerock/opendj/rest2ldap/rest2ldap.properties
index 3f56fc3..4264584 100644
--- a/opendj-rest2ldap/src/main/resources/org/forgerock/opendj/rest2ldap/rest2ldap.properties
+++ b/opendj-rest2ldap/src/main/resources/org/forgerock/opendj/rest2ldap/rest2ldap.properties
@@ -138,3 +138,5 @@
 INFO_REST2LDAP_CREATING_ENDPOINT_81=Rest2Ldap created endpoint '%s' version %s
 ERR_PASSWORD_RESET_SECURE_CONNECTION_82=Passwords can only be reset using a secure connection
 ERR_PASSWORD_RESET_USER_AUTHENTICATED_83=Passwords can only be reset by authenticated users
+ERR_BAD_API_RESOURCE_VERSION_84=The requested resource API version '%s' is unsupported. This endpoint only supports \
+  the following resource API version(s): %s
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 b0a7fb5..d0f5a5f 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
@@ -16,6 +16,10 @@
  */
 package org.opends.server.protocols.http.rest2ldap;
 
+import static org.forgerock.http.handler.Handlers.chainOf;
+import static org.forgerock.http.routing.RouteMatchers.newResourceApiVersionBehaviourManager;
+import static org.forgerock.http.routing.RouteMatchers.resourceApiVersionContextFilter;
+import static org.forgerock.http.routing.Version.version;
 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;
@@ -24,6 +28,7 @@
 import static org.forgerock.opendj.rest2ldap.WritabilityPolicy.READ_ONLY;
 import static org.forgerock.opendj.rest2ldap.WritabilityPolicy.READ_WRITE;
 import static org.forgerock.util.Options.defaultOptions;
+import static org.opends.messages.ConfigMessages.ERR_BAD_ADMIN_API_RESOURCE_VERSION;
 
 import java.util.ArrayList;
 import java.util.Collection;
@@ -35,8 +40,13 @@
 import org.forgerock.http.HttpApplication;
 import org.forgerock.http.HttpApplicationException;
 import org.forgerock.http.io.Buffer;
+import org.forgerock.http.routing.Version;
 import org.forgerock.json.JsonPointer;
+import org.forgerock.json.resource.BadRequestException;
+import org.forgerock.json.resource.Request;
 import org.forgerock.json.resource.RequestHandler;
+import org.forgerock.json.resource.ResourceException;
+import org.forgerock.json.resource.Router;
 import org.forgerock.opendj.config.AbstractManagedObjectDefinition;
 import org.forgerock.opendj.config.AggregationPropertyDefinition;
 import org.forgerock.opendj.config.DefaultBehaviorProvider;
@@ -55,6 +65,7 @@
 import org.forgerock.opendj.ldap.DN;
 import org.forgerock.opendj.ldap.Functions;
 import org.forgerock.opendj.ldap.schema.Syntax;
+import org.forgerock.opendj.rest2ldap.AbstractRequestHandler;
 import org.forgerock.opendj.rest2ldap.ReferencePropertyMapper;
 import org.forgerock.opendj.rest2ldap.Resource;
 import org.forgerock.opendj.rest2ldap.Rest2Ldap;
@@ -64,9 +75,11 @@
 import org.forgerock.opendj.server.config.meta.GlobalCfgDefn;
 import org.forgerock.opendj.server.config.meta.RootCfgDefn;
 import org.forgerock.opendj.server.config.server.AdminEndpointCfg;
+import org.forgerock.services.context.Context;
 import org.forgerock.util.Factory;
 import org.forgerock.util.Function;
 import org.forgerock.util.promise.NeverThrowsException;
+import org.forgerock.util.promise.Promise;
 import org.opends.server.api.HttpEndpoint;
 import org.opends.server.core.ServerContext;
 import org.opends.server.types.InitializationException;
@@ -76,6 +89,7 @@
  */
 public final class AdminEndpoint extends HttpEndpoint<AdminEndpointCfg>
 {
+  private static final Version ADMIN_API_VERSION = version("1.0");
   private static final String TYPE_PROPERTY = "_schema";
   private static final String ADMIN_API = "admin-api";
   private static final String MONITOR = "monitor";
@@ -165,7 +179,20 @@
 
       final Rest2Ldap rest2Ldap = rest2Ldap(defaultOptions(), resources.values());
       final RequestHandler handler = rest2Ldap.newRequestHandlerFor(ADMIN_API);
-      return newHttpHandler(handler);
+      final Router versionRouter = new Router();
+      versionRouter.addRoute(ADMIN_API_VERSION, handler);
+      versionRouter.setDefaultRoute(new AbstractRequestHandler()
+      {
+        @Override
+        protected <V> Promise<V, ResourceException> handleRequest(final Context context, final Request request)
+        {
+          final String message = ERR_BAD_ADMIN_API_RESOURCE_VERSION.get(request.getResourceVersion(), ADMIN_API_VERSION)
+                                                                   .toString();
+          return new BadRequestException(message).asPromise();
+        }
+      });
+      return chainOf(newHttpHandler(versionRouter),
+                     resourceApiVersionContextFilter(newResourceApiVersionBehaviourManager()));
     }
 
     private Resource buildResource(final AbstractManagedObjectDefinition<?, ?> mod)
diff --git a/opendj-server-legacy/src/main/java/org/opends/server/protocols/http/rest2ldap/Rest2LdapEndpoint.java b/opendj-server-legacy/src/main/java/org/opends/server/protocols/http/rest2ldap/Rest2LdapEndpoint.java
index b328186..4ce2892 100644
--- a/opendj-server-legacy/src/main/java/org/opends/server/protocols/http/rest2ldap/Rest2LdapEndpoint.java
+++ b/opendj-server-legacy/src/main/java/org/opends/server/protocols/http/rest2ldap/Rest2LdapEndpoint.java
@@ -15,6 +15,9 @@
  */
 package org.opends.server.protocols.http.rest2ldap;
 
+import static org.forgerock.http.handler.Handlers.chainOf;
+import static org.forgerock.http.routing.RouteMatchers.newResourceApiVersionBehaviourManager;
+import static org.forgerock.http.routing.RouteMatchers.resourceApiVersionContextFilter;
 import static org.forgerock.json.resource.http.CrestHttp.newHttpHandler;
 import static org.forgerock.opendj.rest2ldap.Rest2LdapJsonConfigurator.configureEndpoint;
 import static org.forgerock.util.Options.defaultOptions;
@@ -76,7 +79,8 @@
       final File endpointConfig = getFileForPath(configuration.getConfigDirectory(), serverContext);
       try
       {
-        return newHttpHandler(configureEndpoint(endpointConfig, defaultOptions()));
+        return chainOf(newHttpHandler(configureEndpoint(endpointConfig, defaultOptions())),
+                       resourceApiVersionContextFilter(newResourceApiVersionBehaviourManager()));
       }
       catch (IOException e)
       {
diff --git a/opendj-server-legacy/src/messages/org/opends/messages/config.properties b/opendj-server-legacy/src/messages/org/opends/messages/config.properties
index 6e71327..dda7699 100644
--- a/opendj-server-legacy/src/messages/org/opends/messages/config.properties
+++ b/opendj-server-legacy/src/messages/org/opends/messages/config.properties
@@ -851,3 +851,5 @@
  an invalid URL %s: %s
 ERR_CONFIG_OAUTH2_CONFIG_ERROR_756=Unable to configure the authorization mechanism defined \
  in %s: %s
+ERR_BAD_ADMIN_API_RESOURCE_VERSION_757=The requested admin API version '%s' is unsupported. This endpoint only \
+  supports the following admin API version(s): %s

--
Gitblit v1.10.0