blob: c1f10e61ce00c457e17931b85b3f4fe02a7cd93f [file] [log] [blame]
Dave Borowitz9de65952012-08-13 16:09:45 -07001// Copyright 2012 Google Inc. All Rights Reserved.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package com.google.gitiles;
16
17import static com.google.common.base.Preconditions.checkNotNull;
Shawn Pearce10e68e62016-01-02 09:37:58 -080018import static javax.servlet.http.HttpServletResponse.SC_BAD_REQUEST;
Dave Borowitz9de65952012-08-13 16:09:45 -070019import static javax.servlet.http.HttpServletResponse.SC_FORBIDDEN;
20import static javax.servlet.http.HttpServletResponse.SC_NOT_FOUND;
21import static javax.servlet.http.HttpServletResponse.SC_SERVICE_UNAVAILABLE;
22import static javax.servlet.http.HttpServletResponse.SC_UNAUTHORIZED;
23
Shawn Pearce10e68e62016-01-02 09:37:58 -080024import com.google.common.base.Optional;
Dave Borowitz9de65952012-08-13 16:09:45 -070025import com.google.common.base.Strings;
26import com.google.common.collect.ImmutableMap;
27import com.google.common.collect.Sets;
Dave Borowitz9de65952012-08-13 16:09:45 -070028import com.google.gson.reflect.TypeToken;
Shawn Pearcec709c4c2015-08-28 15:30:42 -070029import com.google.template.soy.data.SoyData;
Dave Borowitz9de65952012-08-13 16:09:45 -070030import com.google.template.soy.data.SoyListData;
31import com.google.template.soy.data.SoyMapData;
32
33import org.eclipse.jgit.errors.RepositoryNotFoundException;
34import org.eclipse.jgit.transport.ServiceMayNotContinueException;
35import org.eclipse.jgit.transport.resolver.ServiceNotAuthorizedException;
36import org.eclipse.jgit.transport.resolver.ServiceNotEnabledException;
37import org.slf4j.Logger;
38import org.slf4j.LoggerFactory;
39
40import java.io.IOException;
Dave Borowitz673d1982014-05-02 12:30:49 -070041import java.io.Writer;
Dave Borowitz27058932014-12-03 15:44:46 -080042import java.util.Collections;
Shawn Pearcec709c4c2015-08-28 15:30:42 -070043import java.util.LinkedHashMap;
44import java.util.List;
Dave Borowitz9de65952012-08-13 16:09:45 -070045import java.util.Map;
46import java.util.Set;
47
Shawn Pearcec709c4c2015-08-28 15:30:42 -070048import javax.annotation.Nullable;
Dave Borowitz9de65952012-08-13 16:09:45 -070049import javax.servlet.http.HttpServletRequest;
50import javax.servlet.http.HttpServletResponse;
51
52/** Serves the top level index page for a Gitiles host. */
53public class HostIndexServlet extends BaseServlet {
Chad Horohoead23f142012-11-12 09:45:39 -080054 private static final long serialVersionUID = 1L;
Dave Borowitz9de65952012-08-13 16:09:45 -070055 private static final Logger log = LoggerFactory.getLogger(HostIndexServlet.class);
56
57 protected final GitilesUrls urls;
Dave Borowitz9de65952012-08-13 16:09:45 -070058
Han-Wen Nienhuysc0200f62016-05-02 17:34:51 +020059 public HostIndexServlet(
60 GitilesAccess.Factory accessFactory, Renderer renderer, GitilesUrls urls) {
Dave Borowitz8d6d6872014-03-16 15:18:14 -070061 super(renderer, accessFactory);
Dave Borowitz9de65952012-08-13 16:09:45 -070062 this.urls = checkNotNull(urls, "urls");
Dave Borowitz9de65952012-08-13 16:09:45 -070063 }
64
Shawn Pearcec709c4c2015-08-28 15:30:42 -070065 private Map<String, RepositoryDescription> list(
Han-Wen Nienhuysc0200f62016-05-02 17:34:51 +020066 HttpServletRequest req, HttpServletResponse res, String prefix, Set<String> branches)
67 throws IOException {
Dave Borowitz9de65952012-08-13 16:09:45 -070068 Map<String, RepositoryDescription> descs;
69 try {
Shawn Pearcec709c4c2015-08-28 15:30:42 -070070 descs = getAccess(req).listRepositories(prefix, branches);
Dave Borowitz9de65952012-08-13 16:09:45 -070071 } catch (RepositoryNotFoundException e) {
72 res.sendError(SC_NOT_FOUND);
Dave Borowitzb1c628f2013-01-11 11:28:20 -080073 return null;
Dave Borowitz9de65952012-08-13 16:09:45 -070074 } catch (ServiceNotEnabledException e) {
75 res.sendError(SC_FORBIDDEN);
Dave Borowitzb1c628f2013-01-11 11:28:20 -080076 return null;
Dave Borowitz9de65952012-08-13 16:09:45 -070077 } catch (ServiceNotAuthorizedException e) {
78 res.sendError(SC_UNAUTHORIZED);
Dave Borowitzb1c628f2013-01-11 11:28:20 -080079 return null;
Dave Borowitz9de65952012-08-13 16:09:45 -070080 } catch (ServiceMayNotContinueException e) {
81 // TODO(dborowitz): Show the error message to the user.
82 res.sendError(SC_FORBIDDEN);
Dave Borowitzb1c628f2013-01-11 11:28:20 -080083 return null;
Dave Borowitz9de65952012-08-13 16:09:45 -070084 } catch (IOException err) {
85 String name = urls.getHostName(req);
Jonathan Niederc0aeff12013-05-06 11:00:47 -070086 log.warn("Cannot scan repositories" + (name != null ? " for " + name : ""), err);
Dave Borowitz9de65952012-08-13 16:09:45 -070087 res.sendError(SC_SERVICE_UNAVAILABLE);
Dave Borowitzb1c628f2013-01-11 11:28:20 -080088 return null;
Dave Borowitz9de65952012-08-13 16:09:45 -070089 }
Shawn Pearcec709c4c2015-08-28 15:30:42 -070090 if (prefix != null && descs.isEmpty()) {
91 res.sendError(SC_NOT_FOUND);
92 return null;
93 }
Dave Borowitzb1c628f2013-01-11 11:28:20 -080094 return descs;
Dave Borowitz9de65952012-08-13 16:09:45 -070095 }
96
Han-Wen Nienhuysc0200f62016-05-02 17:34:51 +020097 private SoyMapData toSoyMapData(
98 RepositoryDescription desc, @Nullable String prefix, GitilesView view) {
Dave Borowitz9de65952012-08-13 16:09:45 -070099 return new SoyMapData(
Shawn Pearcec709c4c2015-08-28 15:30:42 -0700100 "name", stripPrefix(prefix, desc.name),
Dave Borowitz9de65952012-08-13 16:09:45 -0700101 "description", Strings.nullToEmpty(desc.description),
Han-Wen Nienhuysc0200f62016-05-02 17:34:51 +0200102 "url", GitilesView.repositoryIndex().copyFrom(view).setRepositoryName(desc.name).toUrl());
Dave Borowitz9de65952012-08-13 16:09:45 -0700103 }
104
Dave Borowitzb1c628f2013-01-11 11:28:20 -0800105 @Override
Han-Wen Nienhuysc0200f62016-05-02 17:34:51 +0200106 protected void doHead(HttpServletRequest req, HttpServletResponse res) throws IOException {
Shawn Pearce10e68e62016-01-02 09:37:58 -0800107 Optional<FormatType> format = getFormat(req);
108 if (!format.isPresent()) {
109 res.sendError(SC_BAD_REQUEST);
110 return;
111 }
112
113 GitilesView view = ViewFilter.getView(req);
114 String prefix = view.getRepositoryPrefix();
115 if (prefix != null) {
116 Map<String, RepositoryDescription> descs =
Han-Wen Nienhuysc0200f62016-05-02 17:34:51 +0200117 list(req, res, prefix, Collections.<String>emptySet());
Shawn Pearce10e68e62016-01-02 09:37:58 -0800118 if (descs == null) {
119 return;
120 }
121 }
122 switch (format.get()) {
123 case HTML:
124 case JSON:
125 case TEXT:
126 res.setStatus(HttpServletResponse.SC_OK);
127 res.setContentType(format.get().getMimeType());
128 break;
129 default:
130 res.sendError(SC_BAD_REQUEST);
131 break;
132 }
133 }
134
135 @Override
Dave Borowitzb1c628f2013-01-11 11:28:20 -0800136 protected void doGetHtml(HttpServletRequest req, HttpServletResponse res) throws IOException {
Shawn Pearcec709c4c2015-08-28 15:30:42 -0700137 GitilesView view = ViewFilter.getView(req);
138 String prefix = view.getRepositoryPrefix();
139 Map<String, RepositoryDescription> descs = list(req, res, prefix, parseShowBranch(req));
Dave Borowitzb1c628f2013-01-11 11:28:20 -0800140 if (descs == null) {
141 return;
142 }
Shawn Pearcec709c4c2015-08-28 15:30:42 -0700143
Dave Borowitz9de65952012-08-13 16:09:45 -0700144 SoyListData repos = new SoyListData();
145 for (RepositoryDescription desc : descs.values()) {
David Pursehouse969bfc82015-12-08 15:11:17 +0900146 if (prefix == null || desc.name.startsWith(prefix)) {
147 repos.add(toSoyMapData(desc, prefix, view));
148 }
Dave Borowitz9de65952012-08-13 16:09:45 -0700149 }
150
Shawn Pearcec709c4c2015-08-28 15:30:42 -0700151 String hostName = urls.getHostName(req);
152 List<Map<String, String>> breadcrumbs = null;
153 if (prefix != null) {
154 hostName = hostName + '/' + prefix;
155 breadcrumbs = view.getBreadcrumbs();
156 }
Han-Wen Nienhuysc0200f62016-05-02 17:34:51 +0200157 renderHtml(
158 req,
159 res,
160 "gitiles.hostIndex",
161 ImmutableMap.of(
162 "hostName",
163 hostName,
164 "breadcrumbs",
165 SoyData.createFromExistingData(breadcrumbs),
166 "prefix",
167 prefix != null ? prefix + '/' : "",
168 "repositories",
169 repos));
Dave Borowitz9de65952012-08-13 16:09:45 -0700170 }
171
Dave Borowitzb1c628f2013-01-11 11:28:20 -0800172 @Override
173 protected void doGetText(HttpServletRequest req, HttpServletResponse res) throws IOException {
Shawn Pearcec709c4c2015-08-28 15:30:42 -0700174 String prefix = ViewFilter.getView(req).getRepositoryPrefix();
Dave Borowitzb1c628f2013-01-11 11:28:20 -0800175 Set<String> branches = parseShowBranch(req);
Shawn Pearcec709c4c2015-08-28 15:30:42 -0700176 Map<String, RepositoryDescription> descs = list(req, res, prefix, branches);
Dave Borowitzb1c628f2013-01-11 11:28:20 -0800177 if (descs == null) {
178 return;
179 }
Dave Borowitz9de65952012-08-13 16:09:45 -0700180
Dave Borowitz673d1982014-05-02 12:30:49 -0700181 Writer writer = startRenderText(req, res);
Dave Borowitz9de65952012-08-13 16:09:45 -0700182 for (RepositoryDescription repo : descs.values()) {
183 for (String name : branches) {
184 String ref = repo.branches.get(name);
185 if (ref == null) {
186 // Print stub (forty '-' symbols)
187 ref = "----------------------------------------";
188 }
Dave Borowitz673d1982014-05-02 12:30:49 -0700189 writer.write(ref);
190 writer.write(' ');
Dave Borowitz9de65952012-08-13 16:09:45 -0700191 }
Shawn Pearcec709c4c2015-08-28 15:30:42 -0700192 writer.write(GitilesUrls.NAME_ESCAPER.apply(stripPrefix(prefix, repo.name)));
Dave Borowitz673d1982014-05-02 12:30:49 -0700193 writer.write('\n');
Dave Borowitz9de65952012-08-13 16:09:45 -0700194 }
195 writer.flush();
196 writer.close();
197 }
198
Dave Borowitzb1c628f2013-01-11 11:28:20 -0800199 @Override
200 protected void doGetJson(HttpServletRequest req, HttpServletResponse res) throws IOException {
Shawn Pearcec709c4c2015-08-28 15:30:42 -0700201 String prefix = ViewFilter.getView(req).getRepositoryPrefix();
202 Map<String, RepositoryDescription> descs = list(req, res, prefix, parseShowBranch(req));
Dave Borowitzb1c628f2013-01-11 11:28:20 -0800203 if (descs == null) {
204 return;
205 }
Shawn Pearcec709c4c2015-08-28 15:30:42 -0700206 if (prefix != null) {
207 Map<String, RepositoryDescription> r = new LinkedHashMap<>();
208 for (Map.Entry<String, RepositoryDescription> e : descs.entrySet()) {
209 r.put(stripPrefix(prefix, e.getKey()), e.getValue());
210 }
211 descs = r;
212 }
Dave Borowitzb1c628f2013-01-11 11:28:20 -0800213 renderJson(req, res, descs, new TypeToken<Map<String, RepositoryDescription>>() {}.getType());
Dave Borowitz9de65952012-08-13 16:09:45 -0700214 }
215
Shawn Pearcec709c4c2015-08-28 15:30:42 -0700216 private static String stripPrefix(@Nullable String prefix, String name) {
David Pursehouse969bfc82015-12-08 15:11:17 +0900217 if (prefix != null && name.startsWith(prefix)) {
218 return name.substring(prefix.length() + 1);
219 }
220 return name;
Shawn Pearcec709c4c2015-08-28 15:30:42 -0700221 }
222
Dave Borowitz9de65952012-08-13 16:09:45 -0700223 private static Set<String> parseShowBranch(HttpServletRequest req) {
224 // Roughly match Gerrit Code Review's /projects/ API by supporting
225 // both show-branch and b as query parameters.
226 Set<String> branches = Sets.newLinkedHashSet();
227 String[] values = req.getParameterValues("show-branch");
228 if (values != null) {
Dave Borowitz27058932014-12-03 15:44:46 -0800229 Collections.addAll(branches, values);
Dave Borowitz9de65952012-08-13 16:09:45 -0700230 }
231 values = req.getParameterValues("b");
232 if (values != null) {
Dave Borowitz27058932014-12-03 15:44:46 -0800233 Collections.addAll(branches, values);
Dave Borowitz9de65952012-08-13 16:09:45 -0700234 }
235 return branches;
236 }
237}