@Override
  public ThreadPoolRequestReplicator getObject() throws Exception {
    if (replicator == null && nifiProperties.isNode()) {
      final EventReporter eventReporter =
          applicationContext.getBean("eventReporter", EventReporter.class);
      final ClusterCoordinator clusterCoordinator =
          applicationContext.getBean("clusterCoordinator", ClusterCoordinator.class);
      final RequestCompletionCallback requestCompletionCallback =
          applicationContext.getBean("clusterCoordinator", RequestCompletionCallback.class);

      final int numThreads = nifiProperties.getClusterNodeProtocolThreads();
      final Client jerseyClient =
          WebUtils.createClient(
              new DefaultClientConfig(), SslContextFactory.createSslContext(nifiProperties));
      final String connectionTimeout = nifiProperties.getClusterNodeConnectionTimeout();
      final String readTimeout = nifiProperties.getClusterNodeReadTimeout();

      replicator =
          new ThreadPoolRequestReplicator(
              numThreads,
              jerseyClient,
              clusterCoordinator,
              connectionTimeout,
              readTimeout,
              requestCompletionCallback,
              eventReporter,
              nifiProperties);
    }

    return replicator;
  }
 private static SSLContext createTrustContext(final NiFiProperties props) throws Exception {
   return SslContextFactory.createTrustSslContext(
       props.getProperty(NiFiProperties.SECURITY_TRUSTSTORE),
       props.getProperty(NiFiProperties.SECURITY_TRUSTSTORE_PASSWD).toCharArray(),
       props.getProperty(NiFiProperties.SECURITY_TRUSTSTORE_TYPE),
       "TLS");
 }
 private SiteToSiteResource getSiteToSiteResource(final NiFiServiceFacade serviceFacade) {
   final SiteToSiteResource resource =
       new SiteToSiteResource(NiFiProperties.createBasicNiFiProperties(null, null)) {
         @Override
         protected void authorizeSiteToSite() {}
       };
   resource.setProperties(NiFiProperties.createBasicNiFiProperties(null, null));
   resource.setServiceFacade(serviceFacade);
   return resource;
 }
Beispiel #4
0
  /** Seed any users from the authority provider that are not already present. */
  public void seedUserAccounts() {
    // do not seed node's user cache. when/if the node disconnects its
    // cache will be populated lazily (as needed)
    if (properties.isNode()) {
      return;
    }

    Transaction transaction = null;
    writeLock.lock();
    try {
      // start the transaction
      transaction = transactionBuilder.start();

      // seed the accounts
      SeedUserAccountsAction seedUserAccounts = new SeedUserAccountsAction();
      transaction.execute(seedUserAccounts);

      // commit the transaction
      transaction.commit();
    } catch (AdministrationException ae) {
      rollback(transaction);
      throw ae;
    } catch (Throwable t) {
      rollback(transaction);
      throw t;
    } finally {
      closeQuietly(transaction);
      writeLock.unlock();
    }
  }
Beispiel #5
0
 /**
  * Main entry point of the application.
  *
  * @param args things which are ignored
  */
 public static void main(String[] args) {
   logger.info("Launching NiFi...");
   try {
     new NiFi(NiFiProperties.getInstance());
   } catch (final Throwable t) {
     logger.error("Failure to launch NiFi due to " + t, t);
   }
 }
  /**
   * Retrieves the specified input port.
   *
   * @param clientId Optional client id. If the client id is not specified, a new one will be
   *     generated. This value (whether specified or generated) is included in the response.
   * @param id The id of the input port to retrieve
   * @return A inputPortEntity.
   */
  @GET
  @Consumes(MediaType.WILDCARD)
  @Produces(MediaType.APPLICATION_JSON)
  @Path("{id}")
  // TODO - @PreAuthorize("hasAnyRole('ROLE_MONITOR', 'ROLE_DFM', 'ROLE_ADMIN')")
  @ApiOperation(
      value = "Gets an input port",
      response = InputPortEntity.class,
      authorizations = {
        @Authorization(value = "Read Only", type = "ROLE_MONITOR"),
        @Authorization(value = "Data Flow Manager", type = "ROLE_DFM"),
        @Authorization(value = "Administrator", type = "ROLE_ADMIN")
      })
  @ApiResponses(
      value = {
        @ApiResponse(
            code = 400,
            message =
                "NiFi was unable to complete the request because it was invalid. The request should not be retried without modification."),
        @ApiResponse(code = 401, message = "Client could not be authenticated."),
        @ApiResponse(code = 403, message = "Client is not authorized to make this request."),
        @ApiResponse(code = 404, message = "The specified resource could not be found."),
        @ApiResponse(
            code = 409,
            message =
                "The request was valid but NiFi was not in the appropriate state to process it. Retrying the same request later may be successful.")
      })
  public Response getInputPort(
      @ApiParam(
              value =
                  "If the client id is not specified, new one will be generated. This value (whether specified or generated) is included in the response.",
              required = false)
          @QueryParam(CLIENT_ID)
          @DefaultValue(StringUtils.EMPTY)
          ClientIdParameter clientId,
      @ApiParam(value = "The input port id.", required = true) @PathParam("id") String id) {

    // replicate if cluster manager
    if (properties.isClusterManager()) {
      return clusterManager
          .applyRequest(HttpMethod.GET, getAbsolutePath(), getRequestParameters(true), getHeaders())
          .getResponse();
    }

    // get the port
    final PortDTO port = serviceFacade.getInputPort(id);

    // create the revision
    final RevisionDTO revision = new RevisionDTO();
    revision.setClientId(clientId.getClientId());

    // create the response entity
    final InputPortEntity entity = new InputPortEntity();
    entity.setRevision(revision);
    entity.setInputPort(populateRemainingInputPortContent(port));

    return clusterContext(generateOkResponse(entity)).build();
  }
 private String getDatabaseUrl(File databaseFile) {
   String databaseUrl =
       "jdbc:h2:" + databaseFile + ";AUTOCOMMIT=OFF;DB_CLOSE_ON_EXIT=FALSE;LOCK_MODE=3";
   String databaseUrlAppend = properties.getProperty(NiFiProperties.H2_URL_APPEND);
   if (StringUtils.isNotBlank(databaseUrlAppend)) {
     databaseUrl += databaseUrlAppend;
   }
   return databaseUrl;
 }
  @BeforeClass
  public static void setup() throws Exception {
    // configure the location of the nifi properties
    File nifiPropertiesFile = new File("src/test/resources/access-control/nifi.properties");
    System.setProperty(NiFiProperties.PROPERTIES_FILE_PATH, nifiPropertiesFile.getAbsolutePath());

    NiFiProperties props = NiFiProperties.createBasicNiFiProperties(null, null);
    flowXmlPath = props.getProperty(NiFiProperties.FLOW_CONFIGURATION_FILE);

    // delete the database directory to avoid issues with re-registration in
    // testRequestAccessUsingToken
    FileUtils.deleteDirectory(props.getDatabaseRepositoryPath().toFile());

    // load extensions
    NarClassLoaders.getInstance()
        .init(props.getFrameworkWorkingDirectory(), props.getExtensionsWorkingDirectory());
    ExtensionManager.discoverExtensions(NarClassLoaders.getInstance().getExtensionClassLoaders());

    // start the server
    SERVER = new NiFiTestServer("src/main/webapp", CONTEXT_PATH, props);
    SERVER.startServer();
    SERVER.loadFlow();

    // get the base url
    BASE_URL = SERVER.getBaseUrl() + CONTEXT_PATH;

    // create the user
    final Client client = WebUtils.createClient(null, createTrustContext(props));
    TOKEN_USER = new NiFiTestUser(client, null);
  }
Beispiel #9
0
  /**
   * Checks whether or not the request should be replicated to the cluster
   *
   * @return <code>true</code> if the request should be replicated, <code>false</code> otherwise
   */
  boolean isReplicateRequest() {
    // If not a node in a cluster, we do not replicate
    if (!properties.isNode()) {
      return false;
    }

    if (!isConnectedToCluster()) {
      return false;
    }

    // Check if the X-Request-Replicated header is set. If so, the request has already been
    // replicated,
    // so we need to service the request locally. If not, then replicate the request to the entire
    // cluster.
    final String header =
        httpServletRequest.getHeader(RequestReplicator.REPLICATION_INDICATOR_HEADER);
    return header == null;
  }
Beispiel #10
0
  @Override
  public NiFiUser checkAuthorization(String dn) {
    Transaction transaction = null;

    writeLock.lock();
    try {
      // create the connection
      transaction = transactionBuilder.start();

      // determine how long the cache is valid for
      final int cacheSeconds;
      try {
        cacheSeconds =
            (int)
                FormatUtils.getTimeDuration(
                    properties.getUserCredentialCacheDuration(), TimeUnit.SECONDS);
      } catch (IllegalArgumentException iae) {
        throw new AdministrationException(
            "User credential cache duration is not configured correctly.");
      }

      // attempt to authorize the user
      AuthorizeUserAction authorizeUser = new AuthorizeUserAction(dn, cacheSeconds);
      NiFiUser user = transaction.execute(authorizeUser);

      // commit the transaction
      transaction.commit();

      // return the nifi user
      return user;
    } catch (DataAccessException | TransactionException dae) {
      rollback(transaction);
      throw new AdministrationException(dae);
    } catch (AccountDisabledException | AccountPendingException ade) {
      rollback(transaction);
      throw ade;
    } catch (Throwable t) {
      rollback(transaction);
      throw t;
    } finally {
      closeQuietly(transaction);
      writeLock.unlock();
    }
  }
  public TimerDrivenSchedulingAgent(
      final FlowController flowController,
      final FlowEngine flowEngine,
      final ProcessContextFactory contextFactory,
      final StringEncryptor encryptor) {
    this.flowController = flowController;
    this.flowEngine = flowEngine;
    this.contextFactory = contextFactory;
    this.encryptor = encryptor;

    final String boredYieldDuration = NiFiProperties.getInstance().getBoredYieldDuration();
    try {
      noWorkYieldNanos = FormatUtils.getTimeDuration(boredYieldDuration, TimeUnit.NANOSECONDS);
    } catch (final IllegalArgumentException e) {
      throw new RuntimeException(
          "Failed to create SchedulingAgent because the "
              + NiFiProperties.BORED_YIELD_DURATION
              + " property is set to an invalid time duration: "
              + boredYieldDuration);
    }
  }
Beispiel #12
0
  public NiFi(final NiFiProperties properties)
      throws ClassNotFoundException, IOException, NoSuchMethodException, InstantiationException,
          IllegalAccessException, IllegalArgumentException, InvocationTargetException {
    Thread.setDefaultUncaughtExceptionHandler(
        new UncaughtExceptionHandler() {
          @Override
          public void uncaughtException(final Thread t, final Throwable e) {
            logger.error("An Unknown Error Occurred in Thread {}: {}", t, e.toString());
            logger.error("", e);
          }
        });

    // register the shutdown hook
    Runtime.getRuntime()
        .addShutdownHook(
            new Thread(
                new Runnable() {
                  @Override
                  public void run() {
                    // shutdown the jetty server
                    shutdownHook();
                  }
                }));

    final String bootstrapPort = System.getProperty(BOOTSTRAP_PORT_PROPERTY);
    if (bootstrapPort != null) {
      try {
        final int port = Integer.parseInt(bootstrapPort);

        if (port < 1 || port > 65535) {
          throw new RuntimeException(
              "Failed to start NiFi because system property '"
                  + BOOTSTRAP_PORT_PROPERTY
                  + "' is not a valid integer in the range 1 - 65535");
        }

        bootstrapListener = new BootstrapListener(this, port);
        bootstrapListener.start();
      } catch (final NumberFormatException nfe) {
        throw new RuntimeException(
            "Failed to start NiFi because system property '"
                + BOOTSTRAP_PORT_PROPERTY
                + "' is not a valid integer in the range 1 - 65535");
      }
    } else {
      logger.info(
          "NiFi started without Bootstrap Port information provided; will not listen for requests from Bootstrap");
      bootstrapListener = null;
    }

    // delete the web working dir - if the application does not start successfully
    // the web app directories might be in an invalid state. when this happens
    // jetty will not attempt to re-extract the war into the directory. by removing
    // the working directory, we can be assured that it will attempt to extract the
    // war every time the application starts.
    File webWorkingDir = properties.getWebWorkingDirectory();
    FileUtils.deleteFilesInDirectory(webWorkingDir, null, logger, true, true);
    FileUtils.deleteFile(webWorkingDir, logger, 3);

    detectTimingIssues();

    // redirect JUL log events
    SLF4JBridgeHandler.removeHandlersForRootLogger();
    SLF4JBridgeHandler.install();

    // expand the nars
    final ExtensionMapping extensionMapping = NarUnpacker.unpackNars(properties);

    // load the extensions classloaders
    NarClassLoaders.getInstance()
        .init(
            properties.getFrameworkWorkingDirectory(), properties.getExtensionsWorkingDirectory());

    // load the framework classloader
    final ClassLoader frameworkClassLoader =
        NarClassLoaders.getInstance().getFrameworkClassLoader();
    if (frameworkClassLoader == null) {
      throw new IllegalStateException("Unable to find the framework NAR ClassLoader.");
    }

    // discover the extensions
    ExtensionManager.discoverExtensions(NarClassLoaders.getInstance().getExtensionClassLoaders());
    ExtensionManager.logClassLoaderMapping();

    DocGenerator.generate(properties);

    // load the server from the framework classloader
    Thread.currentThread().setContextClassLoader(frameworkClassLoader);
    Class<?> jettyServer =
        Class.forName("org.apache.nifi.web.server.JettyServer", true, frameworkClassLoader);
    Constructor<?> jettyConstructor = jettyServer.getConstructor(NiFiProperties.class);

    final long startTime = System.nanoTime();
    nifiServer = (NiFiServer) jettyConstructor.newInstance(properties);
    nifiServer.setExtensionMapping(extensionMapping);

    if (shutdown) {
      logger.info("NiFi has been shutdown via NiFi Bootstrap. Will not start Controller");
    } else {
      nifiServer.start();

      if (bootstrapListener != null) {
        bootstrapListener.sendStartedStatus(true);
      }

      final long endTime = System.nanoTime();
      logger.info("Controller initialization took " + (endTime - startTime) + " nanoseconds.");
    }
  }
  /**
   * Updates the specified input port.
   *
   * @param httpServletRequest request
   * @param id The id of the input port to update.
   * @param portEntity A inputPortEntity.
   * @return A inputPortEntity.
   */
  @PUT
  @Consumes(MediaType.APPLICATION_JSON)
  @Produces(MediaType.APPLICATION_JSON)
  @Path("{id}")
  // TODO - @PreAuthorize("hasRole('ROLE_DFM')")
  @ApiOperation(
      value = "Updates an input port",
      response = InputPortEntity.class,
      authorizations = {@Authorization(value = "Data Flow Manager", type = "ROLE_DFM")})
  @ApiResponses(
      value = {
        @ApiResponse(
            code = 400,
            message =
                "NiFi was unable to complete the request because it was invalid. The request should not be retried without modification."),
        @ApiResponse(code = 401, message = "Client could not be authenticated."),
        @ApiResponse(code = 403, message = "Client is not authorized to make this request."),
        @ApiResponse(code = 404, message = "The specified resource could not be found."),
        @ApiResponse(
            code = 409,
            message =
                "The request was valid but NiFi was not in the appropriate state to process it. Retrying the same request later may be successful.")
      })
  public Response updateInputPort(
      @Context HttpServletRequest httpServletRequest,
      @ApiParam(value = "The input port id.", required = true) @PathParam("id") String id,
      @ApiParam(value = "The input port configuration details.", required = true)
          InputPortEntity portEntity) {

    if (portEntity == null || portEntity.getInputPort() == null) {
      throw new IllegalArgumentException("Input port details must be specified.");
    }

    if (portEntity.getRevision() == null) {
      throw new IllegalArgumentException("Revision must be specified.");
    }

    // ensure the ids are the same
    final PortDTO requestPortDTO = portEntity.getInputPort();
    if (!id.equals(requestPortDTO.getId())) {
      throw new IllegalArgumentException(
          String.format(
              "The input port id (%s) in the request body does not equal the "
                  + "input port id of the requested resource (%s).",
              requestPortDTO.getId(), id));
    }

    // replicate if cluster manager
    if (properties.isClusterManager()) {
      // change content type to JSON for serializing entity
      final Map<String, String> headersToOverride = new HashMap<>();
      headersToOverride.put("content-type", MediaType.APPLICATION_JSON);

      // replicate the request
      return clusterManager
          .applyRequest(
              HttpMethod.PUT,
              getAbsolutePath(),
              updateClientId(portEntity),
              getHeaders(headersToOverride))
          .getResponse();
    }

    // handle expects request (usually from the cluster manager)
    final String expects = httpServletRequest.getHeader(WebClusterManager.NCM_EXPECTS_HTTP_HEADER);
    if (expects != null) {
      serviceFacade.verifyUpdateInputPort(requestPortDTO);
      return generateContinueResponse().build();
    }

    // update the input port
    final RevisionDTO revision = portEntity.getRevision();
    final ConfigurationSnapshot<PortDTO> controllerResponse =
        serviceFacade.updateInputPort(
            new Revision(revision.getVersion(), revision.getClientId()), requestPortDTO);

    // get the results
    final PortDTO responsePortDTO = controllerResponse.getConfiguration();
    populateRemainingInputPortContent(responsePortDTO);

    // get the updated revision
    final RevisionDTO updatedRevision = new RevisionDTO();
    updatedRevision.setClientId(revision.getClientId());
    updatedRevision.setVersion(controllerResponse.getVersion());

    // build the response entity
    final InputPortEntity entity = new InputPortEntity();
    entity.setRevision(updatedRevision);
    entity.setInputPort(responsePortDTO);

    if (controllerResponse.isNew()) {
      return clusterContext(generateCreatedResponse(URI.create(responsePortDTO.getUri()), entity))
          .build();
    } else {
      return clusterContext(generateOkResponse(entity)).build();
    }
  }
  @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.");
    }
  }
  @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);
    }
  }
  @Override
  public Object getObject() throws Exception {
    if (connectionPool == null) {

      // locate the repository directory
      String repositoryDirectoryPath =
          properties.getProperty(NiFiProperties.REPOSITORY_DATABASE_DIRECTORY);

      // ensure the repository directory is specified
      if (repositoryDirectoryPath == null) {
        throw new NullPointerException("Database directory must be specified.");
      }

      // create a handle to the repository directory
      File repositoryDirectory = new File(repositoryDirectoryPath);

      // create a handle to the database directory and file
      File databaseFile = new File(repositoryDirectory, AUDIT_DATABASE_FILE_NAME);
      String databaseUrl = getDatabaseUrl(databaseFile);

      // create the pool
      connectionPool =
          JdbcConnectionPool.create(databaseUrl, NF_USERNAME_PASSWORD, NF_USERNAME_PASSWORD);
      connectionPool.setMaxConnections(MAX_CONNECTIONS);

      Connection connection = null;
      ResultSet rs = null;
      Statement statement = null;
      try {
        // get a connection
        connection = connectionPool.getConnection();
        connection.setAutoCommit(false);

        // create a statement for creating/updating the database
        statement = connection.createStatement();

        // determine if the tables need to be created
        rs = connection.getMetaData().getTables(null, null, "USER", null);
        if (!rs.next()) {
          logger.info("Database not built for repository: " + databaseUrl + ".  Building now...");

          // create the tables
          statement.execute(CREATE_USER_TABLE);
          statement.execute(CREATE_AUTHORITY_TABLE);

          // seed the anonymous user
          statement.execute(INSERT_ANONYMOUS_USER);
          statement.execute(INSERT_ANONYMOUS_MONITOR_AUTHORITY);
          statement.execute(INSERT_ANONYMOUS_DFM_AUTHORITY);
          statement.execute(INSERT_ANONYMOUS_ADMIN_AUTHORITY);
          statement.execute(INSERT_ANONYMOUS_NIFI_AUTHORITY);
        } else {
          logger.info("Existing database found and connected to at: " + databaseUrl);
        }

        // close the previous result set
        RepositoryUtils.closeQuietly(rs);

        // merge in the provenance role to handle existing databases
        rs = statement.executeQuery(SELECT_ANONYMOUS_PROVENANCE_AUTHORITY);
        if (!rs.next()) {
          statement.execute(INSERT_ANONYMOUS_PROVENANCE_AUTHORITY);
        }

        // commit any changes
        connection.commit();
      } catch (SQLException sqle) {
        RepositoryUtils.rollback(connection, logger);
        throw sqle;
      } finally {
        RepositoryUtils.closeQuietly(rs);
        RepositoryUtils.closeQuietly(statement);
        RepositoryUtils.closeQuietly(connection);
      }
    }

    return connectionPool;
  }
  /**
   * Removes the specified input port.
   *
   * @param httpServletRequest request
   * @param version The revision is used to verify the client is working with the latest version of
   *     the flow.
   * @param clientId Optional client id. If the client id is not specified, a new one will be
   *     generated. This value (whether specified or generated) is included in the response.
   * @param id The id of the input port to remove.
   * @return A inputPortEntity.
   */
  @DELETE
  @Consumes(MediaType.WILDCARD)
  @Produces(MediaType.APPLICATION_JSON)
  @Path("{id}")
  // TODO - @PreAuthorize("hasRole('ROLE_DFM')")
  @ApiOperation(
      value = "Deletes an input port",
      response = InputPortEntity.class,
      authorizations = {@Authorization(value = "Data Flow Manager", type = "ROLE_DFM")})
  @ApiResponses(
      value = {
        @ApiResponse(
            code = 400,
            message =
                "NiFi was unable to complete the request because it was invalid. The request should not be retried without modification."),
        @ApiResponse(code = 401, message = "Client could not be authenticated."),
        @ApiResponse(code = 403, message = "Client is not authorized to make this request."),
        @ApiResponse(code = 404, message = "The specified resource could not be found."),
        @ApiResponse(
            code = 409,
            message =
                "The request was valid but NiFi was not in the appropriate state to process it. Retrying the same request later may be successful.")
      })
  public Response removeInputPort(
      @Context HttpServletRequest httpServletRequest,
      @ApiParam(
              value =
                  "The revision is used to verify the client is working with the latest version of the flow.",
              required = false)
          @QueryParam(VERSION)
          LongParameter version,
      @ApiParam(
              value =
                  "If the client id is not specified, new one will be generated. This value (whether specified or generated) is included in the response.",
              required = false)
          @QueryParam(CLIENT_ID)
          @DefaultValue(StringUtils.EMPTY)
          ClientIdParameter clientId,
      @ApiParam(value = "The input port id.", required = true) @PathParam("id") String id) {

    // replicate if cluster manager
    if (properties.isClusterManager()) {
      return clusterManager
          .applyRequest(
              HttpMethod.DELETE, getAbsolutePath(), getRequestParameters(true), getHeaders())
          .getResponse();
    }

    // handle expects request (usually from the cluster manager)
    final String expects = httpServletRequest.getHeader(WebClusterManager.NCM_EXPECTS_HTTP_HEADER);
    if (expects != null) {
      serviceFacade.verifyDeleteInputPort(id);
      return generateContinueResponse().build();
    }

    // determine the specified version
    Long clientVersion = null;
    if (version != null) {
      clientVersion = version.getLong();
    }

    // delete the specified input port
    final ConfigurationSnapshot<Void> controllerResponse =
        serviceFacade.deleteInputPort(new Revision(clientVersion, clientId.getClientId()), id);

    // get the updated revision
    final RevisionDTO revision = new RevisionDTO();
    revision.setClientId(clientId.getClientId());
    revision.setVersion(controllerResponse.getVersion());

    // build the response entity
    final InputPortEntity entity = new InputPortEntity();
    entity.setRevision(revision);

    return clusterContext(generateOkResponse(entity)).build();
  }