 acquia_contenthub.services.yml                    |   2 +-
 src/Controller/ContentHubEntityRequestHandler.php | 111 ++++++++++++++++++++++
 src/Routing/ResourceRoutes.php                    | 101 ++++++++++----------
 3 files changed, 163 insertions(+), 51 deletions(-)

diff --git a/acquia_contenthub.services.yml b/acquia_contenthub.services.yml
index 8e5f7ef..8d8d528 100644
--- a/acquia_contenthub.services.yml
+++ b/acquia_contenthub.services.yml
@@ -67,7 +67,7 @@ services:
 
   acquia_contenthub.resource_routes:
     class: Drupal\acquia_contenthub\Routing\ResourceRoutes
-    arguments: ['@plugin.manager.rest', '@config.factory', '@acquia_contenthub.entity_manager']
+    arguments: ['@plugin.manager.rest', '@config.factory', '@acquia_contenthub.entity_manager', '@entity_type.manager']
     tags:
       - { name: 'event_subscriber' }
 
diff --git a/src/Controller/ContentHubEntityRequestHandler.php b/src/Controller/ContentHubEntityRequestHandler.php
new file mode 100644
index 0000000..d8362ac
--- /dev/null
+++ b/src/Controller/ContentHubEntityRequestHandler.php
@@ -0,0 +1,111 @@
+<?php
+
+namespace Drupal\acquia_contenthub\Controller;
+
+use Drupal\Component\Plugin\PluginManagerInterface;
+use Drupal\Core\Render\RenderContext;
+use Drupal\Core\Routing\RouteMatchInterface;
+use Drupal\rest\RequestHandler;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+use Symfony\Component\HttpFoundation\Request;
+
+/**
+ * Decorates the REST module's RequestHandler.
+ *
+ * Because the Acquia Content Hub module needs to expose REST access:
+ * - for certain entity types,
+ * - but always only GET,
+ * - but always only its own special format,
+ * - but always only its own special authentication,
+ * - but always only its own special authorization (access checking),
+ * it doesn't make sense to rely on the REST module's default pattern of having
+ * a RestResourceConfig configuration entity. That configuration entity is what
+ * determines which methods/formats/authentication are enabled for a given REST
+ * resource. But this module has the need to fully control that.
+ *
+ * So, we still rely on these to benefit from improvements/bugfixes:
+ * - \Drupal\rest\RequestHandler
+ * - \Drupal\rest\Plugin\rest\resource\EntityResource
+ *
+ * But we decorate RequestHandler to make it not rely on configuration entities,
+ * and to make it instead fully depend on the information in the route. This
+ * unfortunately requires some duplication.
+ *
+ * @todo Remove this when https://www.drupal.org/node/2822201 lands, and this module is able to require Drupal 8.3.x.
+ */
+class ContentHubEntityRequestHandler extends RequestHandler {
+
+  /**
+   * The resource plugin manager.
+   *
+   * @var \Drupal\Component\Plugin\PluginManagerInterface
+   */
+  protected $resourcePluginManager;
+
+  /**
+   * Creates a new RequestHandler instance.
+   *
+   * @param \Drupal\Component\Plugin\PluginManagerInterface $resource_plugin_manager
+   *   The resource plugin manager.
+   */
+  public function __construct(PluginManagerInterface $resource_plugin_manager) {
+    $this->resourcePluginManager = $resource_plugin_manager;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container) {
+    return new static($container->get('plugin.manager.rest'));
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function handle(RouteMatchInterface $route_match, Request $request) {
+    // We only support one method, one format, and use one of the derivatives of
+    // only one resource plugin. (We receive the exact plugin ID via the route
+    // defaults.)
+    $method = 'GET';
+    $format = 'acquia_contenthub_cdf';
+    $resource = $this->resourcePluginManager->createInstance($route_match->getRouteObject()->getDefault('_acquia_content_hub_rest_resource_plugin_id'));
+
+
+    // EVERYTHING BELOW THIS IS MERE DUPLICATION OF THE DECORATED CLASS IN THE
+    // MOST MINIMAL WAY POSSIBLE, AND REMOVING THE DEPENDENCY ON CONFIG ENTITIES
+
+
+    // Determine the request parameters that should be passed to the resource
+    // plugin.
+    $route_parameters = $route_match->getParameters();
+    $parameters = array();
+    // Filter out all internal parameters starting with "_".
+    foreach ($route_parameters as $key => $parameter) {
+      if ($key{0} !== '_') {
+        $parameters[] = $parameter;
+      }
+    }
+
+    // Invoke the operation on the resource plugin.
+    $serializer = $this->container->get('serializer');
+    $unserialized = NULL;
+    $response = call_user_func_array(array($resource, $method), array_merge($parameters, [$unserialized, $request]));
+
+    // Render response.
+    $data = $response->getResponseData();
+    $context = new RenderContext();
+    $output = $this->container->get('renderer')
+      ->executeInRenderContext($context, function () use ($serializer, $data, $format) {
+        return $serializer->serialize($data, $format);
+      });
+
+    if (!$context->isEmpty()) {
+      $response->addCacheableDependency($context->pop());
+    }
+    $response->setContent($output);
+    $response->headers->set('Content-Type', $request->getMimeType($format));
+
+    return $response;
+  }
+
+}
diff --git a/src/Routing/ResourceRoutes.php b/src/Routing/ResourceRoutes.php
index 4b5f0f4..afcae52 100644
--- a/src/Routing/ResourceRoutes.php
+++ b/src/Routing/ResourceRoutes.php
@@ -8,14 +8,16 @@
 namespace Drupal\acquia_contenthub\Routing;
 
 use Drupal\Core\Config\ConfigFactoryInterface;
+use Drupal\Core\Entity\EntityTypeManagerInterface;
 use Drupal\Core\Routing\RouteSubscriberBase;
 use Drupal\rest\Plugin\Type\ResourcePluginManager;
 use Drupal\acquia_contenthub\EntityManager;
+use Symfony\Component\Routing\Route;
 use Symfony\Component\Routing\RouteCollection;
 
 
 /**
- * Subscriber for REST-style routes.
+ * Subscriber for Acquia Content Hub REST routes.
  */
 class ResourceRoutes extends RouteSubscriberBase {
 
@@ -23,6 +25,8 @@ class ResourceRoutes extends RouteSubscriberBase {
    * The Drupal configuration factory.
    *
    * @var \Drupal\Core\Config\ConfigFactoryInterface
+   *
+   * @todo remove
    */
   protected $config;
 
@@ -30,6 +34,8 @@ class ResourceRoutes extends RouteSubscriberBase {
    * The plugin manager for REST plugins.
    *
    * @var \Drupal\rest\Plugin\Type\ResourcePluginManager
+   *
+   * @todo remove
    */
   protected $manager;
 
@@ -41,7 +47,14 @@ class ResourceRoutes extends RouteSubscriberBase {
   protected $entityManager;
 
   /**
-   * Constructs a RouteSubscriber object.
+   * The entity type manager.
+   *
+   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
+   */
+  protected $entityTypeManager;
+
+  /**
+   * Constructs a ResourceRoutes object.
    *
    * @param \Drupal\rest\Plugin\Type\ResourcePluginManager $manager
    *   The resource plugin manager.
@@ -49,68 +62,56 @@ class ResourceRoutes extends RouteSubscriberBase {
    *   The configuration factory holding resource settings.
    * @param \Drupal\acquia_contenthub\EntityManager $entity_manager
    *   The entity manager for Content Hub.
+   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
+   *   The entity type manager.
    */
-  public function __construct(ResourcePluginManager $manager, ConfigFactoryInterface $config, EntityManager $entity_manager) {
+  public function __construct(ResourcePluginManager $manager, ConfigFactoryInterface $config, EntityManager $entity_manager, EntityTypeManagerInterface $entity_type_manager) {
     $this->config = $config;
     $this->manager = $manager;
     $this->entityManager = $entity_manager;
+    $this->entityTypeManager = $entity_type_manager;
   }
 
   /**
-   * Alters existing routes for a specific collection.
+   * Generates Content Hub REST resource routes every eligible entity type.
    *
    * @param \Symfony\Component\Routing\RouteCollection $collection
    *   The route collection for adding routes.
    */
   protected function alterRoutes(RouteCollection $collection) {
-
+    // @todo the returned allowed entity types are wrong, see https://www.drupal.org/node/2822033. This means that we're generating routes even for entity types which have not been enabled at /admin/config/services/acquia-contenthub/configuration.
     $allowed_entity_types = $this->entityManager->getAllowedEntityTypes();
-    // ResourcePluginManager $manager.
-    /* @var \Drupal\rest\Plugin\ResourceInterface[] $resources */
-    $resources = $this->manager->getDefinitions();
-
-    // Iterate over all enabled resource plugins.
-    foreach ($resources as $id => $enabled_methods) {
-      /* @var \Drupal\rest\Plugin\rest\resource\EntityResource $plugin */
-      $plugin = $this->manager->getInstance(array('id' => $id));
-
-      /* @var \Symfony\Component\Routing\Route $route */
-      foreach ($plugin->routes() as $name => $route) {
-        // @todo: Are multiple methods possible here?
-        $methods = $route->getMethods();
-        // Only expose routes where the method is GET.
-        if ($methods[0] != "GET") {
-          continue;
-        }
-        // We have a couple of GET's in the list (XML, JSON, and potentially
-        // content_hubOnly add it once, so filter on the JSON one to make sure
-        // we only add it once.
-        if ($route->getRequirement('_format') !== 'json') {
-          continue;
-        }
-        // Unset routes that are not in our list.
-        if (!in_array($plugin->getDerivativeId(), array_keys($allowed_entity_types))) {
-          $route_name = 'acquia_contenthub.entity.' . $plugin->getDerivativeId() . '.GET.acquia_contenthub_cdf';
-          $collection->remove($route_name);
-          continue;
-        }
-
-        $route->setRequirement('_format', 'acquia_contenthub_cdf');
-
-        // Only allow access to the CDF if the request is coming from a logged
-        // in user with 'Administer Acquia Content Hub' permission or if it
-        // is coming from Acquia Content Hub (validates the HMAC signature).
-        $route->setRequirement('_contenthub_access_check', 'TRUE');
-
-        // Remove the permission required. Open for all and controlled by
-        // entity_access.
-        $requirements = $route->getRequirements();
-        unset($requirements['_permission']);
-        $route->setRequirements($requirements);
 
-        $route_name = 'acquia_contenthub.entity.' . $plugin->getDerivativeId() . '.GET.acquia_contenthub_cdf';
-        $collection->add($route_name, $route);
-      }
+    foreach (array_keys($allowed_entity_types) as $entity_type_id) {
+      // Match the behavior of \Drupal\rest\Plugin\rest\resource\EntityResource:
+      // use the entity type's canonical link template if it has one, otherwise
+      // use EntityResource's generic alternative.
+      $entity_type = $this->entityTypeManager->getDefinition($entity_type_id);
+      $canonical_path = $entity_type->hasLinkTemplate('canonical')
+        ? $entity_type->getLinkTemplate('canonical')
+        : '/entity/' . $entity_type_id . '/{' . $entity_type_id . '}';
+
+      $route = new Route($canonical_path, [
+        '_controller' => '\Drupal\acquia_contenthub\Controller\ContentHubEntityRequestHandler::handle',
+        // @see \Drupal\acquia_contenthub\Controller\ContentHubEntityRequestHandler
+        // @todo Remove this when https://www.drupal.org/node/2822201 lands, and this module is able to require Drupal 8.3.x.
+        '_acquia_content_hub_rest_resource_plugin_id' => 'entity:' . $entity_type_id,
+      ]);
+      $route->setOption('parameters', [
+        $entity_type_id => [
+          'type' => 'entity:' . $entity_type_id,
+        ],
+      ]);
+      // Only allow the Acquia Content Hub CDF format.
+      $route->setRequirement('_format', 'acquia_contenthub_cdf');
+      // Only allow access to the CDF if the request is coming from a logged
+      // in user with 'Administer Acquia Content Hub' permission or if it
+      // is coming from Acquia Content Hub (validates the HMAC signature).
+      $route->setRequirement('_contenthub_access_check', 'TRUE');
+      // Only allow GET.
+      $route->setMethods(['GET']);
+
+      $collection->add('acquia_contenthub.entity.' . $entity_type_id . '.GET.acquia_contenthub_cdf', $route);
     }
   }
 
