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.

GitblitContext.java 16KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503
  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.io.File;
  18. import java.io.FileNotFoundException;
  19. import java.io.FileOutputStream;
  20. import java.io.IOException;
  21. import java.io.InputStream;
  22. import java.io.OutputStream;
  23. import java.text.MessageFormat;
  24. import java.util.ArrayList;
  25. import java.util.List;
  26. import javax.naming.Context;
  27. import javax.naming.InitialContext;
  28. import javax.naming.NamingException;
  29. import javax.servlet.ServletContext;
  30. import javax.servlet.ServletContextEvent;
  31. import org.slf4j.Logger;
  32. import org.slf4j.LoggerFactory;
  33. import com.gitblit.Constants;
  34. import com.gitblit.FileSettings;
  35. import com.gitblit.IStoredSettings;
  36. import com.gitblit.Keys;
  37. import com.gitblit.WebXmlSettings;
  38. import com.gitblit.extensions.LifeCycleListener;
  39. import com.gitblit.guice.CoreModule;
  40. import com.gitblit.guice.WebModule;
  41. import com.gitblit.manager.IAuthenticationManager;
  42. import com.gitblit.manager.IFederationManager;
  43. import com.gitblit.manager.IGitblit;
  44. import com.gitblit.manager.IManager;
  45. import com.gitblit.manager.INotificationManager;
  46. import com.gitblit.manager.IPluginManager;
  47. import com.gitblit.manager.IProjectManager;
  48. import com.gitblit.manager.IRepositoryManager;
  49. import com.gitblit.manager.IRuntimeManager;
  50. import com.gitblit.manager.IServicesManager;
  51. import com.gitblit.manager.IUserManager;
  52. import com.gitblit.tickets.ITicketService;
  53. import com.gitblit.transport.ssh.IPublicKeyManager;
  54. import com.gitblit.utils.ContainerUtils;
  55. import com.gitblit.utils.StringUtils;
  56. import com.google.inject.AbstractModule;
  57. import com.google.inject.Guice;
  58. import com.google.inject.Injector;
  59. import com.google.inject.servlet.GuiceServletContextListener;
  60. /**
  61. * This class is the main entry point for the entire webapp. It is a singleton
  62. * created manually by Gitblit GO or dynamically by the WAR/Express servlet
  63. * container. This class instantiates and starts all managers.
  64. *
  65. * Servlets and filters are injected which allows Gitblit to be completely
  66. * code-driven.
  67. *
  68. * @author James Moger
  69. *
  70. */
  71. public class GitblitContext extends GuiceServletContextListener {
  72. private static GitblitContext gitblit;
  73. protected final Logger logger = LoggerFactory.getLogger(getClass());
  74. private final List<IManager> managers = new ArrayList<IManager>();
  75. private final IStoredSettings goSettings;
  76. private final File goBaseFolder;
  77. /**
  78. * Construct a Gitblit WAR/Express context.
  79. */
  80. public GitblitContext() {
  81. this(null, null);
  82. }
  83. /**
  84. * Construct a Gitblit GO context.
  85. *
  86. * @param settings
  87. * @param baseFolder
  88. */
  89. public GitblitContext(IStoredSettings settings, File baseFolder) {
  90. this.goSettings = settings;
  91. this.goBaseFolder = baseFolder;
  92. gitblit = this;
  93. }
  94. /**
  95. * This method is only used for unit and integration testing.
  96. *
  97. * @param managerClass
  98. * @return a manager
  99. */
  100. @SuppressWarnings("unchecked")
  101. public static <X extends IManager> X getManager(Class<X> managerClass) {
  102. for (IManager manager : gitblit.managers) {
  103. if (managerClass.isAssignableFrom(manager.getClass())) {
  104. return (X) manager;
  105. }
  106. }
  107. return null;
  108. }
  109. @Override
  110. protected Injector getInjector() {
  111. return Guice.createInjector(getModules());
  112. }
  113. /**
  114. * Returns Gitblit's Guice injection modules.
  115. */
  116. protected AbstractModule [] getModules() {
  117. return new AbstractModule [] { new CoreModule(), new WebModule() };
  118. }
  119. /**
  120. * Configure Gitblit from the web.xml, if no configuration has already been
  121. * specified.
  122. *
  123. * @see ServletContextListener.contextInitialize(ServletContextEvent)
  124. */
  125. @Override
  126. public final void contextInitialized(ServletContextEvent contextEvent) {
  127. super.contextInitialized(contextEvent);
  128. ServletContext context = contextEvent.getServletContext();
  129. startCore(context);
  130. }
  131. /**
  132. * Prepare runtime settings and start all manager instances.
  133. */
  134. protected void startCore(ServletContext context) {
  135. Injector injector = (Injector) context.getAttribute(Injector.class.getName());
  136. // create the runtime settings object
  137. IStoredSettings runtimeSettings = injector.getInstance(IStoredSettings.class);
  138. final File baseFolder;
  139. if (goSettings != null) {
  140. // Gitblit GO
  141. baseFolder = configureGO(context, goSettings, goBaseFolder, runtimeSettings);
  142. } else {
  143. // servlet container
  144. WebXmlSettings webxmlSettings = new WebXmlSettings(context);
  145. String contextRealPath = context.getRealPath("/");
  146. File contextFolder = (contextRealPath != null) ? new File(contextRealPath) : null;
  147. // if the base folder dosen't match the default assume they don't want to use express,
  148. // this allows for other containers to customise the basefolder per context.
  149. String defaultBase = Constants.contextFolder$ + "/WEB-INF/data";
  150. String base = getBaseFolderPath(defaultBase);
  151. if (!StringUtils.isEmpty(System.getenv("OPENSHIFT_DATA_DIR")) && defaultBase.equals(base)) {
  152. // RedHat OpenShift
  153. baseFolder = configureExpress(context, webxmlSettings, contextFolder, runtimeSettings);
  154. } else {
  155. // standard WAR
  156. baseFolder = configureWAR(context, webxmlSettings, contextFolder, runtimeSettings);
  157. }
  158. // Test for Tomcat forward-slash/%2F issue and auto-adjust settings
  159. ContainerUtils.CVE_2007_0450.test(runtimeSettings);
  160. }
  161. // Manually configure IRuntimeManager
  162. logManager(IRuntimeManager.class);
  163. IRuntimeManager runtime = injector.getInstance(IRuntimeManager.class);
  164. runtime.setBaseFolder(baseFolder);
  165. runtime.getStatus().isGO = goSettings != null;
  166. runtime.getStatus().servletContainer = context.getServerInfo();
  167. runtime.start();
  168. managers.add(runtime);
  169. // create the plugin manager instance but do not start it
  170. loadManager(injector, IPluginManager.class);
  171. // start all other managers
  172. startManager(injector, INotificationManager.class);
  173. startManager(injector, IUserManager.class);
  174. startManager(injector, IAuthenticationManager.class);
  175. startManager(injector, IPublicKeyManager.class);
  176. startManager(injector, IRepositoryManager.class);
  177. startManager(injector, IProjectManager.class);
  178. startManager(injector, IFederationManager.class);
  179. startManager(injector, ITicketService.class);
  180. startManager(injector, IGitblit.class);
  181. startManager(injector, IServicesManager.class);
  182. // start the plugin manager last so that plugins can depend on
  183. // deterministic access to all other managers in their start() methods
  184. startManager(injector, IPluginManager.class);
  185. logger.info("");
  186. logger.info("All managers started.");
  187. logger.info("");
  188. IPluginManager pluginManager = injector.getInstance(IPluginManager.class);
  189. for (LifeCycleListener listener : pluginManager.getExtensions(LifeCycleListener.class)) {
  190. try {
  191. listener.onStartup();
  192. } catch (Throwable t) {
  193. logger.error(null, t);
  194. }
  195. }
  196. }
  197. private String lookupBaseFolderFromJndi() {
  198. try {
  199. // try to lookup JNDI env-entry for the baseFolder
  200. InitialContext ic = new InitialContext();
  201. Context env = (Context) ic.lookup("java:comp/env");
  202. return (String) env.lookup("baseFolder");
  203. } catch (NamingException n) {
  204. logger.error("Failed to get JNDI env-entry: " + n.getExplanation());
  205. }
  206. return null;
  207. }
  208. protected String getBaseFolderPath(String defaultBaseFolder) {
  209. // try a system property or a JNDI property
  210. String specifiedBaseFolder = System.getProperty("GITBLIT_HOME", lookupBaseFolderFromJndi());
  211. if (!StringUtils.isEmpty(System.getenv("GITBLIT_HOME"))) {
  212. // try an environment variable
  213. specifiedBaseFolder = System.getenv("GITBLIT_HOME");
  214. }
  215. if (!StringUtils.isEmpty(specifiedBaseFolder)) {
  216. // use specified base folder path
  217. return specifiedBaseFolder;
  218. }
  219. // use default base folder path
  220. return defaultBaseFolder;
  221. }
  222. protected <X extends IManager> X loadManager(Injector injector, Class<X> clazz) {
  223. X x = injector.getInstance(clazz);
  224. return x;
  225. }
  226. protected <X extends IManager> X startManager(Injector injector, Class<X> clazz) {
  227. X x = loadManager(injector, clazz);
  228. logManager(clazz);
  229. x.start();
  230. managers.add(x);
  231. return x;
  232. }
  233. protected <X extends IManager> X startManager(X x) {
  234. x.start();
  235. managers.add(x);
  236. return x;
  237. }
  238. protected void logManager(Class<? extends IManager> clazz) {
  239. logger.info("");
  240. logger.info("----[{}]----", clazz.getName());
  241. }
  242. @Override
  243. public final void contextDestroyed(ServletContextEvent contextEvent) {
  244. super.contextDestroyed(contextEvent);
  245. ServletContext context = contextEvent.getServletContext();
  246. destroyContext(context);
  247. }
  248. /**
  249. * Gitblit is being shutdown either because the servlet container is
  250. * shutting down or because the servlet container is re-deploying Gitblit.
  251. */
  252. protected void destroyContext(ServletContext context) {
  253. logger.info("Gitblit context destroyed by servlet container.");
  254. IPluginManager pluginManager = getManager(IPluginManager.class);
  255. for (LifeCycleListener listener : pluginManager.getExtensions(LifeCycleListener.class)) {
  256. try {
  257. listener.onShutdown();
  258. } catch (Throwable t) {
  259. logger.error(null, t);
  260. }
  261. }
  262. for (IManager manager : managers) {
  263. logger.debug("stopping {}", manager.getClass().getSimpleName());
  264. manager.stop();
  265. }
  266. }
  267. /**
  268. * Configures Gitblit GO
  269. *
  270. * @param context
  271. * @param settings
  272. * @param baseFolder
  273. * @param runtimeSettings
  274. * @return the base folder
  275. */
  276. protected File configureGO(
  277. ServletContext context,
  278. IStoredSettings goSettings,
  279. File goBaseFolder,
  280. IStoredSettings runtimeSettings) {
  281. logger.debug("configuring Gitblit GO");
  282. // merge the stored settings into the runtime settings
  283. //
  284. // if runtimeSettings is also a FileSettings w/o a specified target file,
  285. // the target file for runtimeSettings is set to "localSettings".
  286. runtimeSettings.merge(goSettings);
  287. File base = goBaseFolder;
  288. return base;
  289. }
  290. /**
  291. * Configures a standard WAR instance of Gitblit.
  292. *
  293. * @param context
  294. * @param webxmlSettings
  295. * @param contextFolder
  296. * @param runtimeSettings
  297. * @return the base folder
  298. */
  299. protected File configureWAR(
  300. ServletContext context,
  301. WebXmlSettings webxmlSettings,
  302. File contextFolder,
  303. IStoredSettings runtimeSettings) {
  304. // Gitblit is running in a standard servlet container
  305. logger.debug("configuring Gitblit WAR");
  306. logger.info("WAR contextFolder is " + ((contextFolder != null) ? contextFolder.getAbsolutePath() : "<empty>"));
  307. String webXmlPath = webxmlSettings.getString(Constants.baseFolder, Constants.contextFolder$ + "/WEB-INF/data");
  308. if (webXmlPath.contains(Constants.contextFolder$) && contextFolder == null) {
  309. // warn about null contextFolder (issue-199)
  310. logger.error("");
  311. logger.error(MessageFormat.format("\"{0}\" depends on \"{1}\" but \"{2}\" is returning NULL for \"{1}\"!",
  312. Constants.baseFolder, Constants.contextFolder$, context.getServerInfo()));
  313. logger.error(MessageFormat.format("Please specify a non-parameterized path for <context-param> {0} in web.xml!!", Constants.baseFolder));
  314. logger.error(MessageFormat.format("OR configure your servlet container to specify a \"{0}\" parameter in the context configuration!!", Constants.baseFolder));
  315. logger.error("");
  316. }
  317. String baseFolderPath = getBaseFolderPath(webXmlPath);
  318. File baseFolder = com.gitblit.utils.FileUtils.resolveParameter(Constants.contextFolder$, contextFolder, baseFolderPath);
  319. baseFolder.mkdirs();
  320. // try to extract the data folder resource to the baseFolder
  321. extractResources(context, "/WEB-INF/data/", baseFolder);
  322. // delegate all config to baseFolder/gitblit.properties file
  323. File localSettings = new File(baseFolder, "gitblit.properties");
  324. FileSettings fileSettings = new FileSettings(localSettings.getAbsolutePath());
  325. // merge the stored settings into the runtime settings
  326. //
  327. // if runtimeSettings is also a FileSettings w/o a specified target file,
  328. // the target file for runtimeSettings is set to "localSettings".
  329. runtimeSettings.merge(fileSettings);
  330. return baseFolder;
  331. }
  332. /**
  333. * Configures an OpenShift instance of Gitblit.
  334. *
  335. * @param context
  336. * @param webxmlSettings
  337. * @param contextFolder
  338. * @param runtimeSettings
  339. * @return the base folder
  340. */
  341. private File configureExpress(
  342. ServletContext context,
  343. WebXmlSettings webxmlSettings,
  344. File contextFolder,
  345. IStoredSettings runtimeSettings) {
  346. // Gitblit is running in OpenShift/JBoss
  347. logger.debug("configuring Gitblit Express");
  348. String openShift = System.getenv("OPENSHIFT_DATA_DIR");
  349. File base = new File(openShift);
  350. logger.info("EXPRESS contextFolder is " + contextFolder.getAbsolutePath());
  351. // Copy the included scripts to the configured groovy folder
  352. String path = webxmlSettings.getString(Keys.groovy.scriptsFolder, "groovy");
  353. File localScripts = com.gitblit.utils.FileUtils.resolveParameter(Constants.baseFolder$, base, path);
  354. if (!localScripts.exists()) {
  355. File warScripts = new File(contextFolder, "/WEB-INF/data/groovy");
  356. if (!warScripts.equals(localScripts)) {
  357. try {
  358. com.gitblit.utils.FileUtils.copy(localScripts, warScripts.listFiles());
  359. } catch (IOException e) {
  360. logger.error(MessageFormat.format(
  361. "Failed to copy included Groovy scripts from {0} to {1}",
  362. warScripts, localScripts));
  363. }
  364. }
  365. }
  366. // Copy the included gitignore files to the configured gitignore folder
  367. String gitignorePath = webxmlSettings.getString(Keys.git.gitignoreFolder, "gitignore");
  368. File localGitignores = com.gitblit.utils.FileUtils.resolveParameter(Constants.baseFolder$, base, gitignorePath);
  369. if (!localGitignores.exists()) {
  370. File warGitignores = new File(contextFolder, "/WEB-INF/data/gitignore");
  371. if (!warGitignores.equals(localGitignores)) {
  372. try {
  373. com.gitblit.utils.FileUtils.copy(localGitignores, warGitignores.listFiles());
  374. } catch (IOException e) {
  375. logger.error(MessageFormat.format(
  376. "Failed to copy included .gitignore files from {0} to {1}",
  377. warGitignores, localGitignores));
  378. }
  379. }
  380. }
  381. // merge the WebXmlSettings into the runtime settings (for backwards-compatibilty)
  382. runtimeSettings.merge(webxmlSettings);
  383. // settings are to be stored in openshift/gitblit.properties
  384. File localSettings = new File(base, "gitblit.properties");
  385. FileSettings fileSettings = new FileSettings(localSettings.getAbsolutePath());
  386. // merge the stored settings into the runtime settings
  387. //
  388. // if runtimeSettings is also a FileSettings w/o a specified target file,
  389. // the target file for runtimeSettings is set to "localSettings".
  390. runtimeSettings.merge(fileSettings);
  391. return base;
  392. }
  393. protected void extractResources(ServletContext context, String path, File toDir) {
  394. for (String resource : context.getResourcePaths(path)) {
  395. // extract the resource to the directory if it does not exist
  396. File f = new File(toDir, resource.substring(path.length()));
  397. if (!f.exists()) {
  398. InputStream is = null;
  399. OutputStream os = null;
  400. try {
  401. if (resource.charAt(resource.length() - 1) == '/') {
  402. // directory
  403. f.mkdirs();
  404. extractResources(context, resource, f);
  405. } else {
  406. // file
  407. f.getParentFile().mkdirs();
  408. is = context.getResourceAsStream(resource);
  409. os = new FileOutputStream(f);
  410. byte [] buffer = new byte[4096];
  411. int len = 0;
  412. while ((len = is.read(buffer)) > -1) {
  413. os.write(buffer, 0, len);
  414. }
  415. }
  416. } catch (FileNotFoundException e) {
  417. logger.error("Failed to find resource \"" + resource + "\"", e);
  418. } catch (IOException e) {
  419. logger.error("Failed to copy resource \"" + resource + "\" to " + f, e);
  420. } finally {
  421. if (is != null) {
  422. try {
  423. is.close();
  424. } catch (IOException e) {
  425. // ignore
  426. }
  427. }
  428. if (os != null) {
  429. try {
  430. os.close();
  431. } catch (IOException e) {
  432. // ignore
  433. }
  434. }
  435. }
  436. }
  437. }
  438. }
  439. }