| /* |
| * Copyright 2011 gitblit.com. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package com.gitblit.servlet; |
| |
| import java.io.File; |
| import java.io.FileNotFoundException; |
| import java.io.FileOutputStream; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.OutputStream; |
| import java.text.MessageFormat; |
| import java.util.ArrayList; |
| import java.util.List; |
| |
| import javax.naming.Context; |
| import javax.naming.InitialContext; |
| import javax.naming.NamingException; |
| import javax.servlet.ServletContext; |
| import javax.servlet.ServletContextEvent; |
| |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| import com.gitblit.Constants; |
| import com.gitblit.FileSettings; |
| import com.gitblit.IStoredSettings; |
| import com.gitblit.Keys; |
| import com.gitblit.WebXmlSettings; |
| import com.gitblit.extensions.LifeCycleListener; |
| import com.gitblit.guice.CoreModule; |
| import com.gitblit.guice.WebModule; |
| import com.gitblit.manager.IAuthenticationManager; |
| import com.gitblit.manager.IFederationManager; |
| import com.gitblit.manager.IGitblit; |
| import com.gitblit.manager.IManager; |
| import com.gitblit.manager.INotificationManager; |
| import com.gitblit.manager.IPluginManager; |
| import com.gitblit.manager.IProjectManager; |
| import com.gitblit.manager.IRepositoryManager; |
| import com.gitblit.manager.IRuntimeManager; |
| import com.gitblit.manager.IServicesManager; |
| import com.gitblit.manager.IUserManager; |
| import com.gitblit.tickets.ITicketService; |
| import com.gitblit.transport.ssh.IPublicKeyManager; |
| import com.gitblit.utils.ContainerUtils; |
| import com.gitblit.utils.StringUtils; |
| import com.google.inject.AbstractModule; |
| import com.google.inject.Guice; |
| import com.google.inject.Injector; |
| import com.google.inject.servlet.GuiceServletContextListener; |
| |
| /** |
| * This class is the main entry point for the entire webapp. It is a singleton |
| * created manually by Gitblit GO or dynamically by the WAR/Express servlet |
| * container. This class instantiates and starts all managers. |
| * |
| * Servlets and filters are injected which allows Gitblit to be completely |
| * code-driven. |
| * |
| * @author James Moger |
| * |
| */ |
| public class GitblitContext extends GuiceServletContextListener { |
| |
| private static GitblitContext gitblit; |
| |
| protected final Logger logger = LoggerFactory.getLogger(getClass()); |
| |
| private final List<IManager> managers = new ArrayList<IManager>(); |
| |
| private final IStoredSettings goSettings; |
| |
| private final File goBaseFolder; |
| |
| /** |
| * Construct a Gitblit WAR/Express context. |
| */ |
| public GitblitContext() { |
| this(null, null); |
| } |
| |
| /** |
| * Construct a Gitblit GO context. |
| * |
| * @param settings |
| * @param baseFolder |
| */ |
| public GitblitContext(IStoredSettings settings, File baseFolder) { |
| this.goSettings = settings; |
| this.goBaseFolder = baseFolder; |
| gitblit = this; |
| } |
| |
| /** |
| * This method is only used for unit and integration testing. |
| * |
| * @param managerClass |
| * @return a manager |
| */ |
| @SuppressWarnings("unchecked") |
| public static <X extends IManager> X getManager(Class<X> managerClass) { |
| for (IManager manager : gitblit.managers) { |
| if (managerClass.isAssignableFrom(manager.getClass())) { |
| return (X) manager; |
| } |
| } |
| return null; |
| } |
| |
| @Override |
| protected Injector getInjector() { |
| return Guice.createInjector(getModules()); |
| } |
| |
| /** |
| * Returns Gitblit's Guice injection modules. |
| */ |
| protected AbstractModule [] getModules() { |
| return new AbstractModule [] { new CoreModule(), new WebModule() }; |
| } |
| |
| /** |
| * Configure Gitblit from the web.xml, if no configuration has already been |
| * specified. |
| * |
| * @see ServletContextListener.contextInitialize(ServletContextEvent) |
| */ |
| @Override |
| public final void contextInitialized(ServletContextEvent contextEvent) { |
| super.contextInitialized(contextEvent); |
| |
| ServletContext context = contextEvent.getServletContext(); |
| startCore(context); |
| } |
| |
| /** |
| * Prepare runtime settings and start all manager instances. |
| */ |
| protected void startCore(ServletContext context) { |
| Injector injector = (Injector) context.getAttribute(Injector.class.getName()); |
| |
| // create the runtime settings object |
| IStoredSettings runtimeSettings = injector.getInstance(IStoredSettings.class); |
| final File baseFolder; |
| |
| if (goSettings != null) { |
| // Gitblit GO |
| baseFolder = configureGO(context, goSettings, goBaseFolder, runtimeSettings); |
| } else { |
| // servlet container |
| WebXmlSettings webxmlSettings = new WebXmlSettings(context); |
| String contextRealPath = context.getRealPath("/"); |
| File contextFolder = (contextRealPath != null) ? new File(contextRealPath) : null; |
| |
| // if the base folder dosen't match the default assume they don't want to use express, |
| // this allows for other containers to customise the basefolder per context. |
| String defaultBase = Constants.contextFolder$ + "/WEB-INF/data"; |
| String base = getBaseFolderPath(defaultBase); |
| if (!StringUtils.isEmpty(System.getenv("OPENSHIFT_DATA_DIR")) && defaultBase.equals(base)) { |
| // RedHat OpenShift |
| baseFolder = configureExpress(context, webxmlSettings, contextFolder, runtimeSettings); |
| } else { |
| // standard WAR |
| baseFolder = configureWAR(context, webxmlSettings, contextFolder, runtimeSettings); |
| } |
| |
| // Test for Tomcat forward-slash/%2F issue and auto-adjust settings |
| ContainerUtils.CVE_2007_0450.test(runtimeSettings); |
| } |
| |
| // Manually configure IRuntimeManager |
| logManager(IRuntimeManager.class); |
| IRuntimeManager runtime = injector.getInstance(IRuntimeManager.class); |
| runtime.setBaseFolder(baseFolder); |
| runtime.getStatus().isGO = goSettings != null; |
| runtime.getStatus().servletContainer = context.getServerInfo(); |
| runtime.start(); |
| managers.add(runtime); |
| |
| // create the plugin manager instance but do not start it |
| loadManager(injector, IPluginManager.class); |
| |
| // start all other managers |
| startManager(injector, INotificationManager.class); |
| startManager(injector, IUserManager.class); |
| startManager(injector, IAuthenticationManager.class); |
| startManager(injector, IPublicKeyManager.class); |
| startManager(injector, IRepositoryManager.class); |
| startManager(injector, IProjectManager.class); |
| startManager(injector, IFederationManager.class); |
| startManager(injector, ITicketService.class); |
| startManager(injector, IGitblit.class); |
| startManager(injector, IServicesManager.class); |
| |
| // start the plugin manager last so that plugins can depend on |
| // deterministic access to all other managers in their start() methods |
| startManager(injector, IPluginManager.class); |
| |
| logger.info(""); |
| logger.info("All managers started."); |
| logger.info(""); |
| |
| IPluginManager pluginManager = injector.getInstance(IPluginManager.class); |
| for (LifeCycleListener listener : pluginManager.getExtensions(LifeCycleListener.class)) { |
| try { |
| listener.onStartup(); |
| } catch (Throwable t) { |
| logger.error(null, t); |
| } |
| } |
| } |
| |
| private String lookupBaseFolderFromJndi() { |
| try { |
| // try to lookup JNDI env-entry for the baseFolder |
| InitialContext ic = new InitialContext(); |
| Context env = (Context) ic.lookup("java:comp/env"); |
| return (String) env.lookup("baseFolder"); |
| } catch (NamingException n) { |
| logger.error("Failed to get JNDI env-entry: " + n.getExplanation()); |
| } |
| return null; |
| } |
| |
| protected String getBaseFolderPath(String defaultBaseFolder) { |
| // try a system property or a JNDI property |
| String specifiedBaseFolder = System.getProperty("GITBLIT_HOME", lookupBaseFolderFromJndi()); |
| |
| if (!StringUtils.isEmpty(System.getenv("GITBLIT_HOME"))) { |
| // try an environment variable |
| specifiedBaseFolder = System.getenv("GITBLIT_HOME"); |
| } |
| |
| if (!StringUtils.isEmpty(specifiedBaseFolder)) { |
| // use specified base folder path |
| return specifiedBaseFolder; |
| } |
| |
| // use default base folder path |
| return defaultBaseFolder; |
| } |
| |
| protected <X extends IManager> X loadManager(Injector injector, Class<X> clazz) { |
| X x = injector.getInstance(clazz); |
| return x; |
| } |
| |
| protected <X extends IManager> X startManager(Injector injector, Class<X> clazz) { |
| X x = loadManager(injector, clazz); |
| logManager(clazz); |
| return startManager(x); |
| } |
| |
| protected <X extends IManager> X startManager(X x) { |
| x.start(); |
| managers.add(x); |
| return x; |
| } |
| |
| protected void logManager(Class<? extends IManager> clazz) { |
| logger.info(""); |
| logger.info("----[{}]----", clazz.getName()); |
| } |
| |
| @Override |
| public final void contextDestroyed(ServletContextEvent contextEvent) { |
| super.contextDestroyed(contextEvent); |
| ServletContext context = contextEvent.getServletContext(); |
| destroyContext(context); |
| } |
| |
| /** |
| * Gitblit is being shutdown either because the servlet container is |
| * shutting down or because the servlet container is re-deploying Gitblit. |
| */ |
| protected void destroyContext(ServletContext context) { |
| logger.info("Gitblit context destroyed by servlet container."); |
| |
| IPluginManager pluginManager = getManager(IPluginManager.class); |
| for (LifeCycleListener listener : pluginManager.getExtensions(LifeCycleListener.class)) { |
| try { |
| listener.onShutdown(); |
| } catch (Throwable t) { |
| logger.error(null, t); |
| } |
| } |
| |
| for (IManager manager : managers) { |
| logger.debug("stopping {}", manager.getClass().getSimpleName()); |
| manager.stop(); |
| } |
| } |
| |
| /** |
| * Configures Gitblit GO |
| * |
| * @param context |
| * @param settings |
| * @param baseFolder |
| * @param runtimeSettings |
| * @return the base folder |
| */ |
| protected File configureGO( |
| ServletContext context, |
| IStoredSettings goSettings, |
| File goBaseFolder, |
| IStoredSettings runtimeSettings) { |
| |
| logger.debug("configuring Gitblit GO"); |
| |
| // merge the stored settings into the runtime settings |
| // |
| // if runtimeSettings is also a FileSettings w/o a specified target file, |
| // the target file for runtimeSettings is set to "localSettings". |
| runtimeSettings.merge(goSettings); |
| File base = goBaseFolder; |
| return base; |
| } |
| |
| |
| /** |
| * Configures a standard WAR instance of Gitblit. |
| * |
| * @param context |
| * @param webxmlSettings |
| * @param contextFolder |
| * @param runtimeSettings |
| * @return the base folder |
| */ |
| protected File configureWAR( |
| ServletContext context, |
| WebXmlSettings webxmlSettings, |
| File contextFolder, |
| IStoredSettings runtimeSettings) { |
| |
| // Gitblit is running in a standard servlet container |
| logger.debug("configuring Gitblit WAR"); |
| logger.info("WAR contextFolder is " + ((contextFolder != null) ? contextFolder.getAbsolutePath() : "<empty>")); |
| |
| String webXmlPath = webxmlSettings.getString(Constants.baseFolder, Constants.contextFolder$ + "/WEB-INF/data"); |
| |
| if (webXmlPath.contains(Constants.contextFolder$) && contextFolder == null) { |
| // warn about null contextFolder (issue-199) |
| logger.error(""); |
| logger.error(MessageFormat.format("\"{0}\" depends on \"{1}\" but \"{2}\" is returning NULL for \"{1}\"!", |
| Constants.baseFolder, Constants.contextFolder$, context.getServerInfo())); |
| logger.error(MessageFormat.format("Please specify a non-parameterized path for <context-param> {0} in web.xml!!", Constants.baseFolder)); |
| logger.error(MessageFormat.format("OR configure your servlet container to specify a \"{0}\" parameter in the context configuration!!", Constants.baseFolder)); |
| logger.error(""); |
| } |
| |
| String baseFolderPath = getBaseFolderPath(webXmlPath); |
| |
| File baseFolder = com.gitblit.utils.FileUtils.resolveParameter(Constants.contextFolder$, contextFolder, baseFolderPath); |
| baseFolder.mkdirs(); |
| |
| // try to extract the data folder resource to the baseFolder |
| extractResources(context, "/WEB-INF/data/", baseFolder); |
| |
| // delegate all config to baseFolder/gitblit.properties file |
| File localSettings = new File(baseFolder, "gitblit.properties"); |
| FileSettings fileSettings = new FileSettings(localSettings.getAbsolutePath()); |
| |
| // merge the stored settings into the runtime settings |
| // |
| // if runtimeSettings is also a FileSettings w/o a specified target file, |
| // the target file for runtimeSettings is set to "localSettings". |
| runtimeSettings.merge(fileSettings); |
| |
| return baseFolder; |
| } |
| |
| /** |
| * Configures an OpenShift instance of Gitblit. |
| * |
| * @param context |
| * @param webxmlSettings |
| * @param contextFolder |
| * @param runtimeSettings |
| * @return the base folder |
| */ |
| private File configureExpress( |
| ServletContext context, |
| WebXmlSettings webxmlSettings, |
| File contextFolder, |
| IStoredSettings runtimeSettings) { |
| |
| // Gitblit is running in OpenShift/JBoss |
| logger.debug("configuring Gitblit Express"); |
| String openShift = System.getenv("OPENSHIFT_DATA_DIR"); |
| File base = new File(openShift); |
| logger.info("EXPRESS contextFolder is " + contextFolder.getAbsolutePath()); |
| |
| // Copy the included scripts to the configured groovy folder |
| String path = webxmlSettings.getString(Keys.groovy.scriptsFolder, "groovy"); |
| File localScripts = com.gitblit.utils.FileUtils.resolveParameter(Constants.baseFolder$, base, path); |
| if (!localScripts.exists()) { |
| File warScripts = new File(contextFolder, "/WEB-INF/data/groovy"); |
| if (!warScripts.equals(localScripts)) { |
| try { |
| com.gitblit.utils.FileUtils.copy(localScripts, warScripts.listFiles()); |
| } catch (IOException e) { |
| logger.error(MessageFormat.format( |
| "Failed to copy included Groovy scripts from {0} to {1}", |
| warScripts, localScripts)); |
| } |
| } |
| } |
| |
| // Copy the included gitignore files to the configured gitignore folder |
| String gitignorePath = webxmlSettings.getString(Keys.git.gitignoreFolder, "gitignore"); |
| File localGitignores = com.gitblit.utils.FileUtils.resolveParameter(Constants.baseFolder$, base, gitignorePath); |
| if (!localGitignores.exists()) { |
| File warGitignores = new File(contextFolder, "/WEB-INF/data/gitignore"); |
| if (!warGitignores.equals(localGitignores)) { |
| try { |
| com.gitblit.utils.FileUtils.copy(localGitignores, warGitignores.listFiles()); |
| } catch (IOException e) { |
| logger.error(MessageFormat.format( |
| "Failed to copy included .gitignore files from {0} to {1}", |
| warGitignores, localGitignores)); |
| } |
| } |
| } |
| |
| // merge the WebXmlSettings into the runtime settings (for backwards-compatibilty) |
| runtimeSettings.merge(webxmlSettings); |
| |
| // settings are to be stored in openshift/gitblit.properties |
| File localSettings = new File(base, "gitblit.properties"); |
| FileSettings fileSettings = new FileSettings(localSettings.getAbsolutePath()); |
| |
| // merge the stored settings into the runtime settings |
| // |
| // if runtimeSettings is also a FileSettings w/o a specified target file, |
| // the target file for runtimeSettings is set to "localSettings". |
| runtimeSettings.merge(fileSettings); |
| |
| return base; |
| } |
| |
| protected void extractResources(ServletContext context, String path, File toDir) { |
| for (String resource : context.getResourcePaths(path)) { |
| // extract the resource to the directory if it does not exist |
| File f = new File(toDir, resource.substring(path.length())); |
| if (!f.exists()) { |
| InputStream is = null; |
| OutputStream os = null; |
| try { |
| if (resource.charAt(resource.length() - 1) == '/') { |
| // directory |
| f.mkdirs(); |
| extractResources(context, resource, f); |
| } else { |
| // file |
| f.getParentFile().mkdirs(); |
| is = context.getResourceAsStream(resource); |
| os = new FileOutputStream(f); |
| byte [] buffer = new byte[4096]; |
| int len = 0; |
| while ((len = is.read(buffer)) > -1) { |
| os.write(buffer, 0, len); |
| } |
| } |
| } catch (FileNotFoundException e) { |
| logger.error("Failed to find resource \"" + resource + "\"", e); |
| } catch (IOException e) { |
| logger.error("Failed to copy resource \"" + resource + "\" to " + f, e); |
| } finally { |
| if (is != null) { |
| try { |
| is.close(); |
| } catch (IOException e) { |
| // ignore |
| } |
| } |
| if (os != null) { |
| try { |
| os.close(); |
| } catch (IOException e) { |
| // ignore |
| } |
| } |
| } |
| } |
| } |
| } |
| } |