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