diff --git a/.gitignore b/.gitignore
index f026df6c81..6d4eca990c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,6 +1,7 @@
target
.project
.settings
+.classpath
nbactions.xml
.DS_Store
diff --git a/nifi-bootstrap/pom.xml b/nifi-bootstrap/pom.xml
new file mode 100644
index 0000000000..b620c84a78
--- /dev/null
+++ b/nifi-bootstrap/pom.xml
@@ -0,0 +1,18 @@
+
+ 4.0.0
+
+
+ org.apache.nifi
+ nifi-parent
+ 0.0.1-SNAPSHOT
+
+
+ nifi-bootstrap
+ jar
+
+ nifi-bootstrap
+
+
+
+
diff --git a/nifi-bootstrap/src/main/java/org/apache/nifi/bootstrap/RunNiFi.java b/nifi-bootstrap/src/main/java/org/apache/nifi/bootstrap/RunNiFi.java
new file mode 100644
index 0000000000..afa1f4713e
--- /dev/null
+++ b/nifi-bootstrap/src/main/java/org/apache/nifi/bootstrap/RunNiFi.java
@@ -0,0 +1,176 @@
+package org.apache.nifi.bootstrap;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FilenameFilter;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+
+
+/**
+ * Bootstrap class to run Apache NiFi.
+ *
+ * This class looks for the bootstrap.conf file by looking in the following places (in order):
+ *
+ * - First argument to the program
+ * - Java System Property named {@code org.apache.nifi.bootstrap.config.file}
+ * - ${NIFI_HOME}/./conf/bootstrap.conf, where ${NIFI_HOME} references an environment variable {@code NIFI_HOME}
+ * - ./conf/bootstrap.conf, where {@code .} represents the working directory.
+ *
+ *
+ * If the {@code bootstrap.conf} file cannot be found, throws a {@code FileNotFoundException].
+ */
+public class RunNiFi {
+ public static final String DEFAULT_CONFIG_FILE = "./conf/boostrap.conf";
+ public static final String DEFAULT_NIFI_PROPS_FILE = "./conf/nifi.properties";
+
+ @SuppressWarnings({ "rawtypes", "unchecked" })
+ public static void main(final String[] args) throws IOException, InterruptedException {
+ final ProcessBuilder builder = new ProcessBuilder();
+
+ String configFilename = (args.length > 0) ? args[0] : System.getProperty("org.apache.nifi.boostrap.config.file");
+
+ if ( configFilename == null ) {
+ final String nifiHome = System.getenv("NIFI_HOME");
+ if ( nifiHome != null ) {
+ final File nifiHomeFile = new File(nifiHome.trim());
+ final File configFile = new File(nifiHomeFile, DEFAULT_CONFIG_FILE);
+ configFilename = configFile.getAbsolutePath();
+ }
+ }
+
+ if ( configFilename == null ) {
+ configFilename = DEFAULT_CONFIG_FILE;
+ }
+
+ final File configFile = new File(configFilename);
+ if ( !configFile.exists() ) {
+ throw new FileNotFoundException(DEFAULT_CONFIG_FILE);
+ }
+
+ final Properties properties = new Properties();
+ try (final FileInputStream fis = new FileInputStream(configFile)) {
+ properties.load(fis);
+ }
+
+ final Map props = new HashMap<>();
+ props.putAll( (Map) properties );
+
+ final String specifiedWorkingDir = props.get("working.dir");
+ if ( specifiedWorkingDir != null ) {
+ builder.directory(new File(specifiedWorkingDir));
+ }
+
+ final File workingDir = builder.directory();
+
+ final String libFilename = replaceNull(props.get("lib.dir"), "./lib").trim();
+ File libDir = getFile(libFilename, workingDir);
+
+ final String confFilename = replaceNull(props.get("conf.dir"), "./conf").trim();
+ File confDir = getFile(confFilename, workingDir);
+
+ String nifiPropsFilename = props.get("props.file");
+ if ( nifiPropsFilename == null ) {
+ if ( confDir.exists() ) {
+ nifiPropsFilename = new File(confDir, "nifi.properties").getAbsolutePath();
+ } else {
+ nifiPropsFilename = DEFAULT_CONFIG_FILE;
+ }
+ }
+
+ nifiPropsFilename = nifiPropsFilename.trim();
+
+ final List javaAdditionalArgs = new ArrayList<>();
+ for ( final Map.Entry entry : props.entrySet() ) {
+ final String key = entry.getKey();
+ final String value = entry.getValue();
+
+ if ( key.startsWith("java.arg") ) {
+ javaAdditionalArgs.add(value);
+ }
+ }
+
+ final File[] libFiles = libDir.listFiles(new FilenameFilter() {
+ @Override
+ public boolean accept(final File dir, final String filename) {
+ return filename.toLowerCase().endsWith(".jar");
+ }
+ });
+
+ if ( libFiles == null || libFiles.length == 0 ) {
+ throw new RuntimeException("Could not find lib directory at " + libDir.getAbsolutePath());
+ }
+
+ final File[] confFiles = confDir.listFiles();
+ if ( confFiles == null || confFiles.length == 0 ) {
+ throw new RuntimeException("Could not find conf directory at " + confDir.getAbsolutePath());
+ }
+
+ final Path workingDirPath = workingDir.toPath();
+ final List cpFiles = new ArrayList<>(confFiles.length + libFiles.length);
+ cpFiles.add(confDir.getAbsolutePath());
+ for ( final File file : libFiles ) {
+ final Path path = workingDirPath.relativize(file.toPath());
+ final String cpPath = path.toString();
+ cpFiles.add(cpPath);
+ }
+
+ final StringBuilder classPathBuilder = new StringBuilder();
+ for (int i=0; i < cpFiles.size(); i++) {
+ final String filename = cpFiles.get(i);
+ classPathBuilder.append(filename);
+ if ( i < cpFiles.size() - 1 ) {
+ classPathBuilder.append(File.pathSeparatorChar);
+ }
+ }
+
+ final String classPath = classPathBuilder.toString();
+ String javaCmd = props.get("java");
+ if ( javaCmd == null ) {
+ javaCmd = "java";
+ }
+
+ final List cmd = new ArrayList<>();
+ cmd.add(javaCmd);
+ cmd.add("-classpath");
+ cmd.add(classPath);
+ cmd.addAll(javaAdditionalArgs);
+ cmd.add("-Dnifi.properties.file.path=" + nifiPropsFilename);
+ cmd.add("org.apache.nifi.NiFi");
+
+ builder.command(cmd).inheritIO();
+
+ final StringBuilder cmdBuilder = new StringBuilder();
+ for ( final String s : cmd ) {
+ cmdBuilder.append(s).append(" ");
+ }
+ System.out.println("Starting Apache NiFi...");
+ System.out.println("Working Directory: " + workingDir.getAbsolutePath());
+ System.out.println("Command: " + cmdBuilder.toString());
+
+ final Process proc = builder.start();
+ Runtime.getRuntime().addShutdownHook(new ShutdownHook(proc));
+ final int statusCode = proc.waitFor();
+ System.out.println("Apache NiFi exited with Status Code " + statusCode);
+ }
+
+
+ private static File getFile(final String filename, final File workingDir) {
+ File libDir = new File(filename);
+ if ( !libDir.isAbsolute() ) {
+ libDir = new File(workingDir, filename);
+ }
+
+ return libDir;
+ }
+
+ private static String replaceNull(final String value, final String replacement) {
+ return (value == null) ? replacement : value;
+ }
+}
diff --git a/nifi-bootstrap/src/main/java/org/apache/nifi/bootstrap/ShutdownHook.java b/nifi-bootstrap/src/main/java/org/apache/nifi/bootstrap/ShutdownHook.java
new file mode 100644
index 0000000000..55e1f457d2
--- /dev/null
+++ b/nifi-bootstrap/src/main/java/org/apache/nifi/bootstrap/ShutdownHook.java
@@ -0,0 +1,14 @@
+package org.apache.nifi.bootstrap;
+
+public class ShutdownHook extends Thread {
+ private final Process nifiProcess;
+
+ public ShutdownHook(final Process nifiProcess) {
+ this.nifiProcess = nifiProcess;
+ }
+
+ @Override
+ public void run() {
+ nifiProcess.destroy();
+ }
+}