blob: bf9ffd119c0fa2a18205e371f4645dc7e4c80402 [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;
18import static com.google.gitiles.FormatType.JSON;
19import static com.google.gitiles.FormatType.TEXT;
20import static javax.servlet.http.HttpServletResponse.SC_BAD_REQUEST;
21import static javax.servlet.http.HttpServletResponse.SC_FORBIDDEN;
22import static javax.servlet.http.HttpServletResponse.SC_NOT_FOUND;
23import static javax.servlet.http.HttpServletResponse.SC_SERVICE_UNAVAILABLE;
24import static javax.servlet.http.HttpServletResponse.SC_UNAUTHORIZED;
25
26import com.google.common.base.Strings;
27import com.google.common.collect.ImmutableMap;
28import com.google.common.collect.Sets;
29import com.google.common.net.HttpHeaders;
30import com.google.gson.FieldNamingPolicy;
31import com.google.gson.GsonBuilder;
32import com.google.gson.reflect.TypeToken;
33import com.google.template.soy.data.SoyListData;
34import com.google.template.soy.data.SoyMapData;
35
36import org.eclipse.jgit.errors.RepositoryNotFoundException;
37import org.eclipse.jgit.transport.ServiceMayNotContinueException;
38import org.eclipse.jgit.transport.resolver.ServiceNotAuthorizedException;
39import org.eclipse.jgit.transport.resolver.ServiceNotEnabledException;
40import org.slf4j.Logger;
41import org.slf4j.LoggerFactory;
42
43import java.io.IOException;
44import java.io.PrintWriter;
45import java.util.Arrays;
46import java.util.Map;
47import java.util.Set;
48
49import 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 {
54 private static final Logger log = LoggerFactory.getLogger(HostIndexServlet.class);
55
56 protected final GitilesUrls urls;
57 private final GitilesAccess.Factory accessFactory;
58
59 public HostIndexServlet(Renderer renderer, GitilesUrls urls,
60 GitilesAccess.Factory accessFactory) {
61 super(renderer);
62 this.urls = checkNotNull(urls, "urls");
63 this.accessFactory = checkNotNull(accessFactory, "accessFactory");
64 }
65
66 @Override
67 protected void doGet(HttpServletRequest req, HttpServletResponse res) throws IOException {
68 FormatType format;
69 try {
70 format = FormatType.getFormatType(req);
71 } catch (IllegalArgumentException err) {
72 res.sendError(SC_BAD_REQUEST);
73 return;
74 }
75
76 Set<String> branches = parseShowBranch(req);
77 Map<String, RepositoryDescription> descs;
78 try {
79 descs = accessFactory.forRequest(req).listRepositories(branches);
80 } catch (RepositoryNotFoundException e) {
81 res.sendError(SC_NOT_FOUND);
82 return;
83 } catch (ServiceNotEnabledException e) {
84 res.sendError(SC_FORBIDDEN);
85 return;
86 } catch (ServiceNotAuthorizedException e) {
87 res.sendError(SC_UNAUTHORIZED);
88 return;
89 } catch (ServiceMayNotContinueException e) {
90 // TODO(dborowitz): Show the error message to the user.
91 res.sendError(SC_FORBIDDEN);
92 return;
93 } catch (IOException err) {
94 String name = urls.getHostName(req);
95 log.warn("Cannot scan repositories" + (name != null ? "for " + name : ""), err);
96 res.sendError(SC_SERVICE_UNAVAILABLE);
97 return;
98 }
99
100 switch (format) {
101 case HTML:
102 case DEFAULT:
103 default:
104 displayHtml(req, res, descs);
105 break;
106
107 case TEXT:
108 displayText(req, res, branches, descs);
109 break;
110
111 case JSON:
112 displayJson(req, res, descs);
113 break;
114 }
115 }
116
117 private SoyMapData toSoyMapData(RepositoryDescription desc, GitilesView view) {
118 return new SoyMapData(
119 "name", desc.name,
120 "description", Strings.nullToEmpty(desc.description),
121 "url", GitilesView.repositoryIndex()
122 .copyFrom(view)
123 .setRepositoryName(desc.name)
124 .toUrl());
125 }
126
127 private void displayHtml(HttpServletRequest req, HttpServletResponse res,
128 Map<String, RepositoryDescription> descs) throws IOException {
129 SoyListData repos = new SoyListData();
130 for (RepositoryDescription desc : descs.values()) {
131 repos.add(toSoyMapData(desc, ViewFilter.getView(req)));
132 }
133
134 render(req, res, "gitiles.hostIndex", ImmutableMap.of(
135 "hostName", urls.getHostName(req),
136 "baseUrl", urls.getBaseGitUrl(req),
137 "repositories", repos));
138 }
139
140 private void displayText(HttpServletRequest req, HttpServletResponse res,
141 Set<String> branches, Map<String, RepositoryDescription> descs) throws IOException {
142 res.setContentType(TEXT.getMimeType());
143 res.setCharacterEncoding("UTF-8");
144 res.setHeader(HttpHeaders.CONTENT_DISPOSITION, "attachment");
145 setNotCacheable(res);
146
147 PrintWriter writer = res.getWriter();
148 for (RepositoryDescription repo : descs.values()) {
149 for (String name : branches) {
150 String ref = repo.branches.get(name);
151 if (ref == null) {
152 // Print stub (forty '-' symbols)
153 ref = "----------------------------------------";
154 }
155 writer.print(ref);
156 writer.print(' ');
157 }
158 writer.print(GitilesUrls.NAME_ESCAPER.apply(repo.name));
159 writer.print('\n');
160 }
161 writer.flush();
162 writer.close();
163 }
164
165 private void displayJson(HttpServletRequest req, HttpServletResponse res,
166 Map<String, RepositoryDescription> descs) throws IOException {
167 res.setContentType(JSON.getMimeType());
168 res.setCharacterEncoding("UTF-8");
169 res.setHeader(HttpHeaders.CONTENT_DISPOSITION, "attachment");
170 setNotCacheable(res);
171
172 PrintWriter writer = res.getWriter();
173 new GsonBuilder()
174 .setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES)
175 .setPrettyPrinting()
176 .generateNonExecutableJson()
177 .create()
178 .toJson(descs,
179 new TypeToken<Map<String, RepositoryDescription>>() {}.getType(),
180 writer);
181 writer.print('\n');
182 writer.close();
183 }
184
185 private static Set<String> parseShowBranch(HttpServletRequest req) {
186 // Roughly match Gerrit Code Review's /projects/ API by supporting
187 // both show-branch and b as query parameters.
188 Set<String> branches = Sets.newLinkedHashSet();
189 String[] values = req.getParameterValues("show-branch");
190 if (values != null) {
191 branches.addAll(Arrays.asList(values));
192 }
193 values = req.getParameterValues("b");
194 if (values != null) {
195 branches.addAll(Arrays.asList(values));
196 }
197 return branches;
198 }
199}