NIFIREG-222 Improving logic for determining working dir and docs dir

This closes #186.

Signed-off-by: Kevin Doran <kdoran@apache.org>
diff --git a/nifi-registry-core/nifi-registry-bootstrap/src/main/java/org/apache/nifi/registry/bootstrap/RunNiFiRegistry.java b/nifi-registry-core/nifi-registry-bootstrap/src/main/java/org/apache/nifi/registry/bootstrap/RunNiFiRegistry.java
index 769d1c4..c3bc172 100644
--- a/nifi-registry-core/nifi-registry-bootstrap/src/main/java/org/apache/nifi/registry/bootstrap/RunNiFiRegistry.java
+++ b/nifi-registry-core/nifi-registry-bootstrap/src/main/java/org/apache/nifi/registry/bootstrap/RunNiFiRegistry.java
@@ -80,6 +80,7 @@
     public static final String DEFAULT_JAVA_CMD = "java";
     public static final String DEFAULT_PID_DIR = "bin";
     public static final String DEFAULT_LOG_DIR = "./logs";
+    public static final String DEFAULT_DOCS_DIR = "./docs";
 
     public static final String GRACEFUL_SHUTDOWN_PROP = "graceful.shutdown.seconds";
     public static final String DEFAULT_GRACEFUL_SHUTDOWN_VALUE = "20";
@@ -827,21 +828,32 @@
         final Map<String, String> props = new HashMap<>();
         props.putAll((Map) properties);
 
+        // Determine the working dir for launching the NiFi Registry process
+        // The order of precedence is:
+        // 1) Specified in bootstrap.conf via working.dir
+        // 2) NIFI_REGISTRY_HOME env variable
+        // 3) Parent of bootstrap config file's parent
+
+        final File workingDir;
         final String specifiedWorkingDir = props.get("working.dir");
-        if (specifiedWorkingDir != null) {
-            builder.directory(new File(specifiedWorkingDir));
-        }
-
+        final String nifiRegistryHome = System.getenv("NIFI_REGISTRY_HOME");
         final File bootstrapConfigAbsoluteFile = bootstrapConfigFile.getAbsoluteFile();
-        final File binDir = bootstrapConfigAbsoluteFile.getParentFile();
-        final File workingDir = binDir.getParentFile();
 
-        if (specifiedWorkingDir == null) {
-            builder.directory(workingDir);
+        if (!StringUtils.isBlank(specifiedWorkingDir)) {
+            workingDir = new File(specifiedWorkingDir.trim());
+        } else if (!StringUtils.isBlank(nifiRegistryHome)) {
+            workingDir = new File(nifiRegistryHome.trim());
+        } else {
+            final File binDir = bootstrapConfigAbsoluteFile.getParentFile();
+            workingDir = binDir.getParentFile();
         }
 
+        builder.directory(workingDir);
+
         final String nifiRegistryLogDir = replaceNull(System.getProperty("org.apache.nifi.registry.bootstrap.config.log.dir"), DEFAULT_LOG_DIR).trim();
 
+        final String nifiRegistryDocsDir = replaceNull(props.get("docs.dir"), DEFAULT_DOCS_DIR).trim();
+
         final String libFilename = replaceNull(props.get("lib.dir"), "./lib").trim();
         File libDir = getFile(libFilename, workingDir);
         File libSharedDir = getFile(libFilename + "/shared", workingDir);
@@ -944,6 +956,7 @@
         cmd.add("-Dnifi.registry.properties.file.path=" + nifiRegistryPropsFilename);
         cmd.add("-Dnifi.registry.bootstrap.config.file.path=" + bootstrapConfigFile.getAbsolutePath());
         cmd.add("-Dnifi.registry.bootstrap.listen.port=" + listenPort);
+        cmd.add("-Dnifi.registry.bootstrap.config.docs.dir=" + nifiRegistryDocsDir);
         cmd.add("-Dapp=NiFiRegistry");
         cmd.add("-Dorg.apache.nifi.registry.bootstrap.config.log.dir=" + nifiRegistryLogDir);
         cmd.add("org.apache.nifi.registry.NiFiRegistry");
diff --git a/nifi-registry-core/nifi-registry-jetty/src/main/java/org/apache/nifi/registry/jetty/JettyServer.java b/nifi-registry-core/nifi-registry-jetty/src/main/java/org/apache/nifi/registry/jetty/JettyServer.java
index c202a5b..0eb6d88 100644
--- a/nifi-registry-core/nifi-registry-jetty/src/main/java/org/apache/nifi/registry/jetty/JettyServer.java
+++ b/nifi-registry-core/nifi-registry-jetty/src/main/java/org/apache/nifi/registry/jetty/JettyServer.java
@@ -78,18 +78,20 @@
 
     private final NiFiRegistryProperties properties;
     private final CryptoKeyProvider masterKeyProvider;
+    private final String docsLocation;
     private final Server server;
 
     private WebAppContext webUiContext;
     private WebAppContext webApiContext;
     private WebAppContext webDocsContext;
 
-    public JettyServer(final NiFiRegistryProperties properties, final CryptoKeyProvider cryptoKeyProvider) {
+    public JettyServer(final NiFiRegistryProperties properties, final CryptoKeyProvider cryptoKeyProvider, final String docsLocation) {
         final QueuedThreadPool threadPool = new QueuedThreadPool(properties.getWebThreads());
         threadPool.setName("NiFi Registry Web Server");
 
         this.properties = properties;
         this.masterKeyProvider = cryptoKeyProvider;
+        this.docsLocation = docsLocation;
         this.server = new Server(threadPool);
 
         // enable the annotation based configuration to ensure the jsp container is initialized properly
@@ -373,7 +375,24 @@
         resourceHandler.setDirectoriesListed(false);
 
         // load the docs directory
-        final File docsDir = Paths.get("docs").toRealPath().toFile();
+        String docsDirectory = docsLocation;
+        if (StringUtils.isBlank(docsDirectory)) {
+            docsDirectory = "docs";
+        }
+
+        File docsDir;
+        try {
+            docsDir = Paths.get(docsDirectory).toRealPath().toFile();
+        } catch (IOException ex) {
+            logger.warn("Directory '" + docsDirectory + "' is missing. Some documentation will be unavailable.");
+            docsDir = new File(docsDirectory).getAbsoluteFile();
+            final boolean made = docsDir.mkdirs();
+            if (!made) {
+                logger.error("Failed to create 'docs' directory!");
+                startUpFailure(new IOException(docsDir.getAbsolutePath() + " could not be created"));
+            }
+        }
+
         final Resource docsResource = Resource.newResource(docsDir);
 
         // load the rest documentation
diff --git a/nifi-registry-core/nifi-registry-resources/src/main/resources/conf/bootstrap.conf b/nifi-registry-core/nifi-registry-resources/src/main/resources/conf/bootstrap.conf
index 637eb64..e4bf313 100644
--- a/nifi-registry-core/nifi-registry-resources/src/main/resources/conf/bootstrap.conf
+++ b/nifi-registry-core/nifi-registry-resources/src/main/resources/conf/bootstrap.conf
@@ -21,9 +21,15 @@
 # Username to use when running nifi-registry. This value will be ignored on Windows.
 run.as=
 
+# Configure the working directory for launching the NiFi Registry process
+# If not specified, the working directory will fall back to using the NIFI_REGISTRY_HOME env variable
+# If the environment variable is not specified, the working directory will fall back to the parent of this file's parent
+working.dir=
+
 # Configure where nifi-registry's lib and conf directories live
 lib.dir=./lib
 conf.dir=./conf
+docs.dir=./docs
 
 # How long to wait after telling nifi-registry to shutdown before explicitly killing the Process
 graceful.shutdown.seconds=20
diff --git a/nifi-registry-core/nifi-registry-runtime/src/main/java/org/apache/nifi/registry/NiFiRegistry.java b/nifi-registry-core/nifi-registry-runtime/src/main/java/org/apache/nifi/registry/NiFiRegistry.java
index 982b9ea..455ab9d 100644
--- a/nifi-registry-core/nifi-registry-runtime/src/main/java/org/apache/nifi/registry/NiFiRegistry.java
+++ b/nifi-registry-core/nifi-registry-runtime/src/main/java/org/apache/nifi/registry/NiFiRegistry.java
@@ -44,9 +44,11 @@
 
     public static final String NIFI_REGISTRY_PROPERTIES_FILE_PATH_PROPERTY = "nifi.registry.properties.file.path";
     public static final String NIFI_REGISTRY_BOOTSTRAP_FILE_PATH_PROPERTY = "nifi.registry.bootstrap.config.file.path";
+    public static final String NIFI_REGISTRY_BOOTSTRAP_DOCS_DIR_PROPERTY = "nifi.registry.bootstrap.config.docs.dir";
 
     public static final String RELATIVE_BOOTSTRAP_FILE_LOCATION = "conf/bootstrap.conf";
     public static final String RELATIVE_PROPERTIES_FILE_LOCATION = "conf/nifi-registry.properties";
+    public static final String RELATIVE_DOCS_LOCATION = "docs";
 
     private final JettyServer server;
     private final BootstrapListener bootstrapListener;
@@ -104,8 +106,10 @@
         SLF4JBridgeHandler.removeHandlersForRootLogger();
         SLF4JBridgeHandler.install();
 
+        final String docsDir = System.getProperty(NIFI_REGISTRY_BOOTSTRAP_DOCS_DIR_PROPERTY, RELATIVE_DOCS_LOCATION);
+
         final long startTime = System.nanoTime();
-        server = new JettyServer(properties, masterKeyProvider);
+        server = new JettyServer(properties, masterKeyProvider, docsDir);
 
         if (shutdown) {
             LOGGER.info("NiFi Registry has been shutdown via NiFi Registry Bootstrap. Will not start Controller");