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

diff --git a/acquia_contenthub.services.yml b/acquia_contenthub.services.yml
index 29c0885..d393109 100644
--- a/acquia_contenthub.services.yml
+++ b/acquia_contenthub.services.yml
@@ -63,7 +63,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..7ad9f6e
--- /dev/null
+++ b/src/Controller/ContentHubEntityRequestHandler.php
@@ -0,0 +1,138 @@
+<?php
+
+namespace Drupal\acquia_contenthub\Controller;
+
+use Drupal\Component\Plugin\PluginManagerInterface;
+use Drupal\Core\Cache\CacheableResponseInterface;
+use Drupal\Core\Controller\ControllerBase;
+use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Entity\FieldableEntityInterface;
+use Drupal\Core\Render\RenderContext;
+use Drupal\Core\Render\RendererInterface;
+use Drupal\rest\ResourceResponse;
+use Drupal\rest\ResourceResponseInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
+use Symfony\Component\Serializer\SerializerInterface;
+
+/**
+ * @todo docs
+ * @todo give better name
+ */
+class ContentHubEntityRequestHandler extends ControllerBase {
+
+  /**
+   * The resource plugin manager.
+   *
+   * @var \Drupal\Component\Plugin\PluginManagerInterface
+   */
+  protected $resourcePluginManager;
+
+  /**
+   * Creates a new ContentHubEntityRequestHandler instance.
+   *
+   * @param \Drupal\Component\Plugin\PluginManagerInterface $resource_plugin_manager
+   *   The resource plugin manager.
+   * @param \Drupal\Core\Render\RendererInterface
+   *   The renderer.
+   * @param \Symfony\Component\Serializer\SerializerInterface
+   *   The serializer.
+   */
+  public function __construct(PluginManagerInterface $resource_plugin_manager, RendererInterface $renderer, SerializerInterface $serializer) {
+    $this->resourcePluginManager = $resource_plugin_manager;
+    $this->renderer = $renderer;
+    $this->serializer = $serializer;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container) {
+    return new static(
+      $container->get('plugin.manager.rest'),
+      $container->get('renderer'),
+      $container->get('serializer')
+    );
+  }
+
+  /**
+   * Responds to Content Hub entity GET requests.
+   *
+   * @param \Drupal\Core\Entity\EntityInterface $entity
+   *   The entity object.
+   *
+   * @return \Drupal\rest\ResourceResponse
+   *   The response containing the entity with its accessible fields.
+   *
+   * @throws \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException
+   * @see \Drupal\rest\Plugin\rest\resource\EntityResource::get
+   */
+  public function get(Request $request, EntityInterface $entity) {
+    $entity_access = $entity->access('view', NULL, TRUE);
+    if (!$entity_access->isAllowed()) {
+      throw new AccessDeniedHttpException();
+    }
+
+    $response = new ResourceResponse($entity, 200);
+    $response->addCacheableDependency($entity);
+    $response->addCacheableDependency($entity_access);
+
+    if ($entity instanceof FieldableEntityInterface) {
+      foreach ($entity as $field_name => $field) {
+        /** @var \Drupal\Core\Field\FieldItemListInterface $field */
+        $field_access = $field->access('view', NULL, TRUE);
+        $response->addCacheableDependency($field_access);
+
+        if (!$field_access->isAllowed()) {
+          $entity->set($field_name, NULL);
+        }
+      }
+    }
+
+    // @todo move format to class constant.
+    return $this->serializeResponse($request, $response, 'acquia_contenthub_cdf');
+  }
+
+  /**
+   * Renders a response.
+   *
+   * @param \Symfony\Component\HttpFoundation\Request $request
+   *   The request object.
+   * @param \Drupal\rest\ResourceResponseInterface $response
+   *   A REST resource response.
+   * @param string $format
+   *   The response format
+   *
+   * @return \Drupal\rest\ResourceResponse
+   *   The altered response.
+   *
+   * @see \Drupal\rest\RequestHandler::renderResponse()
+   */
+  protected function serializeResponse(Request $request, ResourceResponseInterface $response, $format) {
+    $data = $response->getResponseData();
+
+    // If there is data to send, serialize and set it as the response body.
+    if ($data !== NULL) {
+      if ($response instanceof CacheableResponseInterface) {
+        $context = new RenderContext();
+        $output = $this->renderer->executeInRenderContext($context, function () use ($data, $format) {
+          return $this->serializer->serialize($data, $format);
+        });
+
+        if (!$context->isEmpty()) {
+          $response->addCacheableDependency($context->pop());
+        }
+      }
+      else {
+        $output = $this->serializer->serialize($data, $format);
+      }
+
+      $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..5a8df7f 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,53 @@ 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')
+        ? str_replace('{' . $entity_type_id . '}', '{entity}', $entity_type->getLinkTemplate('canonical'))
+        : '/entity/' . $entity_type_id . '/{entity}';
+
+      $route = new Route($canonical_path, [
+        '_controller' => '\Drupal\acquia_contenthub\Controller\ContentHubEntityRequestHandler::get',
+      ]);
+      $route->setOption('parameters', [
+        'entity' => [
+          '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);
     }
   }
 
