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