create new error module, which displays errors before migration

This commit is contained in:
Sebastian Sdorra
2019-06-12 18:26:58 +02:00
parent bc7402053a
commit 5c7ae749c2
10 changed files with 506 additions and 5 deletions

View File

@@ -69,7 +69,6 @@ import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import java.io.Closeable;
import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
@@ -126,9 +125,7 @@ public class BootstrapContextListener implements ServletContextListener {
public void contextInitialized(ServletContextEvent sce) {
context = sce.getServletContext();
File pluginDirectory = getPluginDirectory();
createContextListener(pluginDirectory);
createContextListener();
contextListener.contextInitialized(sce);
@@ -140,12 +137,23 @@ public class BootstrapContextListener implements ServletContextListener {
}
}
private void createContextListener(File pluginDirectory) {
private void createContextListener() {
Throwable startupError = SCMContext.getContext().getStartupError();
if (startupError != null) {
contextListener = SingleView.error(startupError);
} else {
createMigrationOrNormalContextListener();
}
}
private void createMigrationOrNormalContextListener() {
ClassLoader cl;
Set<PluginWrapper> plugins;
PluginLoader pluginLoader;
try {
File pluginDirectory = getPluginDirectory();
renameOldPluginsFolder(pluginDirectory);
if (!isCorePluginExtractionDisabled()) {

View File

@@ -0,0 +1,109 @@
package sonia.scm.boot;
import com.google.common.base.Throwables;
import com.google.common.collect.ImmutableMap;
import com.google.inject.Guice;
import com.google.inject.Injector;
import com.google.inject.multibindings.Multibinder;
import com.google.inject.servlet.GuiceServletContextListener;
import com.google.inject.servlet.ServletModule;
import sonia.scm.Default;
import sonia.scm.SCMContext;
import sonia.scm.SCMContextProvider;
import sonia.scm.template.MustacheTemplateEngine;
import sonia.scm.template.TemplateEngine;
import sonia.scm.template.TemplateEngineFactory;
import javax.servlet.ServletContext;
import javax.servlet.ServletContextListener;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
final class SingleView {
private SingleView() {
}
static ServletContextListener error(Throwable throwable) {
String error = Throwables.getStackTraceAsString(throwable);
ViewController controller = new SimpleViewController("/templates/error.mustache", request -> {
Object model = ImmutableMap.of(
"contextPath", request.getContextPath(),
"error", error
);
return new View(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, model);
});
return new SingleViewContextListener(controller);
}
private static class SingleViewContextListener extends GuiceServletContextListener {
private final ViewController controller;
private SingleViewContextListener(ViewController controller) {
this.controller = controller;
}
@Override
protected Injector getInjector() {
return Guice.createInjector(new SingleViewModule(controller));
}
}
private static class SingleViewModule extends ServletModule {
private final ViewController viewController;
private SingleViewModule(ViewController viewController) {
this.viewController = viewController;
}
@Override
protected void configureServlets() {
SCMContextProvider context = SCMContext.getContext();
bind(SCMContextProvider.class).toInstance(context);
bind(ViewController.class).toInstance(viewController);
Multibinder<TemplateEngine> engineBinder =
Multibinder.newSetBinder(binder(), TemplateEngine.class);
engineBinder.addBinding().to(MustacheTemplateEngine.class);
bind(TemplateEngine.class).annotatedWith(Default.class).to(
MustacheTemplateEngine.class);
bind(TemplateEngineFactory.class);
bind(ServletContext.class).annotatedWith(Default.class).toInstance(getServletContext());
serve("/images/*", "/styles/*", "/favicon.ico").with(StaticResourceServlet.class);
serve("/*").with(SingleViewServlet.class);
}
}
private static class SimpleViewController implements ViewController {
private final String template;
private final SimpleViewFactory viewFactory;
private SimpleViewController(String template, SimpleViewFactory viewFactory) {
this.template = template;
this.viewFactory = viewFactory;
}
@Override
public String getTemplate() {
return template;
}
@Override
public View createView(HttpServletRequest request) {
return viewFactory.create(request);
}
}
@FunctionalInterface
interface SimpleViewFactory {
View create(HttpServletRequest request);
}
}

View File

@@ -0,0 +1,63 @@
package sonia.scm.boot;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sonia.scm.template.Template;
import sonia.scm.template.TemplateEngine;
import sonia.scm.template.TemplateEngineFactory;
import javax.inject.Inject;
import javax.inject.Singleton;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
@Singleton
public class SingleViewServlet extends HttpServlet {
private static final Logger LOG = LoggerFactory.getLogger(SingleViewServlet.class);
private final Template template;
private final ViewController controller;
@Inject
public SingleViewServlet(TemplateEngineFactory templateEngineFactory, ViewController controller) {
template = createTemplate(templateEngineFactory, controller.getTemplate());
this.controller = controller;
}
private Template createTemplate(TemplateEngineFactory templateEngineFactory, String template) {
TemplateEngine engine = templateEngineFactory.getEngineByExtension(template);
try {
return engine.getTemplate(template);
} catch (IOException e) {
throw new IllegalStateException("failed to parse template: " + template, e);
}
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) {
process(req, resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) {
process(req, resp);
}
private void process(HttpServletRequest request, HttpServletResponse response) {
View view = controller.createView(request);
response.setStatus(view.getStatusCode());
response.setContentType("text/html");
response.setCharacterEncoding("UTF-8");
try (PrintWriter writer = response.getWriter()) {
template.execute(writer, view.getModel());
} catch (IOException ex) {
LOG.error("failed to write view", ex);
}
}
}

View File

@@ -0,0 +1,39 @@
package sonia.scm.boot;
import com.github.sdorra.webresources.CacheControl;
import com.github.sdorra.webresources.WebResourceSender;
import com.google.common.annotations.VisibleForTesting;
import sonia.scm.util.HttpUtil;
import javax.inject.Singleton;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
@Singleton
public class StaticResourceServlet extends HttpServlet {
private final WebResourceSender sender = WebResourceSender.create()
.withGZIP()
.withGZIPMinLength(512)
.withBufferSize(16384)
.withCacheControl(CacheControl.create().noCache());
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {
URL resource = createResourceUrlFromRequest(request);
if (resource != null) {
sender.resource(resource).get(request, response);
} else {
response.setStatus(HttpServletResponse.SC_NOT_FOUND);
}
}
private URL createResourceUrlFromRequest(HttpServletRequest request) throws MalformedURLException {
String uri = HttpUtil.getStrippedURI(request);
return request.getServletContext().getResource(uri);
}
}

View File

@@ -0,0 +1,20 @@
package sonia.scm.boot;
class View {
private final int statusCode;
private final Object model;
View(int statusCode, Object model) {
this.statusCode = statusCode;
this.model = model;
}
int getStatusCode() {
return statusCode;
}
Object getModel() {
return model;
}
}

View File

@@ -0,0 +1,11 @@
package sonia.scm.boot;
import javax.servlet.http.HttpServletRequest;
public interface ViewController {
String getTemplate();
View createView(HttpServletRequest request);
}