James Moger
2014-09-24 a8cac83f310be77c1acb6ef39fc0245787a79834
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";
538991 156             String base = System.getProperty("GITBLIT_HOME",lookupBaseFolderFromJndi());
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;
269c50 219     }
e24670 220
ca4d98 221     protected <X extends IManager> X loadManager(ObjectGraph injector, Class<X> clazz) {
269c50 222         X x = injector.get(clazz);
ca4d98 223         return x;
JM 224     }
225
226     protected <X extends IManager> X startManager(ObjectGraph injector, Class<X> clazz) {
227         X x = loadManager(injector, clazz);
228         logManager(clazz);
269c50 229         x.start();
JM 230         managers.add(x);
231         return x;
232     }
e24670 233
269c50 234     protected void logManager(Class<? extends IManager> clazz) {
JM 235         logger.info("");
236         logger.info("----[{}]----", clazz.getName());
237     }
238
239     /**
240      * Gitblit is being shutdown either because the servlet container is
241      * shutting down or because the servlet container is re-deploying Gitblit.
242      */
243     @Override
244     protected void destroyContext(ServletContext context) {
245         logger.info("Gitblit context destroyed by servlet container.");
c59584 246
JM 247         IPluginManager pluginManager = getManager(IPluginManager.class);
248         for (LifeCycleListener listener : pluginManager.getExtensions(LifeCycleListener.class)) {
249             try {
250                 listener.onShutdown();
251             } catch (Throwable t) {
252                 logger.error(null, t);
253             }
254         }
255
269c50 256         for (IManager manager : managers) {
JM 257             logger.debug("stopping {}", manager.getClass().getSimpleName());
258             manager.stop();
e24670 259         }
JM 260     }
261
262     /**
263      * Configures Gitblit GO
264      *
265      * @param context
266      * @param settings
267      * @param baseFolder
268      * @param runtimeSettings
269      * @return the base folder
270      */
271     protected File configureGO(
272             ServletContext context,
273             IStoredSettings goSettings,
274             File goBaseFolder,
275             IStoredSettings runtimeSettings) {
269c50 276
JM 277         logger.debug("configuring Gitblit GO");
e24670 278
JM 279         // merge the stored settings into the runtime settings
280         //
281         // if runtimeSettings is also a FileSettings w/o a specified target file,
282         // the target file for runtimeSettings is set to "localSettings".
283         runtimeSettings.merge(goSettings);
284         File base = goBaseFolder;
285         return base;
286     }
287
288
289     /**
290      * Configures a standard WAR instance of Gitblit.
291      *
292      * @param context
293      * @param webxmlSettings
294      * @param contextFolder
295      * @param runtimeSettings
296      * @return the base folder
297      */
298     protected File configureWAR(
299             ServletContext context,
300             WebXmlSettings webxmlSettings,
301             File contextFolder,
302             IStoredSettings runtimeSettings) {
303
304         // Gitblit is running in a standard servlet container
269c50 305         logger.debug("configuring Gitblit WAR");
e24670 306         logger.info("WAR contextFolder is " + ((contextFolder != null) ? contextFolder.getAbsolutePath() : "<empty>"));
JM 307
308         String path = webxmlSettings.getString(Constants.baseFolder, Constants.contextFolder$ + "/WEB-INF/data");
309
310         if (path.contains(Constants.contextFolder$) && contextFolder == null) {
311             // warn about null contextFolder (issue-199)
312             logger.error("");
313             logger.error(MessageFormat.format("\"{0}\" depends on \"{1}\" but \"{2}\" is returning NULL for \"{1}\"!",
314                     Constants.baseFolder, Constants.contextFolder$, context.getServerInfo()));
315             logger.error(MessageFormat.format("Please specify a non-parameterized path for <context-param> {0} in web.xml!!", Constants.baseFolder));
316             logger.error(MessageFormat.format("OR configure your servlet container to specify a \"{0}\" parameter in the context configuration!!", Constants.baseFolder));
317             logger.error("");
318         }
319
4b9b3c 320         String externalBase = System.getProperty("GITBLIT_HOME", lookupBaseFolderFromJndi());
JM 321         if (!StringUtils.isEmpty(externalBase)) {
322             path = externalBase;
e24670 323         }
JM 324
325         File base = com.gitblit.utils.FileUtils.resolveParameter(Constants.contextFolder$, contextFolder, path);
326         base.mkdirs();
327
328         // try to extract the data folder resource to the baseFolder
329         File localSettings = new File(base, "gitblit.properties");
330         if (!localSettings.exists()) {
331             extractResources(context, "/WEB-INF/data/", base);
332         }
333
334         // delegate all config to baseFolder/gitblit.properties file
335         FileSettings fileSettings = new FileSettings(localSettings.getAbsolutePath());
336
337         // merge the stored settings into the runtime settings
338         //
339         // if runtimeSettings is also a FileSettings w/o a specified target file,
340         // the target file for runtimeSettings is set to "localSettings".
341         runtimeSettings.merge(fileSettings);
342
343         return base;
344     }
345
346     /**
347      * Configures an OpenShift instance of Gitblit.
348      *
349      * @param context
350      * @param webxmlSettings
351      * @param contextFolder
352      * @param runtimeSettings
353      * @return the base folder
354      */
355     private File configureExpress(
356             ServletContext context,
357             WebXmlSettings webxmlSettings,
358             File contextFolder,
359             IStoredSettings runtimeSettings) {
360
361         // Gitblit is running in OpenShift/JBoss
269c50 362         logger.debug("configuring Gitblit Express");
e24670 363         String openShift = System.getenv("OPENSHIFT_DATA_DIR");
JM 364         File base = new File(openShift);
365         logger.info("EXPRESS contextFolder is " + contextFolder.getAbsolutePath());
366
367         // Copy the included scripts to the configured groovy folder
368         String path = webxmlSettings.getString(Keys.groovy.scriptsFolder, "groovy");
369         File localScripts = com.gitblit.utils.FileUtils.resolveParameter(Constants.baseFolder$, base, path);
370         if (!localScripts.exists()) {
371             File warScripts = new File(contextFolder, "/WEB-INF/data/groovy");
372             if (!warScripts.equals(localScripts)) {
373                 try {
374                     com.gitblit.utils.FileUtils.copy(localScripts, warScripts.listFiles());
375                 } catch (IOException e) {
376                     logger.error(MessageFormat.format(
377                             "Failed to copy included Groovy scripts from {0} to {1}",
378                             warScripts, localScripts));
379                 }
380             }
381         }
382
0047fb 383         // Copy the included gitignore files to the configured gitignore folder
JM 384         String gitignorePath = webxmlSettings.getString(Keys.git.gitignoreFolder, "gitignore");
385         File localGitignores = com.gitblit.utils.FileUtils.resolveParameter(Constants.baseFolder$, base, gitignorePath);
386         if (!localGitignores.exists()) {
387             File warGitignores = new File(contextFolder, "/WEB-INF/data/gitignore");
388             if (!warGitignores.equals(localGitignores)) {
389                 try {
390                     com.gitblit.utils.FileUtils.copy(localGitignores, warGitignores.listFiles());
391                 } catch (IOException e) {
392                     logger.error(MessageFormat.format(
393                             "Failed to copy included .gitignore files from {0} to {1}",
394                             warGitignores, localGitignores));
395                 }
396             }
397         }
398
e24670 399         // merge the WebXmlSettings into the runtime settings (for backwards-compatibilty)
JM 400         runtimeSettings.merge(webxmlSettings);
401
402         // settings are to be stored in openshift/gitblit.properties
403         File localSettings = new File(base, "gitblit.properties");
404         FileSettings fileSettings = new FileSettings(localSettings.getAbsolutePath());
405
406         // merge the stored settings into the runtime settings
407         //
408         // if runtimeSettings is also a FileSettings w/o a specified target file,
409         // the target file for runtimeSettings is set to "localSettings".
410         runtimeSettings.merge(fileSettings);
411
412         return base;
79d324 413     }
699e71 414
79d324 415     protected void extractResources(ServletContext context, String path, File toDir) {
JM 416         for (String resource : context.getResourcePaths(path)) {
417             // extract the resource to the directory if it does not exist
418             File f = new File(toDir, resource.substring(path.length()));
419             if (!f.exists()) {
df5500 420                 InputStream is = null;
JM 421                 OutputStream os = null;
79d324 422                 try {
JM 423                     if (resource.charAt(resource.length() - 1) == '/') {
424                         // directory
425                         f.mkdirs();
426                         extractResources(context, resource, f);
427                     } else {
428                         // file
429                         f.getParentFile().mkdirs();
df5500 430                         is = context.getResourceAsStream(resource);
JM 431                         os = new FileOutputStream(f);
79d324 432                         byte [] buffer = new byte[4096];
JM 433                         int len = 0;
434                         while ((len = is.read(buffer)) > -1) {
435                             os.write(buffer, 0, len);
436                         }
437                     }
438                 } catch (FileNotFoundException e) {
439                     logger.error("Failed to find resource \"" + resource + "\"", e);
440                 } catch (IOException e) {
441                     logger.error("Failed to copy resource \"" + resource + "\" to " + f, e);
df5500 442                 } finally {
JM 443                     if (is != null) {
444                         try {
445                             is.close();
446                         } catch (IOException e) {
447                             // ignore
448                         }
449                     }
450                     if (os != null) {
451                         try {
452                             os.close();
453                         } catch (IOException e) {
454                             // ignore
455                         }
456                     }
79d324 457                 }
JM 458             }
459         }
116422 460     }
79d324 461 }