You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

SyndicationServlet.java 13KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400
  1. /*
  2. * Copyright 2011 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.servlet;
  17. import java.text.MessageFormat;
  18. import java.util.ArrayList;
  19. import java.util.Arrays;
  20. import java.util.Collections;
  21. import java.util.List;
  22. import java.util.Map;
  23. import com.google.inject.Inject;
  24. import com.google.inject.Singleton;
  25. import javax.servlet.http.HttpServlet;
  26. import org.eclipse.jgit.lib.ObjectId;
  27. import org.eclipse.jgit.lib.Repository;
  28. import org.eclipse.jgit.revwalk.RevCommit;
  29. import org.slf4j.Logger;
  30. import org.slf4j.LoggerFactory;
  31. import com.gitblit.Constants;
  32. import com.gitblit.IStoredSettings;
  33. import com.gitblit.Keys;
  34. import com.gitblit.manager.IProjectManager;
  35. import com.gitblit.manager.IRepositoryManager;
  36. import com.gitblit.models.FeedEntryModel;
  37. import com.gitblit.models.ProjectModel;
  38. import com.gitblit.models.RefModel;
  39. import com.gitblit.models.RepositoryModel;
  40. import com.gitblit.models.UserModel;
  41. import com.gitblit.servlet.AuthenticationFilter.AuthenticatedRequest;
  42. import com.gitblit.utils.BugtraqProcessor;
  43. import com.gitblit.utils.HttpUtils;
  44. import com.gitblit.utils.JGitUtils;
  45. import com.gitblit.utils.StringUtils;
  46. import com.gitblit.utils.SyndicationUtils;
  47. /**
  48. * SyndicationServlet generates RSS 2.0 feeds and feed links.
  49. *
  50. * Access to this servlet is protected by the SyndicationFilter.
  51. *
  52. * @author James Moger
  53. *
  54. */
  55. @Singleton
  56. public class SyndicationServlet extends HttpServlet {
  57. private static final long serialVersionUID = 1L;
  58. private transient Logger logger = LoggerFactory.getLogger(SyndicationServlet.class);
  59. private IStoredSettings settings;
  60. private IRepositoryManager repositoryManager;
  61. private IProjectManager projectManager;
  62. @Inject
  63. public SyndicationServlet(
  64. IStoredSettings settings,
  65. IRepositoryManager repositoryManager,
  66. IProjectManager projectManager) {
  67. this.settings = settings;
  68. this.repositoryManager = repositoryManager;
  69. this.projectManager = projectManager;
  70. }
  71. /**
  72. * Create a feed link for the specified repository and branch/tag/commit id.
  73. *
  74. * @param baseURL
  75. * @param repository
  76. * the repository name
  77. * @param objectId
  78. * the branch, tag, or first commit for the feed
  79. * @param length
  80. * the number of commits to include in the feed
  81. * @return an RSS feed url
  82. */
  83. public static String asLink(String baseURL, String repository, String objectId, int length) {
  84. if (baseURL.length() > 0 && baseURL.charAt(baseURL.length() - 1) == '/') {
  85. baseURL = baseURL.substring(0, baseURL.length() - 1);
  86. }
  87. StringBuilder url = new StringBuilder();
  88. url.append(baseURL);
  89. url.append(Constants.SYNDICATION_PATH);
  90. url.append(repository);
  91. if (!StringUtils.isEmpty(objectId) || length > 0) {
  92. StringBuilder parameters = new StringBuilder("?");
  93. if (StringUtils.isEmpty(objectId)) {
  94. parameters.append("l=");
  95. parameters.append(length);
  96. } else {
  97. parameters.append("h=");
  98. parameters.append(objectId);
  99. if (length > 0) {
  100. parameters.append("&l=");
  101. parameters.append(length);
  102. }
  103. }
  104. url.append(parameters);
  105. }
  106. return url.toString();
  107. }
  108. /**
  109. * Determines the appropriate title for a feed.
  110. *
  111. * @param repository
  112. * @param objectId
  113. * @return title of the feed
  114. */
  115. public static String getTitle(String repository, String objectId) {
  116. String id = objectId;
  117. if (!StringUtils.isEmpty(id)) {
  118. if (id.startsWith(org.eclipse.jgit.lib.Constants.R_HEADS)) {
  119. id = id.substring(org.eclipse.jgit.lib.Constants.R_HEADS.length());
  120. } else if (id.startsWith(org.eclipse.jgit.lib.Constants.R_REMOTES)) {
  121. id = id.substring(org.eclipse.jgit.lib.Constants.R_REMOTES.length());
  122. } else if (id.startsWith(org.eclipse.jgit.lib.Constants.R_TAGS)) {
  123. id = id.substring(org.eclipse.jgit.lib.Constants.R_TAGS.length());
  124. }
  125. }
  126. return MessageFormat.format("{0} ({1})", repository, id);
  127. }
  128. /**
  129. * Generates the feed content.
  130. *
  131. * @param request
  132. * @param response
  133. * @throws javax.servlet.ServletException
  134. * @throws java.io.IOException
  135. */
  136. private void processRequest(javax.servlet.http.HttpServletRequest request,
  137. javax.servlet.http.HttpServletResponse response) throws javax.servlet.ServletException,
  138. java.io.IOException {
  139. String servletUrl = request.getContextPath() + request.getServletPath();
  140. String url = request.getRequestURI().substring(servletUrl.length());
  141. if (url.charAt(0) == '/' && url.length() > 1) {
  142. url = url.substring(1);
  143. }
  144. String repositoryName = url;
  145. String objectId = request.getParameter("h");
  146. String l = request.getParameter("l");
  147. String page = request.getParameter("pg");
  148. String searchString = request.getParameter("s");
  149. Constants.SearchType searchType = Constants.SearchType.COMMIT;
  150. if (!StringUtils.isEmpty(request.getParameter("st"))) {
  151. Constants.SearchType type = Constants.SearchType.forName(request.getParameter("st"));
  152. if (type != null) {
  153. searchType = type;
  154. }
  155. }
  156. Constants.FeedObjectType objectType = Constants.FeedObjectType.COMMIT;
  157. if (!StringUtils.isEmpty(request.getParameter("ot"))) {
  158. Constants.FeedObjectType type = Constants.FeedObjectType.forName(request.getParameter("ot"));
  159. if (type != null) {
  160. objectType = type;
  161. }
  162. }
  163. int length = settings.getInteger(Keys.web.syndicationEntries, 25);
  164. if (StringUtils.isEmpty(objectId)) {
  165. objectId = org.eclipse.jgit.lib.Constants.HEAD;
  166. }
  167. if (!StringUtils.isEmpty(l)) {
  168. try {
  169. length = Integer.parseInt(l);
  170. } catch (NumberFormatException x) {
  171. }
  172. }
  173. int offset = 0;
  174. if (!StringUtils.isEmpty(page)) {
  175. try {
  176. offset = length * Integer.parseInt(page);
  177. } catch (NumberFormatException x) {
  178. }
  179. }
  180. response.setContentType("application/rss+xml; charset=UTF-8");
  181. boolean isProjectFeed = false;
  182. String feedName = null;
  183. String feedTitle = null;
  184. String feedDescription = null;
  185. List<String> repositories = null;
  186. if (repositoryName.indexOf('/') == -1 && !repositoryName.toLowerCase().endsWith(".git")) {
  187. // try to find a project
  188. UserModel user = null;
  189. if (request instanceof AuthenticatedRequest) {
  190. user = ((AuthenticatedRequest) request).getUser();
  191. }
  192. ProjectModel project = projectManager.getProjectModel(repositoryName, user);
  193. if (project != null) {
  194. isProjectFeed = true;
  195. repositories = new ArrayList<String>(project.repositories);
  196. // project feed
  197. feedName = project.name;
  198. feedTitle = project.title;
  199. feedDescription = project.description;
  200. }
  201. }
  202. if (repositories == null) {
  203. // could not find project, assume this is a repository
  204. repositories = Arrays.asList(repositoryName);
  205. }
  206. boolean mountParameters = settings.getBoolean(Keys.web.mountParameters, true);
  207. String gitblitUrl = settings.getString(Keys.web.canonicalUrl, null);
  208. if (StringUtils.isEmpty(gitblitUrl)) {
  209. gitblitUrl = HttpUtils.getGitblitURL(request);
  210. }
  211. char fsc = settings.getChar(Keys.web.forwardSlashCharacter, '/');
  212. List<FeedEntryModel> entries = new ArrayList<FeedEntryModel>();
  213. for (String name : repositories) {
  214. Repository repository = repositoryManager.getRepository(name);
  215. RepositoryModel model = repositoryManager.getRepositoryModel(name);
  216. if (repository == null) {
  217. if (model.isCollectingGarbage) {
  218. logger.warn(MessageFormat.format("Temporarily excluding {0} from feed, busy collecting garbage", name));
  219. }
  220. continue;
  221. }
  222. if (!isProjectFeed) {
  223. // single-repository feed
  224. feedName = model.name;
  225. feedTitle = model.name;
  226. feedDescription = model.description;
  227. }
  228. if (objectType == Constants.FeedObjectType.TAG) {
  229. String urlPattern;
  230. if (mountParameters) {
  231. // mounted parameters
  232. urlPattern = "{0}/tag/{1}/{2}";
  233. } else {
  234. // parameterized parameters
  235. urlPattern = "{0}/tag/?r={1}&h={2}";
  236. }
  237. List<RefModel> tags = JGitUtils.getTags(repository, false, length, offset);
  238. for (RefModel tag : tags) {
  239. FeedEntryModel entry = new FeedEntryModel();
  240. entry.title = tag.getName();
  241. entry.author = tag.getAuthorIdent().getName();
  242. entry.link = MessageFormat.format(urlPattern, gitblitUrl,
  243. StringUtils.encodeURL(model.name.replace('/', fsc)), tag.getObjectId().getName());
  244. entry.published = tag.getDate();
  245. entry.contentType = "text/html";
  246. entry.content = tag.getFullMessage();
  247. entry.repository = model.name;
  248. entry.branch = objectId;
  249. entry.tags = new ArrayList<String>();
  250. // add tag id and referenced commit id
  251. entry.tags.add("tag:" + tag.getObjectId().getName());
  252. entry.tags.add("commit:" + tag.getReferencedObjectId().getName());
  253. entries.add(entry);
  254. }
  255. } else {
  256. String urlPattern;
  257. if (mountParameters) {
  258. // mounted parameters
  259. urlPattern = "{0}/commit/{1}/{2}";
  260. } else {
  261. // parameterized parameters
  262. urlPattern = "{0}/commit/?r={1}&h={2}";
  263. }
  264. List<RevCommit> commits;
  265. if (StringUtils.isEmpty(searchString)) {
  266. // standard log/history lookup
  267. commits = JGitUtils.getRevLog(repository, objectId, offset, length);
  268. } else {
  269. // repository search
  270. commits = JGitUtils.searchRevlogs(repository, objectId, searchString, searchType,
  271. offset, length);
  272. }
  273. Map<ObjectId, List<RefModel>> allRefs = JGitUtils.getAllRefs(repository, model.showRemoteBranches);
  274. BugtraqProcessor processor = new BugtraqProcessor(settings);
  275. // convert RevCommit to SyndicatedEntryModel
  276. for (RevCommit commit : commits) {
  277. FeedEntryModel entry = new FeedEntryModel();
  278. entry.title = commit.getShortMessage();
  279. entry.author = commit.getAuthorIdent().getName();
  280. entry.link = MessageFormat.format(urlPattern, gitblitUrl,
  281. StringUtils.encodeURL(model.name.replace('/', fsc)), commit.getName());
  282. entry.published = commit.getCommitterIdent().getWhen();
  283. entry.contentType = "text/html";
  284. String message = processor.processCommitMessage(repository, model, commit.getFullMessage());
  285. entry.content = message;
  286. entry.repository = model.name;
  287. entry.branch = objectId;
  288. entry.tags = new ArrayList<String>();
  289. // add commit id and parent commit ids
  290. entry.tags.add("commit:" + commit.getName());
  291. for (RevCommit parent : commit.getParents()) {
  292. entry.tags.add("parent:" + parent.getName());
  293. }
  294. // add refs to tabs list
  295. List<RefModel> refs = allRefs.get(commit.getId());
  296. if (refs != null && refs.size() > 0) {
  297. for (RefModel ref : refs) {
  298. entry.tags.add("ref:" + ref.getName());
  299. }
  300. }
  301. entries.add(entry);
  302. }
  303. }
  304. }
  305. // sort & truncate the feed
  306. Collections.sort(entries);
  307. if (entries.size() > length) {
  308. // clip the list
  309. entries = entries.subList(0, length);
  310. }
  311. String feedLink;
  312. if (isProjectFeed) {
  313. // project feed
  314. if (mountParameters) {
  315. // mounted url
  316. feedLink = MessageFormat.format("{0}/project/{1}", gitblitUrl,
  317. StringUtils.encodeURL(feedName));
  318. } else {
  319. // parameterized url
  320. feedLink = MessageFormat.format("{0}/project/?p={1}", gitblitUrl,
  321. StringUtils.encodeURL(feedName));
  322. }
  323. } else {
  324. // repository feed
  325. if (mountParameters) {
  326. // mounted url
  327. feedLink = MessageFormat.format("{0}/summary/{1}", gitblitUrl,
  328. StringUtils.encodeURL(feedName));
  329. } else {
  330. // parameterized url
  331. feedLink = MessageFormat.format("{0}/summary/?r={1}", gitblitUrl,
  332. StringUtils.encodeURL(feedName));
  333. }
  334. }
  335. try {
  336. SyndicationUtils.toRSS(gitblitUrl, feedLink, getTitle(feedTitle, objectId),
  337. feedDescription, entries, response.getOutputStream());
  338. } catch (Exception e) {
  339. logger.error("An error occurred during feed generation", e);
  340. }
  341. }
  342. @Override
  343. protected void doPost(javax.servlet.http.HttpServletRequest request,
  344. javax.servlet.http.HttpServletResponse response) throws javax.servlet.ServletException,
  345. java.io.IOException {
  346. processRequest(request, response);
  347. }
  348. @Override
  349. protected void doGet(javax.servlet.http.HttpServletRequest request,
  350. javax.servlet.http.HttpServletResponse response) throws javax.servlet.ServletException,
  351. java.io.IOException {
  352. processRequest(request, response);
  353. }
  354. }