@Override
  public DownloadableContent getContent(final ContentRequestContext request) {
    // if clustered, send request to cluster manager
    if (properties.isClustered()
        && clusterCoordinator != null
        && clusterCoordinator.isConnected()) {
      // get the URI
      URI dataUri;
      try {
        dataUri = new URI(request.getDataUri());
      } catch (final URISyntaxException use) {
        throw new ClusterRequestException(use);
      }

      // set the request parameters
      final MultivaluedMap<String, String> parameters = new MultivaluedMapImpl();
      parameters.add(CLIENT_ID_PARAM, request.getClientId());

      // set the headers
      final Map<String, String> headers = new HashMap<>();
      if (StringUtils.isNotBlank(request.getProxiedEntitiesChain())) {
        headers.put("X-ProxiedEntitiesChain", request.getProxiedEntitiesChain());
      }

      // add the user's authorities (if any) to the headers
      final Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
      if (authentication != null) {
        final Object userDetailsObj = authentication.getPrincipal();
        if (userDetailsObj instanceof NiFiUserDetails) {
          // serialize user details object
          final String hexEncodedUserDetails =
              WebUtils.serializeObjectToHex((Serializable) userDetailsObj);

          // put serialized user details in header
          headers.put("X-ProxiedEntityUserDetails", hexEncodedUserDetails);
        }
      }

      // ensure we were able to detect the cluster node id
      if (request.getClusterNodeId() == null) {
        throw new IllegalArgumentException("Unable to determine the which node has the content.");
      }

      // get the target node and ensure it exists
      final NodeIdentifier nodeId =
          clusterCoordinator.getNodeIdentifier(request.getClusterNodeId());
      final Set<NodeIdentifier> targetNodes = Collections.singleton(nodeId);

      // replicate the request to the specific node
      NodeResponse nodeResponse;
      try {
        nodeResponse =
            requestReplicator
                .replicate(targetNodes, HttpMethod.GET, dataUri, parameters, headers)
                .awaitMergedResponse();
      } catch (InterruptedException e) {
        throw new IllegalClusterStateException(
            "Interrupted while waiting for a response from node");
      }

      final ClientResponse clientResponse = nodeResponse.getClientResponse();
      final MultivaluedMap<String, String> responseHeaders = clientResponse.getHeaders();

      // ensure an appropriate response
      if (Status.NOT_FOUND.getStatusCode() == clientResponse.getStatusInfo().getStatusCode()) {
        throw new ResourceNotFoundException(clientResponse.getEntity(String.class));
      } else if (Status.FORBIDDEN.getStatusCode() == clientResponse.getStatusInfo().getStatusCode()
          || Status.UNAUTHORIZED.getStatusCode()
              == clientResponse.getStatusInfo().getStatusCode()) {
        throw new AccessDeniedException(clientResponse.getEntity(String.class));
      } else if (Status.OK.getStatusCode() != clientResponse.getStatusInfo().getStatusCode()) {
        throw new IllegalStateException(clientResponse.getEntity(String.class));
      }

      // get the file name
      final String contentDisposition = responseHeaders.getFirst("Content-Disposition");
      final String filename = StringUtils.substringBetween(contentDisposition, "filename=\"", "\"");

      // get the content type
      final String contentType = responseHeaders.getFirst("Content-Type");

      // create the downloadable content
      return new DownloadableContent(filename, contentType, clientResponse.getEntityInputStream());
    } else {
      // example URIs:
      // http://localhost:8080/nifi-api/provenance/events/{id}/content/{input|output}
      // http://localhost:8080/nifi-api/flowfile-queues/{uuid}/flowfiles/{uuid}/content

      // get just the context path for comparison
      final String dataUri = StringUtils.substringAfter(request.getDataUri(), "/nifi-api");
      if (StringUtils.isBlank(dataUri)) {
        throw new IllegalArgumentException("The specified data reference URI is not valid.");
      }

      // flowfile listing content
      final Matcher flowFileMatcher = FLOWFILE_CONTENT_URI_PATTERN.matcher(dataUri);
      if (flowFileMatcher.matches()) {
        final String connectionId = flowFileMatcher.group(1);
        final String flowfileId = flowFileMatcher.group(2);

        return getFlowFileContent(connectionId, flowfileId, dataUri);
      }

      // provenance event content
      final Matcher provenanceMatcher = PROVENANCE_CONTENT_URI_PATTERN.matcher(dataUri);
      if (provenanceMatcher.matches()) {
        try {
          final Long eventId = Long.parseLong(provenanceMatcher.group(1));
          final ContentDirection direction =
              ContentDirection.valueOf(provenanceMatcher.group(2).toUpperCase());

          return getProvenanceEventContent(eventId, dataUri, direction);
        } catch (final IllegalArgumentException iae) {
          throw new IllegalArgumentException("The specified data reference URI is not valid.");
        }
      }

      // invalid uri
      throw new IllegalArgumentException("The specified data reference URI is not valid.");
    }
  }
  /**
   * Gets the system diagnostics for this NiFi instance.
   *
   * @return A systemDiagnosticsEntity.
   * @throws InterruptedException if interrupted
   */
  @GET
  @Consumes(MediaType.WILDCARD)
  @Produces(MediaType.APPLICATION_JSON)
  @ApiOperation(
      value = "Gets the diagnostics for the system NiFi is running on",
      response = SystemDiagnosticsEntity.class,
      authorizations = {@Authorization(value = "Read - /system", type = "")})
  @ApiResponses(
      value = {
        @ApiResponse(code = 401, message = "Client could not be authenticated."),
        @ApiResponse(code = 403, message = "Client is not authorized to make this request."),
      })
  public Response getSystemDiagnostics(
      @ApiParam(
              value =
                  "Whether or not to include the breakdown per node. Optional, defaults to false",
              required = false)
          @QueryParam("nodewise")
          @DefaultValue(NODEWISE)
          final Boolean nodewise,
      @ApiParam(value = "The id of the node where to get the status.", required = false)
          @QueryParam("clusterNodeId")
          final String clusterNodeId)
      throws InterruptedException {

    authorizeSystem();

    // ensure a valid request
    if (Boolean.TRUE.equals(nodewise) && clusterNodeId != null) {
      throw new IllegalArgumentException(
          "Nodewise requests cannot be directed at a specific node.");
    }

    if (isReplicateRequest()) {
      // determine where this request should be sent
      if (clusterNodeId == null) {
        final NodeResponse nodeResponse;

        // Determine whether we should replicate only to the cluster coordinator, or if we should
        // replicate directly
        // to the cluster nodes themselves.
        if (getReplicationTarget() == ReplicationTarget.CLUSTER_NODES) {
          nodeResponse =
              getRequestReplicator()
                  .replicate(
                      HttpMethod.GET, getAbsolutePath(), getRequestParameters(), getHeaders())
                  .awaitMergedResponse();
        } else {
          nodeResponse =
              getRequestReplicator()
                  .forwardToCoordinator(
                      getClusterCoordinatorNode(),
                      HttpMethod.GET,
                      getAbsolutePath(),
                      getRequestParameters(),
                      getHeaders())
                  .awaitMergedResponse();
        }

        final SystemDiagnosticsEntity entity =
            (SystemDiagnosticsEntity) nodeResponse.getUpdatedEntity();

        // ensure there is an updated entity (result of merging) and prune the response as necessary
        if (entity != null && !nodewise) {
          entity.getSystemDiagnostics().setNodeSnapshots(null);
        }

        return nodeResponse.getResponse();
      } else {
        return replicate(HttpMethod.GET);
      }
    }

    final SystemDiagnosticsDTO systemDiagnosticsDto = serviceFacade.getSystemDiagnostics();

    // create the response
    final SystemDiagnosticsEntity entity = new SystemDiagnosticsEntity();
    entity.setSystemDiagnostics(systemDiagnosticsDto);

    // generate the response
    return clusterContext(generateOkResponse(entity)).build();
  }
  @Override
  @PreAuthorize("hasRole('ROLE_PROVENANCE')")
  public DownloadableContent getContent(final ContentRequestContext request) {
    // if clustered, send request to cluster manager
    if (properties.isClusterManager()) {
      // get the URI
      URI dataUri;
      try {
        dataUri = new URI(request.getDataUri());
      } catch (final URISyntaxException use) {
        throw new ClusterRequestException(use);
      }

      // set the request parameters
      final MultivaluedMap<String, String> parameters = new MultivaluedMapImpl();
      parameters.add(CLIENT_ID_PARAM, request.getClientId());

      // set the headers
      final Map<String, String> headers = new HashMap<>();
      if (StringUtils.isNotBlank(request.getProxiedEntitiesChain())) {
        headers.put("X-ProxiedEntitiesChain", request.getProxiedEntitiesChain());
      }

      // add the user's authorities (if any) to the headers
      final Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
      if (authentication != null) {
        final Object userDetailsObj = authentication.getPrincipal();
        if (userDetailsObj instanceof NiFiUserDetails) {
          // serialize user details object
          final String hexEncodedUserDetails =
              WebUtils.serializeObjectToHex((Serializable) userDetailsObj);

          // put serialized user details in header
          headers.put("X-ProxiedEntityUserDetails", hexEncodedUserDetails);
        }
      }

      // get the target node and ensure it exists
      final Node targetNode = clusterManager.getNode(request.getClusterNodeId());
      if (targetNode == null) {
        throw new UnknownNodeException("The specified cluster node does not exist.");
      }

      final Set<NodeIdentifier> targetNodes = new HashSet<>();
      targetNodes.add(targetNode.getNodeId());

      // replicate the request to the specific node
      final NodeResponse nodeResponse =
          clusterManager.applyRequest(HttpMethod.GET, dataUri, parameters, headers, targetNodes);
      final ClientResponse clientResponse = nodeResponse.getClientResponse();
      final MultivaluedMap<String, String> responseHeaders = clientResponse.getHeaders();

      // get the file name
      final String contentDisposition = responseHeaders.getFirst("Content-Disposition");
      final String filename = StringUtils.substringBetween(contentDisposition, "filename=\"", "\"");

      // get the content type
      final String contentType = responseHeaders.getFirst("Content-Type");

      // create the downloadable content
      return new DownloadableContent(filename, contentType, clientResponse.getEntityInputStream());
    } else {
      // example URI: http://localhost:8080/nifi-api/controller/provenance/events/1/content/input
      final String eventDetails = StringUtils.substringAfterLast(request.getDataUri(), "events/");
      final String rawEventId = StringUtils.substringBefore(eventDetails, "/content/");
      final String rawDirection = StringUtils.substringAfterLast(eventDetails, "/content/");

      // get the content type
      final Long eventId;
      final ContentDirection direction;
      try {
        eventId = Long.parseLong(rawEventId);
        direction = ContentDirection.valueOf(rawDirection.toUpperCase());
      } catch (final IllegalArgumentException iae) {
        throw new IllegalArgumentException("The specified data reference URI is not valid.");
      }
      return serviceFacade.getContent(eventId, request.getDataUri(), direction);
    }
  }