James Moger
2015-11-22 ed552ba47c02779c270ffd62841d6d1048dade70
commit | author | age
a1f27e 1 /*
JM 2  * Copyright 2013 gitblit.com.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.gitblit.manager;
17
18 import java.io.File;
19 import java.text.MessageFormat;
20 import java.util.ArrayList;
21 import java.util.Collections;
22 import java.util.Date;
23 import java.util.HashMap;
24 import java.util.LinkedHashMap;
25 import java.util.List;
26 import java.util.Map;
27 import java.util.TreeMap;
28 import java.util.concurrent.ConcurrentHashMap;
29
30 import org.eclipse.jgit.storage.file.FileBasedConfig;
31 import org.eclipse.jgit.util.FS;
32 import org.slf4j.Logger;
33 import org.slf4j.LoggerFactory;
34
35 import com.gitblit.IStoredSettings;
36 import com.gitblit.Keys;
37 import com.gitblit.models.ProjectModel;
38 import com.gitblit.models.RepositoryModel;
39 import com.gitblit.models.UserModel;
40 import com.gitblit.utils.DeepCopier;
41 import com.gitblit.utils.ModelUtils;
42 import com.gitblit.utils.ObjectCache;
43 import com.gitblit.utils.StringUtils;
f9980e 44 import com.google.inject.Inject;
JM 45 import com.google.inject.Singleton;
a1f27e 46
JM 47 /**
48  * Project manager handles project-related functions.
49  *
50  * @author James Moger
51  *
52  */
f9980e 53 @Singleton
a1f27e 54 public class ProjectManager implements IProjectManager {
JM 55
56     private final Logger logger = LoggerFactory.getLogger(getClass());
57
58     private final Map<String, ProjectModel> projectCache = new ConcurrentHashMap<String, ProjectModel>();
59
60     private final ObjectCache<String> projectMarkdownCache = new ObjectCache<String>();
61
62     private final ObjectCache<String> projectRepositoriesMarkdownCache = new ObjectCache<String>();
63
64     private final IStoredSettings settings;
65
66     private final IRuntimeManager runtimeManager;
67
68     private final IUserManager userManager;
69
70     private final IRepositoryManager repositoryManager;
71
72     private FileBasedConfig projectConfigs;
73
1b34b0 74     @Inject
a1f27e 75     public ProjectManager(
JM 76             IRuntimeManager runtimeManager,
77             IUserManager userManager,
78             IRepositoryManager repositoryManager) {
79
80         this.settings = runtimeManager.getSettings();
81         this.runtimeManager = runtimeManager;
82         this.userManager = userManager;
83         this.repositoryManager = repositoryManager;
84     }
85
86     @Override
269c50 87     public ProjectManager start() {
a1f27e 88         // load and cache the project metadata
JM 89         projectConfigs = new FileBasedConfig(runtimeManager.getFileOrFolder(Keys.web.projectsFile, "${baseFolder}/projects.conf"), FS.detect());
90         getProjectConfigs();
91
92         return this;
93     }
94
95     @Override
269c50 96     public ProjectManager stop() {
a1f27e 97         return this;
JM 98     }
99
100     private void reloadProjectMarkdown(ProjectModel project) {
101         // project markdown
102         File pmkd = new File(repositoryManager.getRepositoriesFolder(), (project.isRoot ? "" : project.name) + "/project.mkd");
103         if (pmkd.exists()) {
104             Date lm = new Date(pmkd.lastModified());
105             if (!projectMarkdownCache.hasCurrent(project.name, lm)) {
106                 String mkd = com.gitblit.utils.FileUtils.readContent(pmkd,  "\n");
107                 projectMarkdownCache.updateObject(project.name, lm, mkd);
108             }
109             project.projectMarkdown = projectMarkdownCache.getObject(project.name);
110         }
111
112         // project repositories markdown
113         File rmkd = new File(repositoryManager.getRepositoriesFolder(), (project.isRoot ? "" : project.name) + "/repositories.mkd");
114         if (rmkd.exists()) {
115             Date lm = new Date(rmkd.lastModified());
116             if (!projectRepositoriesMarkdownCache.hasCurrent(project.name, lm)) {
117                 String mkd = com.gitblit.utils.FileUtils.readContent(rmkd,  "\n");
118                 projectRepositoriesMarkdownCache.updateObject(project.name, lm, mkd);
119             }
120             project.repositoriesMarkdown = projectRepositoriesMarkdownCache.getObject(project.name);
121         }
122     }
123
124
125     /**
126      * Returns the map of project config.  This map is cached and reloaded if
127      * the underlying projects.conf file changes.
128      *
129      * @return project config map
130      */
131     private Map<String, ProjectModel> getProjectConfigs() {
132         if (projectCache.isEmpty() || projectConfigs.isOutdated()) {
133
134             try {
135                 projectConfigs.load();
136             } catch (Exception e) {
137             }
138
139             // project configs
140             String rootName = settings.getString(Keys.web.repositoryRootGroupName, "main");
141             ProjectModel rootProject = new ProjectModel(rootName, true);
142
143             Map<String, ProjectModel> configs = new HashMap<String, ProjectModel>();
144             // cache the root project under its alias and an empty path
145             configs.put("", rootProject);
146             configs.put(rootProject.name.toLowerCase(), rootProject);
147
148             for (String name : projectConfigs.getSubsections("project")) {
149                 ProjectModel project;
150                 if (name.equalsIgnoreCase(rootName)) {
151                     project = rootProject;
152                 } else {
153                     project = new ProjectModel(name);
154                 }
155                 project.title = projectConfigs.getString("project", name, "title");
156                 project.description = projectConfigs.getString("project", name, "description");
157
158                 reloadProjectMarkdown(project);
159
160                 configs.put(name.toLowerCase(), project);
161             }
162             projectCache.clear();
163             projectCache.putAll(configs);
164         }
165         return projectCache;
166     }
167
168     /**
169      * Returns a list of project models for the user.
170      *
171      * @param user
172      * @param includeUsers
173      * @return list of projects that are accessible to the user
174      */
175     @Override
176     public List<ProjectModel> getProjectModels(UserModel user, boolean includeUsers) {
177         Map<String, ProjectModel> configs = getProjectConfigs();
178
179         // per-user project lists, this accounts for security and visibility
180         Map<String, ProjectModel> map = new TreeMap<String, ProjectModel>();
181         // root project
182         map.put("", configs.get(""));
183
184         for (RepositoryModel model : repositoryManager.getRepositoryModels(user)) {
3983a1 185             String projectPath = StringUtils.getRootPath(model.name);
JM 186             String projectKey = projectPath.toLowerCase();
187             if (!map.containsKey(projectKey)) {
a1f27e 188                 ProjectModel project;
3983a1 189                 if (configs.containsKey(projectKey)) {
a1f27e 190                     // clone the project model because it's repository list will
JM 191                     // be tailored for the requesting user
3983a1 192                     project = DeepCopier.copy(configs.get(projectKey));
a1f27e 193                 } else {
3983a1 194                     project = new ProjectModel(projectPath);
a1f27e 195                 }
3983a1 196                 map.put(projectKey, project);
a1f27e 197             }
3983a1 198             map.get(projectKey).addRepository(model);
a1f27e 199         }
JM 200
201         // sort projects, root project first
202         List<ProjectModel> projects;
203         if (includeUsers) {
204             // all projects
205             projects = new ArrayList<ProjectModel>(map.values());
206             Collections.sort(projects);
207             projects.remove(map.get(""));
208             projects.add(0, map.get(""));
209         } else {
210             // all non-user projects
211             projects = new ArrayList<ProjectModel>();
212             ProjectModel root = map.remove("");
213             for (ProjectModel model : map.values()) {
214                 if (!model.isUserProject()) {
215                     projects.add(model);
216                 }
217             }
218             Collections.sort(projects);
219             projects.add(0, root);
220         }
221         return projects;
222     }
223
224     /**
225      * Returns the project model for the specified user.
226      *
227      * @param name
228      * @param user
229      * @return a project model, or null if it does not exist
230      */
231     @Override
232     public ProjectModel getProjectModel(String name, UserModel user) {
233         for (ProjectModel project : getProjectModels(user, true)) {
234             if (project.name.equalsIgnoreCase(name)) {
235                 return project;
236             }
237         }
238         return null;
239     }
240
241     /**
242      * Returns a project model for the Gitblit/system user.
243      *
244      * @param name a project name
245      * @return a project model or null if the project does not exist
246      */
247     @Override
248     public ProjectModel getProjectModel(String name) {
249         Map<String, ProjectModel> configs = getProjectConfigs();
250         ProjectModel project = configs.get(name.toLowerCase());
251         if (project == null) {
252             project = new ProjectModel(name);
253             if (ModelUtils.isPersonalRepository(name)) {
254                 UserModel user = userManager.getUserModel(ModelUtils.getUserNameFromRepoPath(name));
255                 if (user != null) {
256                     project.title = user.getDisplayName();
257                     project.description = "personal repositories";
258                 }
259             }
260         } else {
261             // clone the object
262             project = DeepCopier.copy(project);
263         }
264         if (StringUtils.isEmpty(name)) {
265             // get root repositories
266             for (String repository : repositoryManager.getRepositoryList()) {
267                 if (repository.indexOf('/') == -1) {
268                     project.addRepository(repository);
269                 }
270             }
271         } else {
272             // get repositories in subfolder
273             String folder = name.toLowerCase() + "/";
274             for (String repository : repositoryManager.getRepositoryList()) {
275                 if (repository.toLowerCase().startsWith(folder)) {
276                     project.addRepository(repository);
277                 }
278             }
279         }
280         if (project.repositories.size() == 0) {
281             // no repositories == no project
282             return null;
283         }
284
285         reloadProjectMarkdown(project);
286         return project;
287     }
288
289     /**
290      * Returns the list of project models that are referenced by the supplied
291      * repository model    list.  This is an alternative method exists to ensure
292      * Gitblit does not call getRepositoryModels(UserModel) twice in a request.
293      *
294      * @param repositoryModels
295      * @param includeUsers
296      * @return a list of project models
297      */
298     @Override
299     public List<ProjectModel> getProjectModels(List<RepositoryModel> repositoryModels, boolean includeUsers) {
300         Map<String, ProjectModel> projects = new LinkedHashMap<String, ProjectModel>();
301         for (RepositoryModel repository : repositoryModels) {
302             if (!includeUsers && repository.isPersonalRepository()) {
303                 // exclude personal repositories
304                 continue;
305             }
306             if (!projects.containsKey(repository.projectPath)) {
307                 ProjectModel project = getProjectModel(repository.projectPath);
308                 if (project == null) {
309                     logger.warn(MessageFormat.format("excluding project \"{0}\" from project list because it is empty!",
310                             repository.projectPath));
311                     continue;
312                 }
313                 projects.put(repository.projectPath, project);
314                 // clear the repo list in the project because that is the system
315                 // list, not the user-accessible list and start building the
316                 // user-accessible list
317                 project.repositories.clear();
318                 project.repositories.add(repository.name);
319                 project.lastChange = repository.lastChange;
320             } else {
321                 // update the user-accessible list
322                 // this is used for repository count
323                 ProjectModel project = projects.get(repository.projectPath);
324                 project.repositories.add(repository.name);
325                 if (project.lastChange.before(repository.lastChange)) {
326                     project.lastChange = repository.lastChange;
327                 }
328             }
329         }
330         return new ArrayList<ProjectModel>(projects.values());
331     }
332 }