Line data Source code
1 : // Copyright (C) 2012 The Android Open Source Project
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 :
15 : package com.google.gerrit.server.restapi.project;
16 :
17 : import static com.google.gerrit.entities.RefNames.REFS_DASHBOARDS;
18 :
19 : import com.google.common.base.Joiner;
20 : import com.google.common.base.MoreObjects;
21 : import com.google.common.base.Splitter;
22 : import com.google.common.base.Strings;
23 : import com.google.common.collect.Lists;
24 : import com.google.gerrit.common.Nullable;
25 : import com.google.gerrit.entities.Project;
26 : import com.google.gerrit.extensions.api.projects.DashboardInfo;
27 : import com.google.gerrit.extensions.api.projects.DashboardSectionInfo;
28 : import com.google.gerrit.extensions.registration.DynamicMap;
29 : import com.google.gerrit.extensions.restapi.AuthException;
30 : import com.google.gerrit.extensions.restapi.ChildCollection;
31 : import com.google.gerrit.extensions.restapi.IdString;
32 : import com.google.gerrit.extensions.restapi.ResourceConflictException;
33 : import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
34 : import com.google.gerrit.extensions.restapi.RestApiException;
35 : import com.google.gerrit.extensions.restapi.RestView;
36 : import com.google.gerrit.extensions.restapi.Url;
37 : import com.google.gerrit.server.CurrentUser;
38 : import com.google.gerrit.server.UrlEncoded;
39 : import com.google.gerrit.server.git.GitRepositoryManager;
40 : import com.google.gerrit.server.permissions.PermissionBackend;
41 : import com.google.gerrit.server.permissions.PermissionBackendException;
42 : import com.google.gerrit.server.permissions.RefPermission;
43 : import com.google.gerrit.server.project.DashboardResource;
44 : import com.google.gerrit.server.project.ProjectResource;
45 : import com.google.gerrit.server.project.ProjectState;
46 : import com.google.inject.Inject;
47 : import com.google.inject.Provider;
48 : import com.google.inject.Singleton;
49 : import java.io.IOException;
50 : import java.util.List;
51 : import org.eclipse.jgit.errors.AmbiguousObjectException;
52 : import org.eclipse.jgit.errors.ConfigInvalidException;
53 : import org.eclipse.jgit.errors.IncorrectObjectTypeException;
54 : import org.eclipse.jgit.errors.RepositoryNotFoundException;
55 : import org.eclipse.jgit.lib.BlobBasedConfig;
56 : import org.eclipse.jgit.lib.Config;
57 : import org.eclipse.jgit.lib.ObjectId;
58 : import org.eclipse.jgit.lib.Repository;
59 :
60 : @Singleton
61 : public class DashboardsCollection implements ChildCollection<ProjectResource, DashboardResource> {
62 : public static final String DEFAULT_DASHBOARD_NAME = "default";
63 :
64 : private final GitRepositoryManager gitManager;
65 : private final DynamicMap<RestView<DashboardResource>> views;
66 : private final Provider<ListDashboards> list;
67 : private final PermissionBackend permissionBackend;
68 :
69 : @Inject
70 : DashboardsCollection(
71 : GitRepositoryManager gitManager,
72 : DynamicMap<RestView<DashboardResource>> views,
73 : Provider<ListDashboards> list,
74 144 : PermissionBackend permissionBackend) {
75 144 : this.gitManager = gitManager;
76 144 : this.views = views;
77 144 : this.list = list;
78 144 : this.permissionBackend = permissionBackend;
79 144 : }
80 :
81 : public static boolean isDefaultDashboard(@Nullable String id) {
82 2 : return DEFAULT_DASHBOARD_NAME.equals(id);
83 : }
84 :
85 : public static boolean isDefaultDashboard(@Nullable IdString id) {
86 2 : return id != null && isDefaultDashboard(id.toString());
87 : }
88 :
89 : @Override
90 : public RestView<ProjectResource> list() throws ResourceNotFoundException {
91 1 : return list.get();
92 : }
93 :
94 : @Override
95 : public DashboardResource parse(ProjectResource parent, IdString id)
96 : throws RestApiException, IOException, ConfigInvalidException, PermissionBackendException {
97 2 : parent.getProjectState().checkStatePermitsRead();
98 2 : if (isDefaultDashboard(id)) {
99 2 : return DashboardResource.projectDefault(parent.getProjectState(), parent.getUser());
100 : }
101 :
102 : DashboardInfo info;
103 : try {
104 2 : info = newDashboardInfo(id.get());
105 0 : } catch (InvalidDashboardId e) {
106 0 : throw new ResourceNotFoundException(id, e);
107 2 : }
108 :
109 2 : for (ProjectState ps : parent.getProjectState().tree()) {
110 : try {
111 2 : return parse(ps, parent.getProjectState(), parent.getUser(), info);
112 0 : } catch (AmbiguousObjectException | ConfigInvalidException | IncorrectObjectTypeException e) {
113 0 : throw new ResourceNotFoundException(id, e);
114 1 : } catch (ResourceNotFoundException e) {
115 1 : continue;
116 : }
117 : }
118 1 : throw new ResourceNotFoundException(id);
119 : }
120 :
121 : public static String normalizeDashboardRef(String ref) {
122 2 : if (!ref.startsWith(REFS_DASHBOARDS)) {
123 1 : return REFS_DASHBOARDS + ref;
124 : }
125 1 : return ref;
126 : }
127 :
128 : private DashboardResource parse(
129 : ProjectState parent, ProjectState current, CurrentUser user, DashboardInfo info)
130 : throws ResourceNotFoundException, IOException, AmbiguousObjectException,
131 : IncorrectObjectTypeException, ConfigInvalidException, PermissionBackendException,
132 : ResourceConflictException {
133 2 : String ref = normalizeDashboardRef(info.ref);
134 : try {
135 2 : permissionBackend.user(user).project(parent.getNameKey()).ref(ref).check(RefPermission.READ);
136 0 : } catch (AuthException e) {
137 : // Don't leak the project's existence
138 0 : throw new ResourceNotFoundException(info.id, e);
139 2 : }
140 2 : if (!Repository.isValidRefName(ref)) {
141 0 : throw new ResourceNotFoundException(info.id);
142 : }
143 :
144 2 : current.checkStatePermitsRead();
145 :
146 2 : try (Repository git = gitManager.openRepository(parent.getNameKey())) {
147 2 : ObjectId objId = git.resolve(ref + ":" + info.path);
148 2 : if (objId == null) {
149 1 : throw new ResourceNotFoundException(info.id);
150 : }
151 2 : BlobBasedConfig cfg = new BlobBasedConfig(null, git, objId);
152 2 : return new DashboardResource(current, user, ref, info.path, cfg, false);
153 0 : } catch (RepositoryNotFoundException e) {
154 0 : throw new ResourceNotFoundException(info.id, e);
155 : }
156 : }
157 :
158 : @Override
159 : public DynamicMap<RestView<DashboardResource>> views() {
160 1 : return views;
161 : }
162 :
163 : public static DashboardInfo newDashboardInfo(String ref, String path) {
164 2 : DashboardInfo info = new DashboardInfo();
165 2 : info.ref = ref;
166 2 : info.path = path;
167 2 : info.id = Joiner.on(':').join(Url.encode(ref), Url.encode(path));
168 2 : return info;
169 : }
170 :
171 : public static class InvalidDashboardId extends Exception {
172 : private static final long serialVersionUID = 1L;
173 :
174 : public InvalidDashboardId(String id) {
175 0 : super(id);
176 0 : }
177 : }
178 :
179 : static DashboardInfo newDashboardInfo(String id) throws InvalidDashboardId {
180 2 : DashboardInfo info = new DashboardInfo();
181 2 : List<String> parts = Lists.newArrayList(Splitter.on(':').limit(2).split(id));
182 2 : if (parts.size() != 2) {
183 0 : throw new InvalidDashboardId(id);
184 : }
185 2 : info.id = id;
186 2 : info.ref = parts.get(0);
187 2 : info.path = parts.get(1);
188 2 : return info;
189 : }
190 :
191 : static DashboardInfo parse(
192 : Project definingProject,
193 : String refName,
194 : String path,
195 : Config config,
196 : String project,
197 : boolean setDefault) {
198 2 : DashboardInfo info = newDashboardInfo(refName, path);
199 2 : info.project = project;
200 2 : info.definingProject = definingProject.getName();
201 2 : String title = config.getString("dashboard", null, "title");
202 2 : info.title = replace(project, title == null ? info.path : title);
203 2 : info.description = replace(project, config.getString("dashboard", null, "description"));
204 2 : info.foreach = config.getString("dashboard", null, "foreach");
205 :
206 2 : if (setDefault) {
207 2 : String id = refName + ":" + path;
208 2 : info.isDefault = id.equals(defaultOf(definingProject)) ? true : null;
209 : }
210 :
211 2 : UrlEncoded u = new UrlEncoded("/dashboard/");
212 2 : u.put("title", MoreObjects.firstNonNull(info.title, info.path));
213 2 : if (info.foreach != null) {
214 1 : u.put("foreach", replace(project, info.foreach));
215 : }
216 2 : for (String name : config.getSubsections("section")) {
217 2 : DashboardSectionInfo s = new DashboardSectionInfo();
218 2 : s.name = name;
219 2 : s.query = config.getString("section", name, "query");
220 2 : u.put(s.name, replace(project, s.query));
221 2 : info.sections.add(s);
222 2 : }
223 2 : info.url = u.toString().replace("%3A", ":");
224 :
225 2 : return info;
226 : }
227 :
228 : @Nullable
229 : private static String replace(String project, String input) {
230 2 : return input == null ? input : input.replace("${project}", project);
231 : }
232 :
233 : private static String defaultOf(Project proj) {
234 2 : final String defaultId =
235 2 : MoreObjects.firstNonNull(
236 2 : proj.getLocalDefaultDashboard(), Strings.nullToEmpty(proj.getDefaultDashboard()));
237 2 : if (defaultId.startsWith(REFS_DASHBOARDS)) {
238 1 : return defaultId.substring(REFS_DASHBOARDS.length());
239 : }
240 1 : return defaultId;
241 : }
242 : }
|