James Moger
2013-11-21 7bf6e183ff8abd0c35eeb29f399da12389562ecb
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.HashMap;
27 import java.util.List;
28 import java.util.Map;
29
06718b 30 import javax.naming.Context;
JM 31 import javax.naming.InitialContext;
32 import javax.naming.NamingException;
79d324 33 import javax.servlet.ServletContext;
116422 34 import javax.servlet.annotation.WebListener;
79d324 35
7bf6e1 36 import com.gitblit.Constants;
JM 37 import com.gitblit.DaggerModule;
38 import com.gitblit.FileSettings;
39 import com.gitblit.IStoredSettings;
40 import com.gitblit.Keys;
41 import com.gitblit.WebXmlSettings;
cacf8b 42 import com.gitblit.dagger.DaggerContextListener;
116422 43 import com.gitblit.git.GitServlet;
db4f6b 44 import com.gitblit.manager.IFederationManager;
JM 45 import com.gitblit.manager.IGitblitManager;
e24670 46 import com.gitblit.manager.IManager;
db4f6b 47 import com.gitblit.manager.INotificationManager;
JM 48 import com.gitblit.manager.IProjectManager;
49 import com.gitblit.manager.IRepositoryManager;
50 import com.gitblit.manager.IRuntimeManager;
269c50 51 import com.gitblit.manager.IServicesManager;
db4f6b 52 import com.gitblit.manager.ISessionManager;
JM 53 import com.gitblit.manager.IUserManager;
79d324 54 import com.gitblit.utils.ContainerUtils;
JM 55 import com.gitblit.utils.StringUtils;
116422 56 import com.gitblit.wicket.GitblitWicketFilter;
79d324 57
e24670 58 import dagger.ObjectGraph;
JM 59
79d324 60 /**
269c50 61  * This class is the main entry point for the entire webapp.  It is a singleton
JM 62  * created manually by Gitblit GO or dynamically by the WAR/Express servlet
63  * container.  This class instantiates and starts all managers followed by
64  * instantiating and registering all servlets and filters.
699e71 65  *
269c50 66  * Leveraging Servlet 3 and Dagger static dependency injection allows Gitblit to
JM 67  * be modular and completely code-driven rather then relying on the fragility of
68  * a web.xml descriptor and the static & monolithic design previously used.
699e71 69  *
79d324 70  * @author James Moger
699e71 71  *
79d324 72  */
116422 73 @WebListener
7bf6e1 74 public class GitblitContext extends DaggerContextListener {
79d324 75
7bf6e1 76     private static GitblitContext gitblit;
269c50 77
JM 78     private final List<IManager> managers = new ArrayList<IManager>();
db4f6b 79
JM 80     private final IStoredSettings goSettings;
e24670 81
JM 82     private final File goBaseFolder;
83
269c50 84     /**
JM 85      * Construct a Gitblit WAR/Express context.
86      */
7bf6e1 87     public GitblitContext() {
db4f6b 88         this.goSettings = null;
e24670 89         this.goBaseFolder = null;
269c50 90         gitblit = this;
db4f6b 91     }
JM 92
269c50 93     /**
JM 94      * Construct a Gitblit GO context.
95      *
96      * @param settings
97      * @param baseFolder
98      */
7bf6e1 99     public GitblitContext(IStoredSettings settings, File baseFolder) {
db4f6b 100         this.goSettings = settings;
e24670 101         this.goBaseFolder = baseFolder;
79d324 102         gitblit = this;
JM 103     }
104
105     /**
269c50 106      * This method is only used for unit and integration testing.
699e71 107      *
269c50 108      * @param managerClass
JM 109      * @return a manager
79d324 110      */
db4f6b 111     @SuppressWarnings("unchecked")
269c50 112     public static <X extends IManager> X getManager(Class<X> managerClass) {
e24670 113         for (IManager manager : gitblit.managers) {
JM 114             if (managerClass.isAssignableFrom(manager.getClass())) {
115                 return (X) manager;
116             }
117         }
db4f6b 118         return null;
a7db57 119     }
699e71 120
a7db57 121     /**
269c50 122      * Returns Gitblit's Dagger injection modules.
79d324 123      */
db4f6b 124     @Override
269c50 125     protected Object [] getModules() {
JM 126         return new Object [] { new DaggerModule() };
79d324 127     }
699e71 128
79d324 129     /**
269c50 130      * Prepare runtime settings and start all manager instances.
79d324 131      */
JM 132     @Override
116422 133     protected void beforeServletInjection(ServletContext context) {
e24670 134         ObjectGraph injector = getInjector(context);
JM 135
136         // create the runtime settings object
137         IStoredSettings runtimeSettings = injector.get(IStoredSettings.class);
138         final File baseFolder;
139
140         if (goSettings != null) {
141             // Gitblit GO
142             baseFolder = configureGO(context, goSettings, goBaseFolder, runtimeSettings);
143         } else {
144             // servlet container
79d324 145             WebXmlSettings webxmlSettings = new WebXmlSettings(context);
JM 146             String contextRealPath = context.getRealPath("/");
147             File contextFolder = (contextRealPath != null) ? new File(contextRealPath) : null;
699e71 148
e24670 149             if (!StringUtils.isEmpty(System.getenv("OPENSHIFT_DATA_DIR"))) {
JM 150                 // RedHat OpenShift
151                 baseFolder = configureExpress(context, webxmlSettings, contextFolder, runtimeSettings);
79d324 152             } else {
e24670 153                 // standard WAR
JM 154                 baseFolder = configureWAR(context, webxmlSettings, contextFolder, runtimeSettings);
79d324 155             }
6f442a 156
e24670 157             // Test for Tomcat forward-slash/%2F issue and auto-adjust settings
JM 158             ContainerUtils.CVE_2007_0450.test(runtimeSettings);
79d324 159         }
699e71 160
269c50 161         // Manually configure IRuntimeManager
JM 162         logManager(IRuntimeManager.class);
163         IRuntimeManager runtime = injector.get(IRuntimeManager.class);
e24670 164         runtime.setBaseFolder(baseFolder);
JM 165         runtime.getStatus().isGO = goSettings != null;
166         runtime.getStatus().servletContainer = context.getServerInfo();
269c50 167         runtime.start();
JM 168         managers.add(runtime);
e24670 169
269c50 170         // start all other managers
bdfdc9 171         startManager(injector, INotificationManager.class);
8f1c9f 172         startManager(injector, IUserManager.class);
aa6d43 173         startManager(injector, ISessionManager.class);
95cdba 174         startManager(injector, IRepositoryManager.class);
a1f27e 175         startManager(injector, IProjectManager.class);
269c50 176         startManager(injector, IGitblitManager.class);
JM 177         startManager(injector, IFederationManager.class);
178         startManager(injector, IServicesManager.class);
e24670 179
269c50 180         logger.info("");
JM 181         logger.info("All managers started.");
182         logger.info("");
183     }
e24670 184
269c50 185     protected <X extends IManager> X startManager(ObjectGraph injector, Class<X> clazz) {
JM 186         logManager(clazz);
187         X x = injector.get(clazz);
188         x.start();
189         managers.add(x);
190         return x;
191     }
e24670 192
269c50 193     protected void logManager(Class<? extends IManager> clazz) {
JM 194         logger.info("");
195         logger.info("----[{}]----", clazz.getName());
196     }
197
198     /**
199      * Instantiate and inject all filters and servlets into the container using
200      * the servlet 3 specification.
201      */
202     @Override
203     protected void injectServlets(ServletContext context) {
204         // access restricted servlets
205         serve(context, Constants.GIT_PATH, GitServlet.class, GitFilter.class);
206         serve(context, Constants.PAGES, PagesServlet.class, PagesFilter.class);
207         serve(context, Constants.RPC_PATH, RpcServlet.class, RpcFilter.class);
208         serve(context, Constants.ZIP_PATH, DownloadZipServlet.class, DownloadZipFilter.class);
209         serve(context, Constants.SYNDICATION_PATH, SyndicationServlet.class, SyndicationFilter.class);
210
211         // servlets
212         serve(context, Constants.FEDERATION_PATH, FederationServlet.class);
213         serve(context, Constants.SPARKLESHARE_INVITE_PATH, SparkleShareInviteServlet.class);
214         serve(context, Constants.BRANCH_GRAPH_PATH, BranchGraphServlet.class);
215         file(context, "/robots.txt", RobotsTxtServlet.class);
216         file(context, "/logo.png", LogoServlet.class);
217
218         // optional force basic authentication
219         filter(context, "/*", EnforceAuthenticationFilter.class, null);
220
221         // Wicket
222         String toIgnore = StringUtils.flattenStrings(getRegisteredPaths(), ",");
223         Map<String, String> params = new HashMap<String, String>();
224         params.put(GitblitWicketFilter.FILTER_MAPPING_PARAM, "/*");
225         params.put(GitblitWicketFilter.IGNORE_PATHS_PARAM, toIgnore);
226         filter(context, "/*", GitblitWicketFilter.class, params);
227     }
228
229     /**
230      * Gitblit is being shutdown either because the servlet container is
231      * shutting down or because the servlet container is re-deploying Gitblit.
232      */
233     @Override
234     protected void destroyContext(ServletContext context) {
235         logger.info("Gitblit context destroyed by servlet container.");
236         for (IManager manager : managers) {
237             logger.debug("stopping {}", manager.getClass().getSimpleName());
238             manager.stop();
e24670 239         }
JM 240     }
241
242     /**
243      * Configures Gitblit GO
244      *
245      * @param context
246      * @param settings
247      * @param baseFolder
248      * @param runtimeSettings
249      * @return the base folder
250      */
251     protected File configureGO(
252             ServletContext context,
253             IStoredSettings goSettings,
254             File goBaseFolder,
255             IStoredSettings runtimeSettings) {
269c50 256
JM 257         logger.debug("configuring Gitblit GO");
e24670 258
JM 259         // merge the stored settings into the runtime settings
260         //
261         // if runtimeSettings is also a FileSettings w/o a specified target file,
262         // the target file for runtimeSettings is set to "localSettings".
263         runtimeSettings.merge(goSettings);
264         File base = goBaseFolder;
265         return base;
266     }
267
268
269     /**
270      * Configures a standard WAR instance of Gitblit.
271      *
272      * @param context
273      * @param webxmlSettings
274      * @param contextFolder
275      * @param runtimeSettings
276      * @return the base folder
277      */
278     protected File configureWAR(
279             ServletContext context,
280             WebXmlSettings webxmlSettings,
281             File contextFolder,
282             IStoredSettings runtimeSettings) {
283
284         // Gitblit is running in a standard servlet container
269c50 285         logger.debug("configuring Gitblit WAR");
e24670 286         logger.info("WAR contextFolder is " + ((contextFolder != null) ? contextFolder.getAbsolutePath() : "<empty>"));
JM 287
288         String path = webxmlSettings.getString(Constants.baseFolder, Constants.contextFolder$ + "/WEB-INF/data");
289
290         if (path.contains(Constants.contextFolder$) && contextFolder == null) {
291             // warn about null contextFolder (issue-199)
292             logger.error("");
293             logger.error(MessageFormat.format("\"{0}\" depends on \"{1}\" but \"{2}\" is returning NULL for \"{1}\"!",
294                     Constants.baseFolder, Constants.contextFolder$, context.getServerInfo()));
295             logger.error(MessageFormat.format("Please specify a non-parameterized path for <context-param> {0} in web.xml!!", Constants.baseFolder));
296             logger.error(MessageFormat.format("OR configure your servlet container to specify a \"{0}\" parameter in the context configuration!!", Constants.baseFolder));
297             logger.error("");
298         }
299
300         try {
301             // try to lookup JNDI env-entry for the baseFolder
302             InitialContext ic = new InitialContext();
303             Context env = (Context) ic.lookup("java:comp/env");
304             String val = (String) env.lookup("baseFolder");
305             if (!StringUtils.isEmpty(val)) {
306                 path = val;
307             }
308         } catch (NamingException n) {
309             logger.error("Failed to get JNDI env-entry: " + n.getExplanation());
310         }
311
312         File base = com.gitblit.utils.FileUtils.resolveParameter(Constants.contextFolder$, contextFolder, path);
313         base.mkdirs();
314
315         // try to extract the data folder resource to the baseFolder
316         File localSettings = new File(base, "gitblit.properties");
317         if (!localSettings.exists()) {
318             extractResources(context, "/WEB-INF/data/", base);
319         }
320
321         // delegate all config to baseFolder/gitblit.properties file
322         FileSettings fileSettings = new FileSettings(localSettings.getAbsolutePath());
323
324         // merge the stored settings into the runtime settings
325         //
326         // if runtimeSettings is also a FileSettings w/o a specified target file,
327         // the target file for runtimeSettings is set to "localSettings".
328         runtimeSettings.merge(fileSettings);
329
330         return base;
331     }
332
333     /**
334      * Configures an OpenShift instance of Gitblit.
335      *
336      * @param context
337      * @param webxmlSettings
338      * @param contextFolder
339      * @param runtimeSettings
340      * @return the base folder
341      */
342     private File configureExpress(
343             ServletContext context,
344             WebXmlSettings webxmlSettings,
345             File contextFolder,
346             IStoredSettings runtimeSettings) {
347
348         // Gitblit is running in OpenShift/JBoss
269c50 349         logger.debug("configuring Gitblit Express");
e24670 350         String openShift = System.getenv("OPENSHIFT_DATA_DIR");
JM 351         File base = new File(openShift);
352         logger.info("EXPRESS contextFolder is " + contextFolder.getAbsolutePath());
353
354         // Copy the included scripts to the configured groovy folder
355         String path = webxmlSettings.getString(Keys.groovy.scriptsFolder, "groovy");
356         File localScripts = com.gitblit.utils.FileUtils.resolveParameter(Constants.baseFolder$, base, path);
357         if (!localScripts.exists()) {
358             File warScripts = new File(contextFolder, "/WEB-INF/data/groovy");
359             if (!warScripts.equals(localScripts)) {
360                 try {
361                     com.gitblit.utils.FileUtils.copy(localScripts, warScripts.listFiles());
362                 } catch (IOException e) {
363                     logger.error(MessageFormat.format(
364                             "Failed to copy included Groovy scripts from {0} to {1}",
365                             warScripts, localScripts));
366                 }
367             }
368         }
369
370         // merge the WebXmlSettings into the runtime settings (for backwards-compatibilty)
371         runtimeSettings.merge(webxmlSettings);
372
373         // settings are to be stored in openshift/gitblit.properties
374         File localSettings = new File(base, "gitblit.properties");
375         FileSettings fileSettings = new FileSettings(localSettings.getAbsolutePath());
376
377         // merge the stored settings into the runtime settings
378         //
379         // if runtimeSettings is also a FileSettings w/o a specified target file,
380         // the target file for runtimeSettings is set to "localSettings".
381         runtimeSettings.merge(fileSettings);
382
383         return base;
79d324 384     }
699e71 385
79d324 386     protected void extractResources(ServletContext context, String path, File toDir) {
JM 387         for (String resource : context.getResourcePaths(path)) {
388             // extract the resource to the directory if it does not exist
389             File f = new File(toDir, resource.substring(path.length()));
390             if (!f.exists()) {
df5500 391                 InputStream is = null;
JM 392                 OutputStream os = null;
79d324 393                 try {
JM 394                     if (resource.charAt(resource.length() - 1) == '/') {
395                         // directory
396                         f.mkdirs();
397                         extractResources(context, resource, f);
398                     } else {
399                         // file
400                         f.getParentFile().mkdirs();
df5500 401                         is = context.getResourceAsStream(resource);
JM 402                         os = new FileOutputStream(f);
79d324 403                         byte [] buffer = new byte[4096];
JM 404                         int len = 0;
405                         while ((len = is.read(buffer)) > -1) {
406                             os.write(buffer, 0, len);
407                         }
408                     }
409                 } catch (FileNotFoundException e) {
410                     logger.error("Failed to find resource \"" + resource + "\"", e);
411                 } catch (IOException e) {
412                     logger.error("Failed to copy resource \"" + resource + "\" to " + f, e);
df5500 413                 } finally {
JM 414                     if (is != null) {
415                         try {
416                             is.close();
417                         } catch (IOException e) {
418                             // ignore
419                         }
420                     }
421                     if (os != null) {
422                         try {
423                             os.close();
424                         } catch (IOException e) {
425                             // ignore
426                         }
427                     }
79d324 428                 }
JM 429             }
430         }
116422 431     }
79d324 432 }