Merged in feature/fail_migration_if_to_old (pull request #270)

Feature/fail migration if to old
This commit is contained in:
Rene Pfeuffer
2019-06-13 09:04:02 +00:00
23 changed files with 792 additions and 683 deletions

View File

@@ -75,7 +75,7 @@ public class ScmContextListener extends GuiceResteasyBootstrapServletContextList
* the logger for ScmContextListener
*/
private static final Logger LOG = LoggerFactory.getLogger(ScmContextListener.class);
private final ClassLoader parent;
private final Set<PluginWrapper> plugins;
private Injector injector;
@@ -101,23 +101,16 @@ public class ScmContextListener extends GuiceResteasyBootstrapServletContextList
super.contextInitialized(servletContextEvent);
afterInjectorCreation(servletContextEvent);
}
private void beforeInjectorCreation() {
}
private boolean hasStartupErrors() {
return SCMContext.getContext().getStartupError() != null;
}
@Override
protected List<? extends Module> getModules(ServletContext context) {
if (hasStartupErrors()) {
return getErrorModules();
}
return getDefaultModules(context);
}
private List<? extends Module> getDefaultModules(ServletContext context) {
DefaultPluginLoader pluginLoader = new DefaultPluginLoader(context, parent, plugins);
ClassOverrides overrides = ClassOverrides.findOverrides(pluginLoader.getUberClassLoader());
@@ -132,15 +125,15 @@ public class ScmContextListener extends GuiceResteasyBootstrapServletContextList
);
appendModules(pluginLoader.getExtensionProcessor(), moduleList);
moduleList.addAll(overrides.getModules());
if (SCMContext.getContext().getStage() == Stage.DEVELOPMENT){
moduleList.add(new DebugModule());
}
moduleList.add(new MapperModule());
return moduleList;
return moduleList;
}
private void appendModules(ExtensionProcessor ep, List<Module> moduleList) {
for (Class<? extends Module> module : ep.byExtensionPoint(Module.class)) {
try {
@@ -151,31 +144,27 @@ public class ScmContextListener extends GuiceResteasyBootstrapServletContextList
}
}
}
private List<? extends Module> getErrorModules() {
return Collections.singletonList(new ScmErrorModule());
}
@Override
protected void withInjector(Injector injector) {
this.injector = injector;
}
private void afterInjectorCreation(ServletContextEvent event) {
if (injector != null && !hasStartupErrors()) {
bindEagerSingletons();
initializeServletContextListeners(event);
}
}
}
private void bindEagerSingletons() {
injector.getInstance(EagerSingletonModule.class).initialize(injector);
}
private void initializeServletContextListeners(ServletContextEvent event) {
injector.getInstance(ServletContextListenerHolder.class).contextInitialized(event);
}
@Override
public void contextDestroyed(ServletContextEvent servletContextEvent)
{
@@ -198,7 +187,7 @@ public class ScmContextListener extends GuiceResteasyBootstrapServletContextList
}
}
}
private void closeCloseables() {
// close Scheduler
IOUtil.close(injector.getInstance(Scheduler.class));

View File

@@ -1,74 +0,0 @@
/**
* 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.multibindings.Multibinder;
import com.google.inject.servlet.ServletModule;
import sonia.scm.template.ErrorServlet;
import sonia.scm.template.MustacheTemplateEngine;
import sonia.scm.template.TemplateEngine;
import sonia.scm.template.TemplateEngineFactory;
/**
*
* @author Sebastian Sdorra
*/
public class ScmErrorModule extends ServletModule
{
/**
* Method description
*
*/
@Override
protected void configureServlets()
{
SCMContextProvider context = SCMContext.getContext();
bind(SCMContextProvider.class).toInstance(context);
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);
serve(ScmServletModule.PATTERN_ALL).with(ErrorServlet.class);
}
}

View File

@@ -63,13 +63,13 @@ import sonia.scm.util.IOUtil;
import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.http.HttpServletResponse;
import javax.xml.bind.DataBindingException;
import javax.xml.bind.JAXB;
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 +126,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 +138,26 @@ 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 if (Versions.isTooOld()) {
contextListener = SingleView.view("/templates/too-old.mustache", HttpServletResponse.SC_CONFLICT);
} else {
createMigrationOrNormalContextListener();
Versions.writeNew();
}
}
private void createMigrationOrNormalContextListener() {
ClassLoader cl;
Set<PluginWrapper> plugins;
PluginLoader pluginLoader;
try {
File pluginDirectory = getPluginDirectory();
renameOldPluginsFolder(pluginDirectory);
if (!isCorePluginExtractionDisabled()) {

View File

@@ -0,0 +1,119 @@
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);
}
static ServletContextListener view(String template, int sc) {
ViewController controller = new SimpleViewController(template, request -> {
Object model = ImmutableMap.of(
"contextPath", request.getContextPath()
);
return new View(sc, 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,77 @@
package sonia.scm.boot;
import com.google.common.annotations.VisibleForTesting;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sonia.scm.SCMContext;
import sonia.scm.SCMContextProvider;
import sonia.scm.util.IOUtil;
import sonia.scm.version.Version;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Optional;
class Versions {
private static final Logger LOG = LoggerFactory.getLogger(Versions.class);
private static final Version MIN_VERSION = Version.parse("1.60");
private final SCMContextProvider contextProvider;
@VisibleForTesting
Versions(SCMContextProvider contextProvider) {
this.contextProvider = contextProvider;
}
@VisibleForTesting
boolean isPreviousVersionTooOld() {
return readVersion().map(v -> v.isOlder(MIN_VERSION)).orElse(false);
}
@VisibleForTesting
void writeNewVersion() {
Path config = contextProvider.resolve(Paths.get("config"));
IOUtil.mkdirs(config.toFile());
String version = contextProvider.getVersion();
LOG.debug("write new version {} to file", version);
Path versionFile = config.resolve("version.txt");
try {
Files.write(versionFile, version.getBytes());
} catch (IOException e) {
throw new IllegalStateException("failed to write version file", e);
}
}
private Optional<Version> readVersion() {
Path versionFile = contextProvider.resolve(Paths.get("config", "version.txt"));
if (versionFile.toFile().exists()) {
return Optional.of(readVersionFromFile(versionFile));
}
return Optional.empty();
}
private Version readVersionFromFile(Path versionFile) {
try {
String versionString = new String(Files.readAllBytes(versionFile), StandardCharsets.UTF_8).trim();
LOG.debug("read previous version {} from file", versionString);
return Version.parse(versionString);
} catch (IOException e) {
throw new IllegalStateException("failed to read version file", e);
}
}
static boolean isTooOld() {
return new Versions(SCMContext.getContext()).isPreviousVersionTooOld();
}
static void writeNew() {
new Versions(SCMContext.getContext()).writeNewVersion();
}
}

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);
}

View File

@@ -1,191 +0,0 @@
/**
* 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 org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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 String TEMPALTE = "/error.mustache";
/** Field description */
private static final long serialVersionUID = -3289076078469757874L;
/**
* the logger for ErrorServlet
*/
private static final Logger logger =
LoggerFactory.getLogger(ErrorServlet.class);
//~--- constructors ---------------------------------------------------------
/**
* Constructs ...
*
*
* @param context
* @param templateEngineFactory
*/
@Inject
public ErrorServlet(SCMContextProvider context,
TemplateEngineFactory templateEngineFactory)
{
this.context = context;
this.templateEngineFactory = templateEngineFactory;
}
//~--- 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<String, Object> env = new HashMap<String, Object>();
String error = Util.EMPTY_STRING;
if (context.getStartupError() != null)
{
error = Throwables.getStackTraceAsString(context.getStartupError());
}
env.put("error", error);
TemplateEngine engine = templateEngineFactory.getDefaultEngine();
Template template = engine.getTemplate(TEMPALTE);
if (template != null)
{
template.execute(writer, env);
}
else if (logger.isWarnEnabled())
{
logger.warn("could not find template {}", TEMPALTE);
}
}
finally
{
IOUtil.close(writer);
}
}
//~--- fields ---------------------------------------------------------------
/** Field description */
private final SCMContextProvider context;
/** Field description */
private final TemplateEngineFactory templateEngineFactory;
}

View File

@@ -37,27 +37,22 @@ package sonia.scm.template;
import com.github.mustachejava.Mustache;
import com.github.mustachejava.MustacheException;
import com.google.common.base.Throwables;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import com.google.common.util.concurrent.UncheckedExecutionException;
import com.google.inject.Inject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sonia.scm.Default;
import sonia.scm.plugin.PluginLoader;
//~--- JDK imports ------------------------------------------------------------
import javax.servlet.ServletContext;
import java.io.IOException;
import java.io.Reader;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
import javax.servlet.ServletContext;
//~--- JDK imports ------------------------------------------------------------
/**
*
@@ -67,6 +62,14 @@ import javax.servlet.ServletContext;
public class MustacheTemplateEngine implements TemplateEngine
{
/**
* Used to implement optional injection for the PluginLoader.
* @see <a href="https://github.com/google/guice/wiki/FrequentlyAskedQuestions#how-can-i-inject-optional-parameters-into-a-constructor">Optional Injection</a>
*/
static class PluginLoaderHolder {
@Inject(optional = true) PluginLoader pluginLoader;
}
/** Field description */
public static final TemplateType TYPE = new TemplateType("mustache",
"Mustache", "mustache");
@@ -87,13 +90,12 @@ public class MustacheTemplateEngine implements TemplateEngine
*
*
* @param context
* @param pluginLoader
* @param pluginLoaderHolder
*/
@Inject
public MustacheTemplateEngine(@Default ServletContext context,
PluginLoader pluginLoader)
public MustacheTemplateEngine(@Default ServletContext context, PluginLoaderHolder pluginLoaderHolder)
{
factory = new ServletMustacheFactory(context, pluginLoader);
factory = new ServletMustacheFactory(context, createClassLoader(pluginLoaderHolder.pluginLoader));
ThreadFactory threadFactory =
new ThreadFactoryBuilder().setNameFormat(THREAD_NAME).build();
@@ -101,6 +103,13 @@ public class MustacheTemplateEngine implements TemplateEngine
factory.setExecutorService(Executors.newCachedThreadPool(threadFactory));
}
private ClassLoader createClassLoader(PluginLoader pluginLoader) {
if (pluginLoader == null) {
return Thread.currentThread().getContextClassLoader();
}
return pluginLoader.getUberClassLoader();
}
//~--- get methods ----------------------------------------------------------
/**
@@ -112,12 +121,9 @@ public class MustacheTemplateEngine implements TemplateEngine
*
* @return
*
* @throws IOException
*/
@Override
public Template getTemplate(String templateIdentifier, Reader reader)
throws IOException
{
public Template getTemplate(String templateIdentifier, Reader reader) {
if (logger.isTraceEnabled())
{
logger.trace("try to create mustache template from reader with id {}",

View File

@@ -36,22 +36,17 @@ package sonia.scm.template;
//~--- non-JDK imports --------------------------------------------------------
import com.github.mustachejava.DefaultMustacheFactory;
import com.google.common.base.Charsets;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sonia.scm.plugin.PluginLoader;
//~--- JDK imports ------------------------------------------------------------
import javax.servlet.ServletContext;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import javax.servlet.ServletContext;
//~--- JDK imports ------------------------------------------------------------
/**
*
@@ -73,13 +68,12 @@ public class ServletMustacheFactory extends DefaultMustacheFactory
*
*
* @param servletContext
* @param pluginLoader
* @param classLoader
*/
public ServletMustacheFactory(ServletContext servletContext,
PluginLoader pluginLoader)
public ServletMustacheFactory(ServletContext servletContext, ClassLoader classLoader)
{
this.servletContext = servletContext;
this.pluginLoader = pluginLoader;
this.classLoader = classLoader;
}
//~--- get methods ----------------------------------------------------------
@@ -116,7 +110,7 @@ public class ServletMustacheFactory extends DefaultMustacheFactory
resourceName = resourceName.substring(1);
}
is = pluginLoader.getUberClassLoader().getResourceAsStream(resourceName);
is = classLoader.getResourceAsStream(resourceName);
}
if (is != null)
@@ -144,9 +138,8 @@ public class ServletMustacheFactory extends DefaultMustacheFactory
//~--- fields ---------------------------------------------------------------
/** Field description */
private final PluginLoader pluginLoader;
/** Field description */
private ServletContext servletContext;
private ClassLoader classLoader;
}

View File

@@ -0,0 +1,14 @@
{{< layout}}
{{$title}}SCM-Manager Error{{/title}}
{{$content}}
<h2 class="subtitle">An error occurred during SCM-Manager startup.</h2>
<div class="notification is-danger">
<pre>
{{ error }}
</pre>
</div>
{{/content}}
{{/ layout}}

View File

@@ -1,102 +0,0 @@
<!--
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
-->
<!DOCTYPE html>
<html>
<head>
<title>SCM-Manager support information</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<style type="text/css">
body {
background-color: #ffffff;
margin: 10px;
color: #202020;
font-family: Verdana,Helvetica,Arial,sans-serif;
font-size: 75%;
}
h1, h2, h3, h4, h5 {
font-family: Arial, "Arial CE", "Lucida Grande CE", lucida, "Helvetica CE", sans-serif;
font-weight: bold;
margin: 0px;
padding: 0px;
color: #D20005;
}
h1 {
font-size: 18px;
border-bottom: 1px solid #AFAFAF;
}
h2 {
font-size: 14px;
border-bottom: 1px solid #AFAFAF;
}
a:link, a:visited {
color: #045491;
font-weight: bold;
text-decoration: none;
}
a:link:hover, a:visited:hover {
color: #045491;
font-weight: bold;
text-decoration: underline;
}
table {
border: 0 none;
border-collapse: collapse;
font-size: 100%;
margin: 20px 0;
padding: 20px;
width: 100%;
}
td, th {
padding: 3px;
vertical-align: top;
border: 1px solid #CCCCCC;
text-align: left;
}
.small {
width: 20%;
}
</style>
</head>
<body>
<h1>SCM-Manager Repositories</h1>
<ul>
{{#repositories}}
<li>
<a href="{{url}}">{{name}}</a>
</li>
{{/repositories}}
</ul>
</body>
</html>

View File

@@ -1,150 +0,0 @@
<!--
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
-->
<!DOCTYPE html>
<html>
<head>
<title>SCM-Manager support information</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<style type="text/css">
body {
background-color: #ffffff;
margin: 10px;
color: #202020;
font-family: Verdana,Helvetica,Arial,sans-serif;
font-size: 75%;
}
h1, h2, h3, h4, h5 {
font-family: Arial, "Arial CE", "Lucida Grande CE", lucida, "Helvetica CE", sans-serif;
font-weight: bold;
margin: 0px;
padding: 0px;
color: #D20005;
}
h1 {
font-size: 18px;
border-bottom: 1px solid #AFAFAF;
}
h2 {
font-size: 14px;
border-bottom: 1px solid #AFAFAF;
}
a:link, a:visited {
color: #045491;
font-weight: bold;
text-decoration: none;
}
a:link:hover, a:visited:hover {
color: #045491;
font-weight: bold;
text-decoration: underline;
}
table {
border: 0 none;
border-collapse: collapse;
font-size: 100%;
margin: 20px 0;
padding: 20px;
width: 100%;
}
td, th {
padding: 3px;
vertical-align: top;
border: 1px solid #CCCCCC;
text-align: left;
}
.small {
width: 20%;
}
</style>
</head>
<body>
<h1>SCM-Manager support information</h1>
<p>Information for SCM-Manager support.</p>
<h2>Version</h2>
<ul>
<li>Version: {{version.version}}</li>
<li>Stage: {{version.stage}}</li>
<li>StoreFactory: {{version.storeFactory}}</li>
</ul>
<h2>Configuration</h2>
<ul>
<li>Anonymous Access Enabled: {{configuration.anonymousAccessEnabled}}</li>
<li>Enable Proxy: {{configuration.enableProxy}}</li>
<li>Force Base Url: {{configuration.forceBaseUrl}}</li>
<li>Disable Grouping Grid: {{configuration.disableGroupingGrid}}</li>
<li>Enable Repository Archive: {{configuration.enableRepositoryArchive}}</li>
</ul>
<h2>Installed Plugins</h2>
<ul>
{{#pluginManager.installed}}
<li>{{id}}</li>
{{/pluginManager.installed}}
</ul>
<h2>Runtime</h2>
<ul>
<li>Free Memory: {{runtime.freeMemory}}</li>
<li>Total Memory: {{runtime.totalMemory}}</li>
<li>Max Memory: {{runtime.maxMemory}}</li>
<li>Available Processors: {{runtime.availableProcessors}}</li>
</ul>
<h2>System</h2>
<ul>
<li>OS: {{system.os}}</li>
<li>Architecture: {{system.arch}}</li>
<li>ServletContainer: {{system.container}}</li>
<li>Java: {{system.java}}</li>
<li>Local: {{system.locale}}</li>
<li>TimeZone: {{system.timeZone}}</li>
</ul>
<h2>Repository Handlers</h2>
<ul>
{{#repositoryHandlers}}
<li>{{type.displayName}}/{{type.name}} ({{versionInformation}})</li>
{{/repositoryHandlers}}
</ul>
</body>
</html>

View File

@@ -0,0 +1,14 @@
{{< layout}}
{{$title}}SCM-Manager Error{{/title}}
{{$content}}
<h2 class="subtitle">An error occurred during SCM-Manager startup.</h2>
<p class="notification is-danger">
We cannot migrate your SCM-Manager 1 installation,
because the version is too old.<br />
Please migrate to version 1.60 or newer, before migration to 2.x.
</p>
{{/content}}
{{/ layout}}

View File

@@ -1,102 +0,0 @@
<!--
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
-->
<!DOCTYPE html>
<html>
<head>
<title>SCM-Manager Error</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<style type="text/css">
body {
background-color: #ffffff;
margin: 10px;
color: #202020;
font-family: Verdana,Helvetica,Arial,sans-serif;
font-size: 75%;
}
h1, h2, h3, h4, h5 {
font-family: Arial, "Arial CE", "Lucida Grande CE", lucida, "Helvetica CE", sans-serif;
font-weight: bold;
margin: 0px;
padding: 0px;
color: #D20005;
}
h1 {
font-size: 18px;
border-bottom: 1px solid #AFAFAF;
}
h2 {
font-size: 14px;
border-bottom: 1px solid #AFAFAF;
}
a:link, a:visited {
color: #045491;
font-weight: bold;
text-decoration: none;
}
a:link:hover, a:visited:hover {
color: #045491;
font-weight: bold;
text-decoration: underline;
}
table {
border: 0 none;
border-collapse: collapse;
font-size: 100%;
margin: 20px 0;
padding: 20px;
width: 100%;
}
td, th {
padding: 3px;
vertical-align: top;
border: 1px solid #CCCCCC;
text-align: left;
}
.small {
width: 20%;
}
</style>
</head>
<body>
<h1>SCM-Manager Error</h1>
<p>
There is an error occurred during SCM-Manager startup.
</p>
<pre>
{{error}}
</pre>
</body>
</html>

View File

@@ -0,0 +1,90 @@
package sonia.scm.boot;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import sonia.scm.template.Template;
import sonia.scm.template.TemplateEngine;
import sonia.scm.template.TemplateEngineFactory;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.Mockito.*;
@ExtendWith(MockitoExtension.class)
class SingleViewServletTest {
@Mock
private TemplateEngineFactory templateEngineFactory;
@Mock
private TemplateEngine templateEngine;
@Mock
private Template template;
@Mock
private HttpServletRequest request;
@Mock
private HttpServletResponse response;
@Mock
private PrintWriter writer;
@Mock
private ViewController controller;
@Test
void shouldRenderTheTemplateOnGet() throws IOException {
prepareTemplate("/template");
doReturn(new View(200, "hello")).when(controller).createView(request);
new SingleViewServlet(templateEngineFactory, controller).doGet(request, response);
verifyResponse(200, "hello");
}
private void verifyResponse(int sc, Object model) throws IOException {
verify(response).setStatus(sc);
verify(response).setContentType("text/html");
verify(response).setCharacterEncoding("UTF-8");
verify(template).execute(writer, model);
}
@Test
void shouldRenderTheTemplateOnPost() throws IOException {
prepareTemplate("/template");
doReturn(new View(201, "hello")).when(controller).createView(request);
new SingleViewServlet(templateEngineFactory, controller).doPost(request, response);
verifyResponse(201, "hello");
}
@Test
void shouldThrowIllegalStateExceptionOnIOException() throws IOException {
doReturn("/template").when(controller).getTemplate();
doReturn(templateEngine).when(templateEngineFactory).getEngineByExtension("/template");
doThrow(IOException.class).when(templateEngine).getTemplate("/template");
assertThrows(IllegalStateException.class, () -> new SingleViewServlet(templateEngineFactory, controller));
}
private void prepareTemplate(String templatePath) throws IOException {
doReturn(templateEngine).when(templateEngineFactory).getEngineByExtension(templatePath);
doReturn(template).when(templateEngine).getTemplate(templatePath);
doReturn(templatePath).when(controller).getTemplate();
doReturn(writer).when(response).getWriter();
}
}

View File

@@ -0,0 +1,111 @@
package sonia.scm.boot;
import com.google.inject.Injector;
import com.google.inject.servlet.GuiceFilter;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import javax.servlet.FilterConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.Map;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.*;
@ExtendWith(MockitoExtension.class)
class SingleViewTest {
@Mock
private ServletContext servletContext;
@Mock
private HttpServletRequest request;
@Captor
private ArgumentCaptor<Injector> captor;
private GuiceFilter guiceFilter;
@BeforeEach
void setUpGuiceFilter() throws ServletException {
guiceFilter = new GuiceFilter();
FilterConfig config = mock(FilterConfig.class);
doReturn(servletContext).when(config).getServletContext();
guiceFilter.init(config);
}
@AfterEach
void tearDownGuiceFilter() {
guiceFilter.destroy();
}
@Test
void shouldCreateViewControllerForView() {
ServletContextListener listener = SingleView.view("/my-template", 409);
when(request.getContextPath()).thenReturn("/scm");
ViewController instance = findViewController(listener);
assertThat(instance.getTemplate()).isEqualTo("/my-template");
View view = instance.createView(request);
assertThat(view.getStatusCode()).isEqualTo(409);
}
@Test
void shouldCreateViewControllerForError() {
ServletContextListener listener = SingleView.error(new IOException("awesome io"));
when(request.getContextPath()).thenReturn("/scm");
ViewController instance = findViewController(listener);
assertErrorViewController(instance, "awesome io");
}
@Test
void shouldBindServlets() {
ServletContextListener listener = SingleView.error(new IOException("awesome io"));
Injector injector = findInjector(listener);
assertThat(injector.getInstance(StaticResourceServlet.class)).isNotNull();
assertThat(injector.getInstance(SingleViewServlet.class)).isNotNull();
}
@SuppressWarnings("unchecked")
private void assertErrorViewController(ViewController instance, String contains) {
assertThat(instance.getTemplate()).isEqualTo("/templates/error.mustache");
View view = instance.createView(request);
assertThat(view.getStatusCode()).isEqualTo(500);
assertThat(view.getModel()).isInstanceOfSatisfying(Map.class, map -> {
assertThat(map).containsEntry("contextPath", "/scm");
String error = (String) map.get("error");
assertThat(error).contains(contains);
}
);
}
private ViewController findViewController(ServletContextListener listener) {
Injector injector = findInjector(listener);
return injector.getInstance(ViewController.class);
}
private Injector findInjector(ServletContextListener listener) {
listener.contextInitialized(new ServletContextEvent(servletContext));
verify(servletContext).setAttribute(anyString(), captor.capture());
return captor.getValue();
}
}

View File

@@ -0,0 +1,61 @@
package sonia.scm.boot;
import com.google.common.io.Resources;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import javax.servlet.ServletContext;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.net.URL;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.verify;
@ExtendWith(MockitoExtension.class)
class StaticResourceServletTest {
@Mock
private HttpServletRequest request;
@Mock
private ServletOutputStream stream;
@Mock
private HttpServletResponse response;
@Mock
private ServletContext context;
@Test
void shouldServeResource() throws IOException {
doReturn("/scm").when(request).getContextPath();
doReturn("/scm/resource.txt").when(request).getRequestURI();
doReturn(context).when(request).getServletContext();
URL resource = Resources.getResource("sonia/scm/boot/resource.txt");
doReturn(resource).when(context).getResource("/resource.txt");
doReturn(stream).when(response).getOutputStream();
StaticResourceServlet servlet = new StaticResourceServlet();
servlet.doGet(request, response);
verify(response).setStatus(HttpServletResponse.SC_OK);
}
@Test
void shouldReturnNotFound() throws IOException {
doReturn("/scm").when(request).getContextPath();
doReturn("/scm/resource.txt").when(request).getRequestURI();
doReturn(context).when(request).getServletContext();
StaticResourceServlet servlet = new StaticResourceServlet();
servlet.doGet(request, response);
verify(response).setStatus(HttpServletResponse.SC_NOT_FOUND);
}
}

View File

@@ -0,0 +1,86 @@
package sonia.scm.boot;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junitpioneer.jupiter.TempDirectory;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import sonia.scm.SCMContextProvider;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.doReturn;
@ExtendWith({MockitoExtension.class, TempDirectory.class})
class VersionsTest {
@Mock
private SCMContextProvider contextProvider;
@InjectMocks
private Versions versions;
@Test
void shouldReturnTrueForVersionsPreviousTo160(@TempDirectory.TempDir Path directory) throws IOException {
setVersion(directory, "1.59");
assertThat(versions.isPreviousVersionTooOld()).isTrue();
setVersion(directory, "1.12");
assertThat(versions.isPreviousVersionTooOld()).isTrue();
}
@Test
void shouldReturnFalseForVersion160(@TempDirectory.TempDir Path directory) throws IOException {
setVersion(directory, "1.60");
assertThat(versions.isPreviousVersionTooOld()).isFalse();
}
@Test
void shouldNotFailIfVersionContainsLineBreak(@TempDirectory.TempDir Path directory) throws IOException {
setVersion(directory, "1.59\n");
assertThat(versions.isPreviousVersionTooOld()).isTrue();
}
@Test
void shouldReturnFalseForVersionsNewerAs160(@TempDirectory.TempDir Path directory) throws IOException {
setVersion(directory, "1.61");
assertThat(versions.isPreviousVersionTooOld()).isFalse();
setVersion(directory, "1.82");
assertThat(versions.isPreviousVersionTooOld()).isFalse();
}
@Test
void shouldReturnFalseForNonExistingVersionFile(@TempDirectory.TempDir Path directory) {
setVersionFile(directory.resolve("version.txt"));
assertThat(versions.isPreviousVersionTooOld()).isFalse();
}
@Test
void shouldWriteNewVersion(@TempDirectory.TempDir Path directory) {
Path config = directory.resolve("config");
doReturn(config).when(contextProvider).resolve(Paths.get("config"));
doReturn("2.0.0").when(contextProvider).getVersion();
versions.writeNewVersion();
Path versionFile = config.resolve("version.txt");
assertThat(versionFile).exists().hasContent("2.0.0");
}
private void setVersion(Path directory, String version) throws IOException {
Path file = directory.resolve("version.txt");
Files.write(file, version.getBytes(StandardCharsets.UTF_8));
setVersionFile(file);
}
private void setVersionFile(Path file) {
doReturn(file).when(contextProvider).resolve(Paths.get("config", "version.txt"));
}
}

View File

@@ -35,16 +35,21 @@ package sonia.scm.template;
//~--- non-JDK imports --------------------------------------------------------
import com.google.common.collect.ImmutableMap;
import org.assertj.core.api.Assertions;
import org.junit.Test;
import sonia.scm.plugin.PluginLoader;
import static org.mockito.Mockito.*;
import javax.servlet.ServletContext;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringWriter;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
//~--- JDK imports ------------------------------------------------------------
import java.io.InputStream;
import javax.servlet.ServletContext;
/**
*
* @author Sebastian Sdorra
@@ -68,7 +73,10 @@ public class MustacheTemplateEngineTest extends TemplateEngineTestBase
when(loader.getUberClassLoader()).thenReturn(
Thread.currentThread().getContextClassLoader());
return new MustacheTemplateEngine(context, loader);
MustacheTemplateEngine.PluginLoaderHolder holder = new MustacheTemplateEngine.PluginLoaderHolder();
holder.pluginLoader = loader;
return new MustacheTemplateEngine(context, holder);
}
//~--- get methods ----------------------------------------------------------
@@ -116,4 +124,18 @@ public class MustacheTemplateEngineTest extends TemplateEngineTestBase
return MustacheTemplateEngineTest.class.getResourceAsStream(
"/sonia/scm/template/".concat(resource).concat(".mustache"));
}
@Test
public void testCreateEngineWithoutPluginLoader() throws IOException {
ServletContext context = mock(ServletContext.class);
MustacheTemplateEngine.PluginLoaderHolder holder = new MustacheTemplateEngine.PluginLoaderHolder();
MustacheTemplateEngine engine = new MustacheTemplateEngine(context, holder);
Template template = engine.getTemplate(getTemplateResource());
StringWriter writer = new StringWriter();
template.execute(writer, ImmutableMap.of("name", "World"));
Assertions.assertThat(writer.toString()).isEqualTo("Hello World!");
}
}

View File

@@ -0,0 +1 @@
Resource for testing