Make documentation base URL configurable

Administrators may want to store documentation on a separate static
content server from the actual Gerrit server, or use a reverse proxy
configuration to serve the docs from somewhere other than
/Documentation. Allow this with a new configuration option,
gerrit.docUrl.

For parallelism while loading the app we still probe
/Documentation/index.html; the result of this request is simply
discarded if the server config endpoint later returns a configured
documentation URL.

Change-Id: I98477e3539f5c6271014cedd03354c3345e7379b
diff --git a/Documentation/config-gerrit.txt b/Documentation/config-gerrit.txt
index 723e24c..f95d9d1 100644
--- a/Documentation/config-gerrit.txt
+++ b/Documentation/config-gerrit.txt
@@ -1695,6 +1695,18 @@
 by the system administrator, and might not even be running on the
 same host as Gerrit.
 
+[[gerrit.docUrl]]gerrit.docUrl::
++
+Optional base URL for documentation, under which one can find
+"index.html", "rest-api.html", etc. Used as the base for the fixed set
+of links in the "Documentation" tab. A slash is implicitly appended.
+(For finer control over the top menu, consider writing a
+link:dev-plugins.html#top-menu-extensions[plugin].)
++
+If unset or empty, the documentation tab will only be shown if
+`/Documentation/index.html` can be reached by the browser at app load
+time.
+
 [[gerrit.installCommitMsgHookCommand]]gerrit.installCommitMsgHookCommand::
 +
 Optional command to install the `commit-msg` hook. Typically of the
diff --git a/Documentation/rest-api-config.txt b/Documentation/rest-api-config.txt
index 5a6c032..6762411 100644
--- a/Documentation/rest-api-config.txt
+++ b/Documentation/rest-api-config.txt
@@ -1163,6 +1163,10 @@
 |`all_users_name`    ||
 Name of the link:config-gerrit.html#gerrit.allUsers[project in which
 meta data of all users is stored].
+|`doc_url`           |optional|
+Custom base URL where Gerrit server documentation is located.
+(Documentation may still be available at /Documentation relative to the
+Gerrit base path even if this value is unset.)
 |`report_bug_url`    |optional|
 link:config-gerrit.html#gerrit.reportBugUrl[URL to report bugs].
 |`report_bug_text`   |optional, not set if default|
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/Gerrit.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/Gerrit.java
index 3cb9514..070a868 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/Gerrit.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/Gerrit.java
@@ -113,6 +113,7 @@
   private static String myHost;
   private static ServerInfo myServerInfo;
   private static boolean hasDocumentation;
+  private static String docUrl;
   private static HostPageData.Theme myTheme;
   private static Account myAccount;
   private static String defaultScreenToken;
@@ -434,12 +435,18 @@
       @Override
       public void onSuccess(DocInfo indexInfo) {
         hasDocumentation = indexInfo != null;
+        docUrl = selfRedirect("/Documentation/");
       }
     }));
     ConfigServerApi.serverInfo(cbg.add(new GerritCallback<ServerInfo>() {
       @Override
       public void onSuccess(ServerInfo info) {
         myServerInfo = info;
+        String du = info.gerrit().docUrl();
+        if (du != null && !du.isEmpty()) {
+          hasDocumentation = true;
+          docUrl = du;
+        }
       }
     }));
     HostPageDataService hpd = GWT.create(HostPageDataService.class);
@@ -1001,7 +1008,7 @@
 
   private static void addDocLink(final LinkMenuBar m, final String text,
       final String href) {
-    final Anchor atag = anchor(text, selfRedirect("/Documentation/" + href));
+    final Anchor atag = anchor(text, docUrl + href);
     atag.setTarget("_blank");
     m.add(atag);
   }
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/config/GerritInfo.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/config/GerritInfo.java
index 66e4154..83c1ba9 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/config/GerritInfo.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/config/GerritInfo.java
@@ -36,6 +36,7 @@
 
   public final native String allProjects() /*-{ return this.all_projects; }-*/;
   public final native String allUsers() /*-{ return this.all_users; }-*/;
+  public final native String docUrl() /*-{ return this.doc_url; }-*/;
   public final native String reportBugUrl() /*-{ return this.report_bug_url; }-*/;
   public final native String reportBugText() /*-{ return this.report_bug_text; }-*/;
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/GetServerInfo.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/GetServerInfo.java
index e4a8c34..9629a91 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/config/GetServerInfo.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/GetServerInfo.java
@@ -14,8 +14,10 @@
 
 package com.google.gerrit.server.config;
 
+import com.google.common.base.CharMatcher;
 import com.google.common.base.Function;
 import com.google.common.base.Optional;
+import com.google.common.base.Strings;
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Lists;
 import com.google.gerrit.common.data.GitwebType;
@@ -230,9 +232,18 @@
     info.allUsers = allUsersName.get();
     info.reportBugUrl = cfg.getString("gerrit", null, "reportBugUrl");
     info.reportBugText = cfg.getString("gerrit", null, "reportBugText");
+    info.docUrl = getDocUrl(cfg);
     return info;
   }
 
+  private String getDocUrl(Config cfg) {
+    String docUrl = cfg.getString("gerrit", null, "docUrl");
+    if (Strings.isNullOrEmpty(docUrl)) {
+      return null;
+    }
+    return CharMatcher.is('/').trimTrailingFrom(docUrl) + '/';
+  }
+
   private GitwebInfo getGitwebInfo(GitwebConfig cfg) {
     if (cfg.getUrl() == null || cfg.getGitwebType() == null) {
       return null;
@@ -336,6 +347,7 @@
   public static class GerritInfo {
     public String allProjects;
     public String allUsers;
+    public String docUrl;
     public String reportBugUrl;
     public String reportBugText;
   }