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