Plugin support for project weblinks

Plugins can now contribute project links that will be displayed on the
project list screen in the 'Repository Browser' column.

Change-Id: Ia96679b249dd9677d9138e8af6239e1e5ea6f1d4
diff --git a/Documentation/dev-plugins.txt b/Documentation/dev-plugins.txt
index dc98e11..73f4bab 100644
--- a/Documentation/dev-plugins.txt
+++ b/Documentation/dev-plugins.txt
@@ -1739,6 +1739,9 @@
 }
 ----
 
+ProjectWebLinks will appear in the project list in the
+`Repository Browser` column.
+
 [[documentation]]
 == Documentation
 
diff --git a/Documentation/rest-api-projects.txt b/Documentation/rest-api-projects.txt
index 418b93c..624cff9 100644
--- a/Documentation/rest-api-projects.txt
+++ b/Documentation/rest-api-projects.txt
@@ -1501,6 +1501,9 @@
 is increased for each non-visible project).
 |`description` |optional|The description of the project.
 |`branches`    |optional|Map of branch names to HEAD revisions.
+|'web_links'   |optional|
+Links to the project in external sites as a list of
+link:rest-api-changes.html#web-link-info[WebLinkInfo] entries.
 |===========================
 
 [[project-input]]
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/ProjectInfo.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/ProjectInfo.java
index 811a05f..bb07e44 100644
--- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/ProjectInfo.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/ProjectInfo.java
@@ -16,6 +16,7 @@
 
 import com.google.gerrit.extensions.api.projects.ProjectState;
 
+import java.util.List;
 import java.util.Map;
 
 public class ProjectInfo {
@@ -25,4 +26,5 @@
   public String description;
   public ProjectState state;
   public Map<String, String> branches;
-}
\ No newline at end of file
+  public List<WebLinkInfo> webLinks;
+}
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/RevisionInfo.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/RevisionInfo.java
index a89911c..8f61aa2 100644
--- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/RevisionInfo.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/RevisionInfo.java
@@ -27,14 +27,4 @@
   public Map<String, FileInfo> files;
   public Map<String, ActionInfo> actions;
   public List<WebLinkInfo> webLinks;
-
-  public static class WebLinkInfo {
-    public String name;
-    public String url;
-
-    public WebLinkInfo(String name, String url) {
-      this.name = name;
-      this.url = url;
-    }
-  }
 }
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/WebLinkInfo.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/WebLinkInfo.java
new file mode 100644
index 0000000..7695c8c
--- /dev/null
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/WebLinkInfo.java
@@ -0,0 +1,25 @@
+// Copyright (C) 2014 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.extensions.common;
+
+public class WebLinkInfo {
+  public String name;
+  public String url;
+
+  public WebLinkInfo(String name, String url) {
+    this.name = name;
+    this.url = url;
+  }
+}
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/webui/ProjectWebLink.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/webui/ProjectWebLink.java
new file mode 100644
index 0000000..61e9982
--- /dev/null
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/webui/ProjectWebLink.java
@@ -0,0 +1,29 @@
+// Copyright (C) 2014 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.extensions.webui;
+
+import com.google.gerrit.extensions.annotations.ExtensionPoint;
+
+@ExtensionPoint
+public interface ProjectWebLink extends WebLink {
+
+  /**
+   * URL to project in external service.
+   *
+   * @param projectName Name of the project
+   * @return url to project in external service.
+   */
+  String getProjectUrl(String projectName);
+}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/WebLinkInfo.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/WebLinkInfo.java
new file mode 100644
index 0000000..64b9cb8
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/WebLinkInfo.java
@@ -0,0 +1,26 @@
+// Copyright (C) 2014 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.client;
+
+import com.google.gwt.core.client.JavaScriptObject;
+
+public class WebLinkInfo extends JavaScriptObject {
+
+  public final native String name() /*-{ return this.name; }-*/;
+  public final native String url() /*-{ return this.url; }-*/;
+
+  protected WebLinkInfo() {
+  }
+}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectListScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectListScreen.java
index 3a6cb7d..3bd05e0 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectListScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectListScreen.java
@@ -19,9 +19,11 @@
 import com.google.gerrit.client.Dispatcher;
 import com.google.gerrit.client.Gerrit;
 import com.google.gerrit.client.GitwebLink;
+import com.google.gerrit.client.WebLinkInfo;
 import com.google.gerrit.client.projects.ProjectInfo;
 import com.google.gerrit.client.projects.ProjectMap;
 import com.google.gerrit.client.rpc.GerritCallback;
+import com.google.gerrit.client.rpc.Natives;
 import com.google.gerrit.client.ui.FilteredUserInterface;
 import com.google.gerrit.client.ui.HighlightingInlineHyperlink;
 import com.google.gerrit.client.ui.Hyperlink;
@@ -43,6 +45,8 @@
 import com.google.gwt.user.client.ui.Label;
 import com.google.gwtexpui.globalkey.client.NpTextBox;
 
+import java.util.List;
+
 public class ProjectListScreen extends Screen implements FilteredUserInterface {
   private Hyperlink prev;
   private Hyperlink next;
@@ -167,13 +171,11 @@
       @Override
       protected void initColumnHeaders() {
         super.initColumnHeaders();
-        if (Gerrit.getGitwebLink() != null) {
-          table.setText(0, ProjectsTable.C_REPO_BROWSER,
-              Util.C.projectRepoBrowser());
-          table.getFlexCellFormatter().
-            addStyleName(0, ProjectsTable.C_REPO_BROWSER,
-                Gerrit.RESOURCES.css().dataHeader());
-        }
+        table.setText(0, ProjectsTable.C_REPO_BROWSER,
+            Util.C.projectRepoBrowser());
+        table.getFlexCellFormatter().
+          addStyleName(0, ProjectsTable.C_REPO_BROWSER,
+              Gerrit.RESOURCES.css().dataHeader());
       }
 
       @Override
@@ -188,11 +190,8 @@
       @Override
       protected void insert(int row, ProjectInfo k) {
         super.insert(row, k);
-        if (Gerrit.getGitwebLink() != null) {
-          table.getFlexCellFormatter().
-            addStyleName(row, ProjectsTable.C_REPO_BROWSER,
-                Gerrit.RESOURCES.css().dataCell());
-        }
+        table.getFlexCellFormatter().addStyleName(row,
+            ProjectsTable.C_REPO_BROWSER, Gerrit.RESOURCES.css().dataCell());
       }
 
       @Override
@@ -219,15 +218,33 @@
         fp.add(new HighlightingInlineHyperlink(k.name(), link(k), subname));
         table.setWidget(row, ProjectsTable.C_NAME, fp);
         table.setText(row, ProjectsTable.C_DESCRIPTION, k.description());
-        GitwebLink l = Gerrit.getGitwebLink();
-        if (l != null) {
-          table.setWidget(row, ProjectsTable.C_REPO_BROWSER,
-              new Anchor(l.getLinkName(), false, l.toProject(k
-              .name_key())));
-        }
+        addWebLinks(row, k);
 
         setRowItem(row, k);
       }
+
+      private void addWebLinks(int row, ProjectInfo k) {
+        GitwebLink gitWebLink = Gerrit.getGitwebLink();
+        List<WebLinkInfo> webLinks = Natives.asList(k.web_links());
+        if (gitWebLink != null || (webLinks != null && !webLinks.isEmpty())) {
+          FlowPanel p = new FlowPanel();
+          table.setWidget(row, ProjectsTable.C_REPO_BROWSER, p);
+
+          if (gitWebLink != null) {
+            Anchor a = new Anchor();
+            a.setText(gitWebLink.getLinkName());
+            a.setHref(gitWebLink.toProject(k.name_key()));
+            p.add(a);
+          }
+
+          for (WebLinkInfo weblink : webLinks) {
+            Anchor a = new Anchor();
+            a.setText("(" + weblink.name() + ")");
+            a.setHref(weblink.url());
+            p.add(a);
+          }
+        }
+      }
     };
     projects.setSavePointerId(PageLinks.ADMIN_PROJECTS);
 
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/CommitBox.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/CommitBox.java
index 6400b2b..d6b7feb 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/CommitBox.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/CommitBox.java
@@ -17,11 +17,11 @@
 import com.google.gerrit.client.FormatUtil;
 import com.google.gerrit.client.Gerrit;
 import com.google.gerrit.client.GitwebLink;
+import com.google.gerrit.client.WebLinkInfo;
 import com.google.gerrit.client.changes.ChangeInfo;
 import com.google.gerrit.client.changes.ChangeInfo.CommitInfo;
 import com.google.gerrit.client.changes.ChangeInfo.GitPerson;
 import com.google.gerrit.client.changes.ChangeInfo.RevisionInfo;
-import com.google.gerrit.client.changes.ChangeInfo.WebLinkInfo;
 import com.google.gerrit.client.rpc.Natives;
 import com.google.gerrit.client.ui.CommentLinkProcessor;
 import com.google.gerrit.client.ui.InlineHyperlink;
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeInfo.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeInfo.java
index a627725..c1cbc6a 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeInfo.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeInfo.java
@@ -14,6 +14,7 @@
 
 package com.google.gerrit.client.changes;
 
+import com.google.gerrit.client.WebLinkInfo;
 import com.google.gerrit.client.account.AccountInfo;
 import com.google.gerrit.client.actions.ActionInfo;
 import com.google.gerrit.client.diff.FileInfo;
@@ -297,11 +298,4 @@
     protected IncludedInInfo() {
     }
   }
-
-  public static class WebLinkInfo extends JavaScriptObject {
-    public final native String name() /*-{ return this.name; }-*/;
-    public final native String url() /*-{ return this.url; }-*/;
-    protected WebLinkInfo() {
-    }
-  }
 }
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/projects/ProjectInfo.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/projects/ProjectInfo.java
index bd50e06..048ebbd 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/projects/ProjectInfo.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/projects/ProjectInfo.java
@@ -14,9 +14,11 @@
 
 package com.google.gerrit.client.projects;
 
+import com.google.gerrit.client.WebLinkInfo;
 import com.google.gerrit.extensions.api.projects.ProjectState;
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gwt.core.client.JsArray;
 import com.google.gwt.user.client.ui.SuggestOracle;
 
 public class ProjectInfo
@@ -28,6 +30,7 @@
 
   public final native String name() /*-{ return this.name; }-*/;
   public final native String description() /*-{ return this.description; }-*/;
+  public final native JsArray<WebLinkInfo> web_links() /*-{ return this.web_links; }-*/;
 
   public final ProjectState state() {
     return ProjectState.valueOf(getStringState());
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/WebLinks.java b/gerrit-server/src/main/java/com/google/gerrit/server/WebLinks.java
index d2bc8b7..58cf8ac4 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/WebLinks.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/WebLinks.java
@@ -11,26 +11,30 @@
 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 // See the License for the specific language governing permissions and
 // limitations under the License.
+
 package com.google.gerrit.server;
 
 import com.google.common.collect.Lists;
 import com.google.gerrit.extensions.registration.DynamicSet;
 import com.google.gerrit.extensions.webui.PatchSetWebLink;
+import com.google.gerrit.extensions.webui.ProjectWebLink;
 import com.google.inject.Inject;
 
 import java.util.List;
 
 public class WebLinks {
 
-  private DynamicSet<PatchSetWebLink> patchSetLinks;
+  private final DynamicSet<PatchSetWebLink> patchSetLinks;
+  private final DynamicSet<ProjectWebLink> projectLinks;
 
   @Inject
-  public WebLinks(final DynamicSet<PatchSetWebLink> patchSetLinks) {
+  public WebLinks(DynamicSet<PatchSetWebLink> patchSetLinks,
+      DynamicSet<ProjectWebLink> projectLinks) {
     this.patchSetLinks = patchSetLinks;
+    this.projectLinks = projectLinks;
   }
 
-  public Iterable<Link> getPatchSetLinks(final String project,
-      final String commit) {
+  public Iterable<Link> getPatchSetLinks(String project, String commit) {
     List<Link> links = Lists.newArrayList();
     for (PatchSetWebLink webLink : patchSetLinks) {
       links.add(new Link(webLink.getLinkName(),
@@ -39,6 +43,15 @@
     return links;
   }
 
+  public Iterable<Link> getProjectLinks(String project) {
+    List<Link> links = Lists.newArrayList();
+    for (ProjectWebLink webLink : projectLinks) {
+      links.add(new Link(webLink.getLinkName(),
+          webLink.getProjectUrl(project)));
+    }
+    return links;
+  }
+
   public class Link {
     public String name;
     public String url;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/WebLinksProvider.java b/gerrit-server/src/main/java/com/google/gerrit/server/WebLinksProvider.java
index 71c13a3..e3ffa62 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/WebLinksProvider.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/WebLinksProvider.java
@@ -11,25 +11,30 @@
 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 // See the License for the specific language governing permissions and
 // limitations under the License.
+
 package com.google.gerrit.server;
 
 import com.google.gerrit.extensions.registration.DynamicSet;
 import com.google.gerrit.extensions.webui.PatchSetWebLink;
+import com.google.gerrit.extensions.webui.ProjectWebLink;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
 
 public class WebLinksProvider implements Provider<WebLinks> {
 
-  private DynamicSet<PatchSetWebLink> patchSetLinks;
+  private final DynamicSet<PatchSetWebLink> patchSetLinks;
+  private final DynamicSet<ProjectWebLink> projectLinks;
 
   @Inject
-  public WebLinksProvider(DynamicSet<PatchSetWebLink> patchSetLinks) {
+  public WebLinksProvider(DynamicSet<PatchSetWebLink> patchSetLinks,
+      DynamicSet<ProjectWebLink> projectLinks) {
     this.patchSetLinks = patchSetLinks;
+    this.projectLinks = projectLinks;
   }
 
   @Override
   public WebLinks get() {
-    WebLinks webLinks = new WebLinks(patchSetLinks);
+    WebLinks webLinks = new WebLinks(patchSetLinks, projectLinks);
     return webLinks;
   }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeJson.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeJson.java
index 4a7cbdb..42bfab2 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeJson.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeJson.java
@@ -60,6 +60,7 @@
 import com.google.gerrit.extensions.common.GitPerson;
 import com.google.gerrit.extensions.common.ListChangesOption;
 import com.google.gerrit.extensions.common.RevisionInfo;
+import com.google.gerrit.extensions.common.WebLinkInfo;
 import com.google.gerrit.extensions.config.DownloadCommand;
 import com.google.gerrit.extensions.config.DownloadScheme;
 import com.google.gerrit.extensions.registration.DynamicMap;
@@ -882,7 +883,7 @@
       out.webLinks = Lists.newArrayList();
       for (WebLinks.Link link : webLinks.get().getPatchSetLinks(
           project, in.getRevision().get())) {
-        out.webLinks.add(new RevisionInfo.WebLinkInfo(link.name, link.url));
+        out.webLinks.add(new WebLinkInfo(link.name, link.url));
       }
     }
     return out;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritGlobalModule.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritGlobalModule.java
index f09cd7c..a1c2ddb 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritGlobalModule.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritGlobalModule.java
@@ -32,6 +32,7 @@
 import com.google.gerrit.extensions.registration.DynamicSet;
 import com.google.gerrit.extensions.systemstatus.MessageOfTheDay;
 import com.google.gerrit.extensions.webui.PatchSetWebLink;
+import com.google.gerrit.extensions.webui.ProjectWebLink;
 import com.google.gerrit.extensions.webui.TopMenu;
 import com.google.gerrit.reviewdb.client.AccountGroup;
 import com.google.gerrit.rules.PrologModule;
@@ -271,6 +272,7 @@
     DynamicMap.mapOf(binder(), DownloadCommand.class);
     DynamicMap.mapOf(binder(), ProjectConfigEntry.class);
     DynamicSet.setOf(binder(), PatchSetWebLink.class);
+    DynamicSet.setOf(binder(), ProjectWebLink.class);
 
     bind(AnonymousUser.class);
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/ListProjects.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/ListProjects.java
index d249b1d..55f15f5 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/ListProjects.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/ListProjects.java
@@ -22,6 +22,7 @@
 import com.google.gerrit.common.data.GroupReference;
 import com.google.gerrit.common.errors.NoSuchGroupException;
 import com.google.gerrit.extensions.common.ProjectInfo;
+import com.google.gerrit.extensions.common.WebLinkInfo;
 import com.google.gerrit.extensions.restapi.BinaryResult;
 import com.google.gerrit.extensions.restapi.RestReadView;
 import com.google.gerrit.extensions.restapi.TopLevelResource;
@@ -32,12 +33,14 @@
 import com.google.gerrit.server.CurrentUser;
 import com.google.gerrit.server.OutputFormat;
 import com.google.gerrit.server.StringUtil;
+import com.google.gerrit.server.WebLinks;
 import com.google.gerrit.server.account.GroupCache;
 import com.google.gerrit.server.account.GroupControl;
 import com.google.gerrit.server.git.GitRepositoryManager;
 import com.google.gerrit.server.util.TreeFormatter;
 import com.google.gson.reflect.TypeToken;
 import com.google.inject.Inject;
+import com.google.inject.Provider;
 
 import org.eclipse.jgit.errors.RepositoryNotFoundException;
 import org.eclipse.jgit.lib.Constants;
@@ -106,6 +109,7 @@
   private final GroupControl.Factory groupControlFactory;
   private final GitRepositoryManager repoManager;
   private final ProjectNode.Factory projectNodeFactory;
+  private final Provider<WebLinks> webLinks;
 
   @Deprecated
   @Option(name = "--format", usage = "(deprecated) output format")
@@ -179,13 +183,15 @@
   @Inject
   protected ListProjects(CurrentUser currentUser, ProjectCache projectCache,
       GroupCache groupCache, GroupControl.Factory groupControlFactory,
-      GitRepositoryManager repoManager, ProjectNode.Factory projectNodeFactory) {
+      GitRepositoryManager repoManager, ProjectNode.Factory projectNodeFactory,
+      Provider<WebLinks> webLinks) {
     this.currentUser = currentUser;
     this.projectCache = projectCache;
     this.groupCache = groupCache;
     this.groupControlFactory = groupControlFactory;
     this.repoManager = repoManager;
     this.projectNodeFactory = projectNodeFactory;
+    this.webLinks = webLinks;
   }
 
   public List<String> getShowBranch() {
@@ -368,6 +374,13 @@
             log.warn("Unexpected error reading " + projectName, err);
             continue;
           }
+
+          info.webLinks = Lists.newArrayList();
+          for (WebLinks.Link link : webLinks.get().getProjectLinks(projectName.get())) {
+            if (!Strings.isNullOrEmpty(link.name) && !Strings.isNullOrEmpty(link.url)) {
+              info.webLinks.add(new WebLinkInfo(link.name, link.url));
+            }
+          }
         }
 
         if (foundIndex++ < start) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectJson.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectJson.java
index 57d1406..590d69b 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectJson.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectJson.java
@@ -15,19 +15,25 @@
 package com.google.gerrit.server.project;
 
 import com.google.common.base.Strings;
+import com.google.common.collect.Lists;
 import com.google.gerrit.extensions.common.ProjectInfo;
+import com.google.gerrit.extensions.common.WebLinkInfo;
 import com.google.gerrit.extensions.restapi.Url;
 import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.server.WebLinks;
 import com.google.gerrit.server.config.AllProjectsName;
 import com.google.inject.Inject;
+import com.google.inject.Provider;
 
 public class ProjectJson {
 
   private final AllProjectsName allProjects;
+  private final Provider<WebLinks> webLinks;
 
   @Inject
-  ProjectJson(AllProjectsName allProjects) {
+  ProjectJson(AllProjectsName allProjects, Provider<WebLinks> webLinks) {
     this.allProjects = allProjects;
+    this.webLinks = webLinks;
   }
 
   public ProjectInfo format(ProjectResource rsrc) {
@@ -42,6 +48,14 @@
     info.description = Strings.emptyToNull(p.getDescription());
     info.state = p.getState();
     info.id = Url.encode(info.name);
+
+    info.webLinks = Lists.newArrayList();
+    for (WebLinks.Link link : webLinks.get().getProjectLinks(p.getName())) {
+      if (!Strings.isNullOrEmpty(link.name) && !Strings.isNullOrEmpty(link.url)) {
+        info.webLinks.add(new WebLinkInfo(link.name, link.url));
+      }
+    }
+
     return info;
   }
 }