diff --git a/scm-core/src/main/java/sonia/scm/BasicContextProvider.java b/scm-core/src/main/java/sonia/scm/BasicContextProvider.java index cc30c81fdd..3a40cf3f2d 100644 --- a/scm-core/src/main/java/sonia/scm/BasicContextProvider.java +++ b/scm-core/src/main/java/sonia/scm/BasicContextProvider.java @@ -89,9 +89,16 @@ public class BasicContextProvider implements SCMContextProvider */ public BasicContextProvider() { - baseDirectory = findBaseDirectory(); - version = loadVersion(); - stage = loadProjectStage(); + try + { + baseDirectory = findBaseDirectory(); + version = loadVersion(); + stage = loadProjectStage(); + } + catch (Throwable ex) + { + this.startupError = ex; + } } //~--- methods -------------------------------------------------------------- @@ -137,6 +144,18 @@ public class BasicContextProvider implements SCMContextProvider return stage; } + /** + * {@inheritDoc} + * + * + * @return + */ + @Override + public Throwable getStartupError() + { + return startupError; + } + /** * Returns the version of the SCM-Manager. If the version is not set, the * {@link #DEFAULT_VERSION} is returned. @@ -182,7 +201,16 @@ public class BasicContextProvider implements SCMContextProvider if (!directory.exists() &&!directory.mkdirs()) { - throw new IllegalStateException("could not create directory"); + String msg = "could not create home directory at ".concat( + directory.getAbsolutePath()); + + // do not use logger + // http://www.slf4j.org/codes.html#substituteLogger + System.err.println("==================================================="); + System.err.append("Error: ").println(msg); + System.err.println("==================================================="); + + throw new IllegalStateException(msg); } return directory; @@ -319,6 +347,9 @@ public class BasicContextProvider implements SCMContextProvider /** stage of the current SCM-Manager instance */ private Stage stage; + /** startup exception */ + private Throwable startupError; + /** the version of the SCM-Manager */ private String version; } diff --git a/scm-core/src/main/java/sonia/scm/SCMContextProvider.java b/scm-core/src/main/java/sonia/scm/SCMContextProvider.java index f8906b02d7..18328403fe 100644 --- a/scm-core/src/main/java/sonia/scm/SCMContextProvider.java +++ b/scm-core/src/main/java/sonia/scm/SCMContextProvider.java @@ -74,6 +74,16 @@ public interface SCMContextProvider extends Closeable */ public Stage getStage(); + /** + * Returns a exception which is occurred on context startup. + * The method returns null if the start was successful. + * + * + * @return startup exception of null + * @since 1.14 + */ + public Throwable getStartupError(); + /** * Returns the version of the SCM-Manager. * diff --git a/scm-webapp/src/main/java/sonia/scm/ScmContextListener.java b/scm-webapp/src/main/java/sonia/scm/ScmContextListener.java index 7f6050becb..2258a82dd1 100644 --- a/scm-webapp/src/main/java/sonia/scm/ScmContextListener.java +++ b/scm-webapp/src/main/java/sonia/scm/ScmContextListener.java @@ -74,29 +74,29 @@ public class ScmContextListener extends GuiceServletContextListener @Override public void contextDestroyed(ServletContextEvent servletContextEvent) { - if (injector != null) + if ((globalInjector != null) && startupError) { // close RepositoryManager - IOUtil.close(injector.getInstance(RepositoryManager.class)); + IOUtil.close(globalInjector.getInstance(RepositoryManager.class)); // close Authenticator - IOUtil.close(injector.getInstance(AuthenticationManager.class)); + IOUtil.close(globalInjector.getInstance(AuthenticationManager.class)); // close GroupManager - IOUtil.close(injector.getInstance(GroupManager.class)); + IOUtil.close(globalInjector.getInstance(GroupManager.class)); // close UserManager - IOUtil.close(injector.getInstance(UserManager.class)); + IOUtil.close(globalInjector.getInstance(UserManager.class)); // close StoreFactory - IOUtil.close(injector.getInstance(StoreFactory.class)); + IOUtil.close(globalInjector.getInstance(StoreFactory.class)); // close CacheManager - IOUtil.close(injector.getInstance(CacheManager.class)); + IOUtil.close(globalInjector.getInstance(CacheManager.class)); // remove thread local store - injector.getInstance(LocalSecurityContextHolder.class).destroy(); + globalInjector.getInstance(LocalSecurityContextHolder.class).destroy(); } super.contextDestroyed(servletContextEvent); @@ -111,9 +111,17 @@ public class ScmContextListener extends GuiceServletContextListener @Override public void contextInitialized(ServletContextEvent servletContextEvent) { - ScmUpgradeHandler upgradeHandler = new ScmUpgradeHandler(); + if (SCMContext.getContext().getStartupError() == null) + { + ScmUpgradeHandler upgradeHandler = new ScmUpgradeHandler(); + + upgradeHandler.doUpgrade(); + } + else + { + startupError = true; + } - upgradeHandler.doUpgrade(); super.contextInitialized(servletContextEvent); } @@ -127,6 +135,26 @@ public class ScmContextListener extends GuiceServletContextListener */ @Override protected Injector getInjector() + { + if (startupError) + { + globalInjector = getErrorInjector(); + } + else + { + globalInjector = getDefaultInjector(); + } + + return globalInjector; + } + + /** + * Method description + * + * + * @return + */ + private Injector getDefaultInjector() { PluginLoader pluginLoader = new DefaultPluginLoader(); BindingExtensionProcessor bindExtProcessor = @@ -140,8 +168,8 @@ public class ScmContextListener extends GuiceServletContextListener new ArrayList(bindExtProcessor.getModuleSet()); moduleList.add(0, main); - injector = Guice.createInjector(moduleList); + Injector injector = Guice.createInjector(moduleList); SCMContextProvider context = SCMContext.getContext(); // init StoreFactory @@ -173,8 +201,22 @@ public class ScmContextListener extends GuiceServletContextListener return injector; } + /** + * Method description + * + * + * @return + */ + private Injector getErrorInjector() + { + return Guice.createInjector(new ScmErrorModule()); + } + //~--- fields --------------------------------------------------------------- /** Field description */ - private Injector injector; + private Injector globalInjector; + + /** Field description */ + private boolean startupError = false; } diff --git a/scm-webapp/src/main/java/sonia/scm/ScmErrorModule.java b/scm-webapp/src/main/java/sonia/scm/ScmErrorModule.java new file mode 100644 index 0000000000..5057327c21 --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/ScmErrorModule.java @@ -0,0 +1,62 @@ +/** + * Copyright (c) 2010, Sebastian Sdorra All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. 2. Redistributions in + * binary form must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. 3. Neither the name of SCM-Manager; + * nor the names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * http://bitbucket.org/sdorra/scm-manager + * + */ + + + +package sonia.scm; + +//~--- non-JDK imports -------------------------------------------------------- + +import com.google.inject.servlet.ServletModule; + +import sonia.scm.template.ErrorServlet; +import sonia.scm.template.FreemarkerTemplateHandler; +import sonia.scm.template.TemplateHandler; + +/** + * + * @author Sebastian Sdorra + */ +public class ScmErrorModule extends ServletModule +{ + + /** + * Method description + * + */ + @Override + protected void configureServlets() + { + SCMContextProvider context = SCMContext.getContext(); + + bind(SCMContextProvider.class).toInstance(context); + bind(TemplateHandler.class).to(FreemarkerTemplateHandler.class); + serve(ScmServletModule.PATTERN_ALL).with(ErrorServlet.class); + } +} diff --git a/scm-webapp/src/main/java/sonia/scm/template/ErrorServlet.java b/scm-webapp/src/main/java/sonia/scm/template/ErrorServlet.java new file mode 100644 index 0000000000..1d66f8b399 --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/template/ErrorServlet.java @@ -0,0 +1,165 @@ +/** + * Copyright (c) 2010, Sebastian Sdorra All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. 2. Redistributions in + * binary form must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. 3. Neither the name of SCM-Manager; + * nor the names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * http://bitbucket.org/sdorra/scm-manager + * + */ + + + +package sonia.scm.template; + +//~--- non-JDK imports -------------------------------------------------------- + +import com.google.common.base.Throwables; +import com.google.inject.Inject; +import com.google.inject.Singleton; + +import sonia.scm.SCMContextProvider; +import sonia.scm.util.IOUtil; +import sonia.scm.util.Util; + +//~--- JDK imports ------------------------------------------------------------ + +import java.io.IOException; +import java.io.PrintWriter; + +import java.util.HashMap; +import java.util.Map; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +/** + * + * @author Sebastian Sdorra + */ +@Singleton +public class ErrorServlet extends HttpServlet +{ + + /** Field description */ + private static final long serialVersionUID = -3289076078469757874L; + + //~--- constructors --------------------------------------------------------- + + /** + * Constructs ... + * + * + * @param context + * @param handler + */ + @Inject + public ErrorServlet(SCMContextProvider context, TemplateHandler handler) + { + this.context = context; + this.handler = handler; + } + + //~--- methods -------------------------------------------------------------- + + /** + * Method description + * + * + * @param request + * @param response + * + * @throws IOException + * @throws ServletException + */ + @Override + protected void doGet(HttpServletRequest request, HttpServletResponse response) + throws ServletException, IOException + { + processRequest(request, response); + } + + /** + * Method description + * + * + * @param request + * @param response + * + * @throws IOException + * @throws ServletException + */ + @Override + protected void doPost(HttpServletRequest request, + HttpServletResponse response) + throws ServletException, IOException + { + processRequest(request, response); + } + + /** + * Method description + * + * + * @param request + * @param response + * + * @throws IOException + * @throws ServletException + */ + private void processRequest(HttpServletRequest request, + HttpServletResponse response) + throws ServletException, IOException + { + PrintWriter writer = null; + + try + { + writer = response.getWriter(); + + Map env = new HashMap(); + String error = Util.EMPTY_STRING; + + if (context.getStartupError() != null) + { + error = Throwables.getStackTraceAsString(context.getStartupError()); + } + + env.put("error", error); + handler.render("/error.html", writer, env); + } + finally + { + IOUtil.close(context); + } + } + + //~--- fields --------------------------------------------------------------- + + /** Field description */ + private SCMContextProvider context; + + /** Field description */ + private TemplateHandler handler; +} diff --git a/scm-webapp/src/main/webapp/error.html b/scm-webapp/src/main/webapp/error.html new file mode 100644 index 0000000000..5dc166deec --- /dev/null +++ b/scm-webapp/src/main/webapp/error.html @@ -0,0 +1,101 @@ + + + + + + SCM-Manager Error + + + + +

SCM-Manager Error

+ +

+ There is an error occurred during SCM-Manager startup. +

+ +
+      ${error}
+    
+ + +