diff --git a/Jenkinsfile b/Jenkinsfile index 6a9fa7e044..c759de5733 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -37,7 +37,7 @@ node('docker') { } stage('Integration Test') { - mvn 'verify -Pit -pl :scm-webapp,:scm-it -Dmaven.test.failure.ignore=true' + mvn 'verify -Pit -pl :scm-webapp,:scm-it -Dmaven.test.failure.ignore=true -DClassLoaderLeakPreventor.threadWaitMs=10' } stage('SonarQube') { diff --git a/pom.xml b/pom.xml index 7a8e16b869..98ddfde58a 100644 --- a/pom.xml +++ b/pom.xml @@ -220,7 +220,13 @@ org.jboss.resteasy - resteasy-jaxrs + resteasy-core + ${resteasy.version} + + + + org.jboss.resteasy + resteasy-core-spi ${resteasy.version} @@ -831,7 +837,7 @@ 3.0.1 2.1.1 - 3.6.2.Final + 4.4.1.Final 1.19.4 2.11.1 2.10.0 @@ -839,7 +845,7 @@ 2.3.0 - 1.6.0 + 1.6.1 9.4.22.v20191022 diff --git a/scm-core/pom.xml b/scm-core/pom.xml index 8437b256b2..4e8e315253 100644 --- a/scm-core/pom.xml +++ b/scm-core/pom.xml @@ -98,7 +98,7 @@ org.jboss.resteasy - resteasy-jaxrs + resteasy-core test diff --git a/scm-plugins/pom.xml b/scm-plugins/pom.xml index 1611633449..e6b2929bd2 100644 --- a/scm-plugins/pom.xml +++ b/scm-plugins/pom.xml @@ -72,7 +72,13 @@ org.jboss.resteasy - resteasy-jaxrs + resteasy-core + test + + + + org.jboss.resteasy + resteasy-core-spi test diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/api/v2/resources/GitConfigResourceTest.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/api/v2/resources/GitConfigResourceTest.java index c25b5c5b60..a561914b42 100644 --- a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/api/v2/resources/GitConfigResourceTest.java +++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/api/v2/resources/GitConfigResourceTest.java @@ -2,14 +2,11 @@ package sonia.scm.api.v2.resources; import com.github.sdorra.shiro.ShiroRule; import com.github.sdorra.shiro.SubjectAware; -import org.jboss.resteasy.core.Dispatcher; -import org.jboss.resteasy.mock.MockDispatcherFactory; import org.jboss.resteasy.mock.MockHttpRequest; import org.jboss.resteasy.mock.MockHttpResponse; import org.junit.Before; import org.junit.Rule; import org.junit.Test; -import org.junit.rules.ExpectedException; import org.junit.runner.RunWith; import org.mockito.Answers; import org.mockito.ArgumentCaptor; @@ -27,6 +24,7 @@ import sonia.scm.repository.RepositoryManager; import sonia.scm.store.ConfigurationStore; import sonia.scm.store.ConfigurationStoreFactory; import sonia.scm.web.GitVndMediaType; +import sonia.scm.web.RestDispatcher; import javax.servlet.http.HttpServletResponse; import java.io.UnsupportedEncodingException; @@ -52,10 +50,7 @@ public class GitConfigResourceTest { @Rule public ShiroRule shiro = new ShiroRule(); - @Rule - public ExpectedException thrown = ExpectedException.none(); - - private Dispatcher dispatcher = MockDispatcherFactory.createDispatcher(); + private RestDispatcher dispatcher = new RestDispatcher(); private final URI baseUri = URI.create("/"); @@ -89,7 +84,7 @@ public class GitConfigResourceTest { when(repositoryHandler.getConfig()).thenReturn(gitConfig); GitRepositoryConfigResource gitRepositoryConfigResource = new GitRepositoryConfigResource(repositoryConfigMapper, repositoryManager, new GitRepositoryConfigStoreProvider(configurationStoreFactory)); GitConfigResource gitConfigResource = new GitConfigResource(dtoToConfigMapper, configToDtoMapper, repositoryHandler, of(gitRepositoryConfigResource)); - dispatcher.getRegistry().addSingletonResource(gitConfigResource); + dispatcher.addSingletonResource(gitConfigResource); when(scmPathInfoStore.get().getApiRestUri()).thenReturn(baseUri); } @@ -137,10 +132,11 @@ public class GitConfigResourceTest { @Test @SubjectAware(username = "writeOnly") - public void shouldNotGetConfigWhenNotAuthorized() throws URISyntaxException { - thrown.expectMessage("Subject does not have permission [configuration:read:git]"); + public void shouldNotGetConfigWhenNotAuthorized() throws URISyntaxException, UnsupportedEncodingException { + MockHttpResponse response = get(); - get(); + assertEquals("Subject does not have permission [configuration:read:git]", response.getContentAsString()); + assertEquals(HttpServletResponse.SC_FORBIDDEN, response.getStatus()); } @Test @@ -152,10 +148,11 @@ public class GitConfigResourceTest { @Test @SubjectAware(username = "readOnly") - public void shouldNotUpdateConfigWhenNotAuthorized() throws URISyntaxException { - thrown.expectMessage("Subject does not have permission [configuration:write:git]"); + public void shouldNotUpdateConfigWhenNotAuthorized() throws URISyntaxException, UnsupportedEncodingException { + MockHttpResponse response = put(); - put(); + assertEquals("Subject does not have permission [configuration:write:git]", response.getContentAsString()); + assertEquals(HttpServletResponse.SC_FORBIDDEN, response.getStatus()); } @Test diff --git a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigAutoConfigurationResourceTest.java b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigAutoConfigurationResourceTest.java index 1f88bfe665..5a3b809973 100644 --- a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigAutoConfigurationResourceTest.java +++ b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigAutoConfigurationResourceTest.java @@ -2,14 +2,11 @@ package sonia.scm.api.v2.resources; import com.github.sdorra.shiro.ShiroRule; import com.github.sdorra.shiro.SubjectAware; -import org.jboss.resteasy.core.Dispatcher; -import org.jboss.resteasy.mock.MockDispatcherFactory; import org.jboss.resteasy.mock.MockHttpRequest; import org.jboss.resteasy.mock.MockHttpResponse; import org.junit.Before; import org.junit.Rule; import org.junit.Test; -import org.junit.rules.ExpectedException; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.InjectMocks; @@ -18,12 +15,15 @@ import org.mockito.junit.MockitoJUnitRunner; import sonia.scm.repository.HgConfig; import sonia.scm.repository.HgRepositoryHandler; import sonia.scm.web.HgVndMediaType; +import sonia.scm.web.RestDispatcher; import javax.inject.Provider; import javax.servlet.http.HttpServletResponse; import java.net.URISyntaxException; -import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -37,10 +37,7 @@ public class HgConfigAutoConfigurationResourceTest { @Rule public ShiroRule shiro = new ShiroRule(); - @Rule - public ExpectedException thrown = ExpectedException.none(); - - private Dispatcher dispatcher = MockDispatcherFactory.createDispatcher(); + private RestDispatcher dispatcher = new RestDispatcher(); @InjectMocks private HgConfigDtoToHgConfigMapperImpl dtoToConfigMapper; @@ -57,7 +54,7 @@ public class HgConfigAutoConfigurationResourceTest { new HgConfigAutoConfigurationResource(dtoToConfigMapper, repositoryHandler); when(resourceProvider.get()).thenReturn(resource); - dispatcher.getRegistry().addSingletonResource( + dispatcher.addSingletonResource( new HgConfigResource(null, null, null, null, resourceProvider, null)); } @@ -76,9 +73,10 @@ public class HgConfigAutoConfigurationResourceTest { @Test @SubjectAware(username = "readOnly") public void shouldNotSetDefaultConfigAndInstallHgWhenNotAuthorized() throws Exception { - thrown.expectMessage("Subject does not have permission [configuration:write:hg]"); + MockHttpResponse response = put(null); - put(null); + assertEquals("Subject does not have permission [configuration:write:hg]", response.getContentAsString()); + assertEquals(HttpServletResponse.SC_FORBIDDEN, response.getStatus()); } @Test @@ -95,9 +93,10 @@ public class HgConfigAutoConfigurationResourceTest { @Test @SubjectAware(username = "readOnly") public void shouldNotUpdateConfigAndInstallHgWhenNotAuthorized() throws Exception { - thrown.expectMessage("Subject does not have permission [configuration:write:hg]"); + MockHttpResponse response = put("{\"disabled\":true}"); - put("{\"disabled\":true}"); + assertEquals("Subject does not have permission [configuration:write:hg]", response.getContentAsString()); + assertEquals(HttpServletResponse.SC_FORBIDDEN, response.getStatus()); } private MockHttpResponse put(String content) throws URISyntaxException { diff --git a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigInstallationsResourceTest.java b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigInstallationsResourceTest.java index bcd9543d28..e77ed917ed 100644 --- a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigInstallationsResourceTest.java +++ b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigInstallationsResourceTest.java @@ -2,19 +2,17 @@ package sonia.scm.api.v2.resources; import com.github.sdorra.shiro.ShiroRule; import com.github.sdorra.shiro.SubjectAware; -import org.jboss.resteasy.core.Dispatcher; -import org.jboss.resteasy.mock.MockDispatcherFactory; import org.jboss.resteasy.mock.MockHttpRequest; import org.jboss.resteasy.mock.MockHttpResponse; import org.junit.Before; import org.junit.Rule; import org.junit.Test; -import org.junit.rules.ExpectedException; import org.junit.runner.RunWith; import org.mockito.Answers; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; +import sonia.scm.web.RestDispatcher; import javax.inject.Provider; import javax.servlet.http.HttpServletResponse; @@ -35,10 +33,7 @@ public class HgConfigInstallationsResourceTest { @Rule public ShiroRule shiro = new ShiroRule(); - @Rule - public ExpectedException thrown = ExpectedException.none(); - - private Dispatcher dispatcher = MockDispatcherFactory.createDispatcher(); + private RestDispatcher dispatcher = new RestDispatcher(); private final URI baseUri = URI.create("/"); @@ -57,7 +52,7 @@ public class HgConfigInstallationsResourceTest { HgConfigInstallationsResource resource = new HgConfigInstallationsResource(mapper); when(resourceProvider.get()).thenReturn(resource); - dispatcher.getRegistry().addSingletonResource( + dispatcher.addSingletonResource( new HgConfigResource(null, null, null, null, null, resourceProvider)); @@ -82,9 +77,10 @@ public class HgConfigInstallationsResourceTest { @Test @SubjectAware(username = "writeOnly") public void shouldNotGetHgInstallationsWhenNotAuthorized() throws Exception { - thrown.expectMessage("Subject does not have permission [configuration:read:hg]"); + MockHttpResponse response = get("hg"); - get("hg"); + assertEquals("Subject does not have permission [configuration:read:hg]", response.getContentAsString()); + assertEquals(HttpServletResponse.SC_FORBIDDEN, response.getStatus()); } @Test @@ -104,9 +100,10 @@ public class HgConfigInstallationsResourceTest { @Test @SubjectAware(username = "writeOnly") public void shouldNotGetPythonInstallationsWhenNotAuthorized() throws Exception { - thrown.expectMessage("Subject does not have permission [configuration:read:hg]"); + MockHttpResponse response = get("python"); - get("python"); + assertEquals("Subject does not have permission [configuration:read:hg]", response.getContentAsString()); + assertEquals(HttpServletResponse.SC_FORBIDDEN, response.getStatus()); } private MockHttpResponse get(String path) throws URISyntaxException { diff --git a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigPackageResourceTest.java b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigPackageResourceTest.java index 473ddfe4b4..9cd0e4b17e 100644 --- a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigPackageResourceTest.java +++ b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigPackageResourceTest.java @@ -5,14 +5,11 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ObjectNode; import com.github.sdorra.shiro.ShiroRule; import com.github.sdorra.shiro.SubjectAware; -import org.jboss.resteasy.core.Dispatcher; -import org.jboss.resteasy.mock.MockDispatcherFactory; import org.jboss.resteasy.mock.MockHttpRequest; import org.jboss.resteasy.mock.MockHttpResponse; import org.junit.Before; import org.junit.Rule; import org.junit.Test; -import org.junit.rules.ExpectedException; import org.junit.runner.RunWith; import org.mockito.Answers; import org.mockito.InjectMocks; @@ -23,6 +20,7 @@ import sonia.scm.installer.HgPackageReader; import sonia.scm.net.ahc.AdvancedHttpClient; import sonia.scm.repository.HgConfig; import sonia.scm.repository.HgRepositoryHandler; +import sonia.scm.web.RestDispatcher; import javax.inject.Provider; import javax.servlet.http.HttpServletResponse; @@ -49,10 +47,7 @@ public class HgConfigPackageResourceTest { @Rule public ShiroRule shiro = new ShiroRule(); - @Rule - public ExpectedException thrown = ExpectedException.none(); - - private Dispatcher dispatcher = MockDispatcherFactory.createDispatcher(); + private RestDispatcher dispatcher = new RestDispatcher(); private final URI baseUri = java.net.URI.create("/"); @@ -113,9 +108,10 @@ public class HgConfigPackageResourceTest { @Test @SubjectAware(username = "writeOnly") public void shouldNotGetPackagesWhenNotAuthorized() throws Exception { - thrown.expectMessage("Subject does not have permission [configuration:read:hg]"); + MockHttpResponse response = get(); - get(); + assertEquals("Subject does not have permission [configuration:read:hg]", response.getContentAsString()); + assertEquals(HttpServletResponse.SC_FORBIDDEN, response.getStatus()); } @Test @@ -158,9 +154,10 @@ public class HgConfigPackageResourceTest { @Test @SubjectAware(username = "readOnly") public void shouldNotInstallPackageWhenNotAuthorized() throws Exception { - thrown.expectMessage("Subject does not have permission [configuration:write:hg]"); + MockHttpResponse response = put("don-t-care"); - put("don-t-care"); + assertEquals("Subject does not have permission [configuration:write:hg]", response.getContentAsString()); + assertEquals(HttpServletResponse.SC_FORBIDDEN, response.getStatus()); } private List createPackages() { @@ -191,7 +188,7 @@ public class HgConfigPackageResourceTest { new HgConfigPackageResource(hgPackageReader, advancedHttpClient, repositoryHandler, mapper); when(hgConfigPackageResourceProvider.get()).thenReturn(hgConfigPackageResource); - dispatcher.getRegistry().addSingletonResource( + dispatcher.addSingletonResource( new HgConfigResource(null, null, null, hgConfigPackageResourceProvider, null, null)); } diff --git a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigResourceTest.java b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigResourceTest.java index e0253ad86a..eae03f729d 100644 --- a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigResourceTest.java +++ b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigResourceTest.java @@ -4,14 +4,11 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ObjectNode; import com.github.sdorra.shiro.ShiroRule; import com.github.sdorra.shiro.SubjectAware; -import org.jboss.resteasy.core.Dispatcher; -import org.jboss.resteasy.mock.MockDispatcherFactory; import org.jboss.resteasy.mock.MockHttpRequest; import org.jboss.resteasy.mock.MockHttpResponse; import org.junit.Before; import org.junit.Rule; import org.junit.Test; -import org.junit.rules.ExpectedException; import org.junit.runner.RunWith; import org.mockito.Answers; import org.mockito.InjectMocks; @@ -20,6 +17,7 @@ import org.mockito.junit.MockitoJUnitRunner; import sonia.scm.repository.HgConfig; import sonia.scm.repository.HgRepositoryHandler; import sonia.scm.web.HgVndMediaType; +import sonia.scm.web.RestDispatcher; import javax.inject.Provider; import javax.servlet.http.HttpServletResponse; @@ -43,10 +41,7 @@ public class HgConfigResourceTest { @Rule public ShiroRule shiro = new ShiroRule(); - @Rule - public ExpectedException thrown = ExpectedException.none(); - - private Dispatcher dispatcher = MockDispatcherFactory.createDispatcher(); + private RestDispatcher dispatcher = new RestDispatcher(); private final URI baseUri = URI.create("/"); @@ -78,7 +73,7 @@ public class HgConfigResourceTest { HgConfigResource gitConfigResource = new HgConfigResource(dtoToConfigMapper, configToDtoMapper, repositoryHandler, packagesResource, autoconfigResource, installationsResource); - dispatcher.getRegistry().addSingletonResource(gitConfigResource); + dispatcher.addSingletonResource(gitConfigResource); when(scmPathInfoStore.get().getApiRestUri()).thenReturn(baseUri); } @@ -120,10 +115,11 @@ public class HgConfigResourceTest { @Test @SubjectAware(username = "writeOnly") - public void shouldNotGetConfigWhenNotAuthorized() throws URISyntaxException { - thrown.expectMessage("Subject does not have permission [configuration:read:hg]"); + public void shouldNotGetConfigWhenNotAuthorized() throws URISyntaxException, UnsupportedEncodingException { + MockHttpResponse response = get(); - get(); + assertEquals("Subject does not have permission [configuration:read:hg]", response.getContentAsString()); + assertEquals(HttpServletResponse.SC_FORBIDDEN, response.getStatus()); } @Test @@ -135,10 +131,11 @@ public class HgConfigResourceTest { @Test @SubjectAware(username = "readOnly") - public void shouldNotUpdateConfigWhenNotAuthorized() throws URISyntaxException { - thrown.expectMessage("Subject does not have permission [configuration:write:hg]"); + public void shouldNotUpdateConfigWhenNotAuthorized() throws URISyntaxException, UnsupportedEncodingException { + MockHttpResponse response = put(); - put(); + assertEquals("Subject does not have permission [configuration:write:hg]", response.getContentAsString()); + assertEquals(HttpServletResponse.SC_FORBIDDEN, response.getStatus()); } private MockHttpResponse get() throws URISyntaxException { diff --git a/scm-plugins/scm-svn-plugin/src/test/java/sonia/scm/api/v2/resources/SvnConfigResourceTest.java b/scm-plugins/scm-svn-plugin/src/test/java/sonia/scm/api/v2/resources/SvnConfigResourceTest.java index f7ccf039b2..ee2918160c 100644 --- a/scm-plugins/scm-svn-plugin/src/test/java/sonia/scm/api/v2/resources/SvnConfigResourceTest.java +++ b/scm-plugins/scm-svn-plugin/src/test/java/sonia/scm/api/v2/resources/SvnConfigResourceTest.java @@ -4,14 +4,11 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ObjectNode; import com.github.sdorra.shiro.ShiroRule; import com.github.sdorra.shiro.SubjectAware; -import org.jboss.resteasy.core.Dispatcher; -import org.jboss.resteasy.mock.MockDispatcherFactory; import org.jboss.resteasy.mock.MockHttpRequest; import org.jboss.resteasy.mock.MockHttpResponse; import org.junit.Before; import org.junit.Rule; import org.junit.Test; -import org.junit.rules.ExpectedException; import org.junit.runner.RunWith; import org.mockito.Answers; import org.mockito.InjectMocks; @@ -19,6 +16,7 @@ import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; import sonia.scm.repository.SvnConfig; import sonia.scm.repository.SvnRepositoryHandler; +import sonia.scm.web.RestDispatcher; import sonia.scm.web.SvnVndMediaType; import javax.servlet.http.HttpServletResponse; @@ -42,10 +40,7 @@ public class SvnConfigResourceTest { @Rule public ShiroRule shiro = new ShiroRule(); - @Rule - public ExpectedException thrown = ExpectedException.none(); - - private Dispatcher dispatcher = MockDispatcherFactory.createDispatcher(); + private RestDispatcher dispatcher = new RestDispatcher(); private final URI baseUri = URI.create("/"); @@ -66,7 +61,7 @@ public class SvnConfigResourceTest { SvnConfig gitConfig = createConfiguration(); when(repositoryHandler.getConfig()).thenReturn(gitConfig); SvnConfigResource gitConfigResource = new SvnConfigResource(dtoToConfigMapper, configToDtoMapper, repositoryHandler); - dispatcher.getRegistry().addSingletonResource(gitConfigResource); + dispatcher.addSingletonResource(gitConfigResource); when(scmPathInfoStore.get().getApiRestUri()).thenReturn(baseUri); } @@ -108,10 +103,11 @@ public class SvnConfigResourceTest { @Test @SubjectAware(username = "writeOnly") - public void shouldNotGetConfigWhenNotAuthorized() throws URISyntaxException { - thrown.expectMessage("Subject does not have permission [configuration:read:svn]"); + public void shouldNotGetConfigWhenNotAuthorized() throws URISyntaxException, UnsupportedEncodingException { + MockHttpResponse response = get(); - get(); + assertEquals("Subject does not have permission [configuration:read:svn]", response.getContentAsString()); + assertEquals(HttpServletResponse.SC_FORBIDDEN, response.getStatus()); } @Test @@ -123,10 +119,11 @@ public class SvnConfigResourceTest { @Test @SubjectAware(username = "readOnly") - public void shouldNotUpdateConfigWhenNotAuthorized() throws URISyntaxException { - thrown.expectMessage("Subject does not have permission [configuration:write:svn]"); + public void shouldNotUpdateConfigWhenNotAuthorized() throws URISyntaxException, UnsupportedEncodingException { + MockHttpResponse response = put(); - put(); + assertEquals("Subject does not have permission [configuration:write:svn]", response.getContentAsString()); + assertEquals(HttpServletResponse.SC_FORBIDDEN, response.getStatus()); } private MockHttpResponse get() throws URISyntaxException { diff --git a/scm-test/pom.xml b/scm-test/pom.xml index bddbcf2c85..d36b8c55f8 100644 --- a/scm-test/pom.xml +++ b/scm-test/pom.xml @@ -42,6 +42,20 @@ ${mockito.version} + + org.jboss.resteasy + resteasy-core-spi + + + org.jboss.resteasy + resteasy-core + + + org.jboss.resteasy + resteasy-jackson2-provider + ${resteasy.version} + + org.slf4j slf4j-simple diff --git a/scm-test/src/main/java/sonia/scm/web/RestDispatcher.java b/scm-test/src/main/java/sonia/scm/web/RestDispatcher.java new file mode 100644 index 0000000000..29d57f204f --- /dev/null +++ b/scm-test/src/main/java/sonia/scm/web/RestDispatcher.java @@ -0,0 +1,111 @@ +package sonia.scm.web; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.apache.shiro.authz.AuthorizationException; +import org.apache.shiro.authz.UnauthorizedException; +import org.jboss.resteasy.mock.MockDispatcherFactory; +import org.jboss.resteasy.spi.Dispatcher; +import org.jboss.resteasy.spi.HttpRequest; +import org.jboss.resteasy.spi.HttpResponse; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import sonia.scm.AlreadyExistsException; +import sonia.scm.BadRequestException; +import sonia.scm.ConcurrentModificationException; +import sonia.scm.NotFoundException; +import sonia.scm.ScmConstraintViolationException; + +import javax.ws.rs.Produces; +import javax.ws.rs.core.Response; +import javax.ws.rs.core.Response.Status; +import javax.ws.rs.ext.ContextResolver; +import javax.ws.rs.ext.ExceptionMapper; +import javax.ws.rs.ext.Provider; +import java.util.HashMap; +import java.util.Map; + +public class RestDispatcher { + + private static final Logger LOG = LoggerFactory.getLogger(RestDispatcher.class); + + private final Dispatcher dispatcher; + private final EnhanceableExceptionMapper exceptionMapper; + + public RestDispatcher() { + dispatcher = MockDispatcherFactory.createDispatcher(); + exceptionMapper = new EnhanceableExceptionMapper(); + dispatcher.getProviderFactory().register(exceptionMapper); + dispatcher.getProviderFactory().registerProviderInstance(new JacksonProducer()); + } + + public void addSingletonResource(Object resource) { + dispatcher.getRegistry().addSingletonResource(resource); + } + + public void invoke(HttpRequest in, HttpResponse response) { + dispatcher.invoke(in, response); + } + + public void registerException(Class exceptionClass, Status status) { + exceptionMapper.registerException(exceptionClass, status); + } + + public void putDefaultContextObject(Class clazz, T object) { + dispatcher.getDefaultContextObjects().put(clazz, object); + } + + private static class EnhanceableExceptionMapper implements ExceptionMapper { + + private final Map, Integer> statusCodes = new HashMap<>(); + + public EnhanceableExceptionMapper() { + registerException(NotFoundException.class, Status.NOT_FOUND); + registerException(AlreadyExistsException.class, Status.CONFLICT); + registerException(ConcurrentModificationException.class, Status.CONFLICT); + registerException(UnauthorizedException.class, Status.FORBIDDEN); + registerException(AuthorizationException.class, Status.FORBIDDEN); + registerException(BadRequestException.class, Status.BAD_REQUEST); + registerException(ScmConstraintViolationException.class, Status.BAD_REQUEST); + } + + private void registerException(Class exceptionClass, Status status) { + statusCodes.put(exceptionClass, status.getStatusCode()); + } + + @Override + public Response toResponse(Exception e) { + return Response.status(getStatus(e)).entity(e.getMessage()).build(); + } + + private Integer getStatus(Exception ex) { + return statusCodes + .entrySet() + .stream() + .filter(e -> e.getKey().isAssignableFrom(ex.getClass())) + .map(Map.Entry::getValue) + .findAny() + .orElse(handleUnknownException(ex)); + } + + private Integer handleUnknownException(Exception ex) { + LOG.info("got unknown exception in rest api test", ex); + return 500; + } + } + + @Provider + @Produces("application/*+json") + public static class JacksonProducer implements ContextResolver { + public JacksonProducer() { + this.json + = new ObjectMapper().findAndRegisterModules(); + } + + @Override + public ObjectMapper getContext(Class objectType) { + return json; + } + + private final ObjectMapper json; + } +} diff --git a/scm-webapp/pom.xml b/scm-webapp/pom.xml index 571c3b5370..cd7a9e1c3c 100644 --- a/scm-webapp/pom.xml +++ b/scm-webapp/pom.xml @@ -124,7 +124,7 @@ org.jboss.resteasy - resteasy-jaxrs + resteasy-core @@ -154,26 +154,38 @@ org.jboss.resteasy - resteasy-validator-provider-11 + resteasy-validator-provider ${resteasy.version} - org.hibernate + org.hibernate.validator hibernate-validator - 5.3.6.Final + 6.1.0.Final javax.el javax.el-api - 2.2.4 + 3.0.0 - org.glassfish.web + org.glassfish javax.el - 2.2.4 + 3.0.1-b11 + + + + javax.xml.bind + jaxb-api + 2.3.0 + + + + org.glassfish.jaxb + jaxb-runtime + 2.3.0 diff --git a/scm-webapp/src/main/java/sonia/scm/event/LegmanScmEventBus.java b/scm-webapp/src/main/java/sonia/scm/event/LegmanScmEventBus.java index 85f760a71f..52b80679d9 100644 --- a/scm-webapp/src/main/java/sonia/scm/event/LegmanScmEventBus.java +++ b/scm-webapp/src/main/java/sonia/scm/event/LegmanScmEventBus.java @@ -90,8 +90,12 @@ public class LegmanScmEventBus extends ScmEventBus @Override public void post(Object event) { - logger.debug("post {} to event bus {}", event, name); - eventBus.post(event); + if (eventBus != null) { + logger.debug("post {} to event bus {}", event, name); + eventBus.post(event); + } else { + logger.error("failed to post event {}, because event bus is shutdown", event); + } } /** @@ -104,9 +108,12 @@ public class LegmanScmEventBus extends ScmEventBus @Override public void register(Object object) { - logger.trace("register {} to event bus {}", object, name); - eventBus.register(object); - + if (eventBus != null) { + logger.trace("register {} to event bus {}", object, name); + eventBus.register(object); + } else { + logger.error("failed to register {}, because eventbus is shutdown", object); + } } /** @@ -118,22 +125,37 @@ public class LegmanScmEventBus extends ScmEventBus @Override public void unregister(Object object) { - logger.trace("unregister {} from event bus {}", object, name); - - try - { - eventBus.unregister(object); + if (eventBus != null) { + logger.trace("unregister {} from event bus {}", object, name); + + try { + eventBus.unregister(object); + } catch (IllegalArgumentException ex) { + logger.trace("object {} was not registered", object); + } + } else { + logger.error("failed to unregister object {}, because event bus is shutdown", object); } - catch (IllegalArgumentException ex) - { - logger.trace("object {} was not registered", object); + } + + @Subscribe(async = false) + public void shutdownEventBus(ShutdownEventBusEvent shutdownEventBusEvent) { + if (eventBus != null) { + logger.info("shutdown event bus executor for {}, because of received ShutdownEventBusEvent", name); + eventBus.shutdown(); + eventBus = null; + } else { + logger.warn("event bus was already shutdown"); } } @Subscribe(async = false) public void recreateEventBus(RecreateEventBusEvent recreateEventBusEvent) { - logger.info("shutdown event bus executor for {}", name); - eventBus.shutdown(); + if (eventBus != null) { + logger.info("shutdown event bus executor for {}, because of received RecreateEventBusEvent", name); + eventBus.shutdown(); + } + logger.info("recreate event bus because of received RecreateEventBusEvent"); eventBus = create(); } diff --git a/scm-webapp/src/main/java/sonia/scm/event/ShutdownEventBusEvent.java b/scm-webapp/src/main/java/sonia/scm/event/ShutdownEventBusEvent.java new file mode 100644 index 0000000000..5e5ab9ca5c --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/event/ShutdownEventBusEvent.java @@ -0,0 +1,4 @@ +package sonia.scm.event; + +public class ShutdownEventBusEvent { +} diff --git a/scm-webapp/src/main/java/sonia/scm/lifecycle/BootstrapContextFilter.java b/scm-webapp/src/main/java/sonia/scm/lifecycle/BootstrapContextFilter.java index ffb7631922..1d642d9c66 100644 --- a/scm-webapp/src/main/java/sonia/scm/lifecycle/BootstrapContextFilter.java +++ b/scm-webapp/src/main/java/sonia/scm/lifecycle/BootstrapContextFilter.java @@ -58,13 +58,16 @@ public class BootstrapContextFilter extends GuiceFilter { private final BootstrapContextListener listener = new BootstrapContextListener(); + private ClassLoader webAppClassLoader; + /** Field description */ private FilterConfig filterConfig; @Override public void init(FilterConfig filterConfig) throws ServletException { this.filterConfig = filterConfig; - + // store webapp classloader for delayed restarts + webAppClassLoader = Thread.currentThread().getContextClassLoader(); initializeContext(); } @@ -97,7 +100,7 @@ public class BootstrapContextFilter extends GuiceFilter { if (filterConfig == null) { LOG.error("filter config is null, scm-manager is not initialized"); } else { - RestartStrategy restartStrategy = RestartStrategy.get(); + RestartStrategy restartStrategy = RestartStrategy.get(webAppClassLoader); restartStrategy.restart(new GuiceInjectionContext()); } } diff --git a/scm-webapp/src/main/java/sonia/scm/lifecycle/BootstrapContextListener.java b/scm-webapp/src/main/java/sonia/scm/lifecycle/BootstrapContextListener.java index 81fe7cced9..e3fd5528eb 100644 --- a/scm-webapp/src/main/java/sonia/scm/lifecycle/BootstrapContextListener.java +++ b/scm-webapp/src/main/java/sonia/scm/lifecycle/BootstrapContextListener.java @@ -66,7 +66,7 @@ public class BootstrapContextListener extends GuiceServletContextListener { private static final Logger LOG = LoggerFactory.getLogger(BootstrapContextListener.class); - private final ClassLoaderLifeCycle classLoaderLifeCycle = ClassLoaderLifeCycle.create(); + private ClassLoaderLifeCycle classLoaderLifeCycle = ClassLoaderLifeCycle.create(); private ServletContext context; private InjectionLifeCycle injectionLifeCycle; @@ -112,6 +112,8 @@ public class BootstrapContextListener extends GuiceServletContextListener { injectionLifeCycle.shutdown(); injectionLifeCycle = null; classLoaderLifeCycle.shutdown(); + + super.contextDestroyed(sce); } private Injector createStageTwoInjector() { diff --git a/scm-webapp/src/main/java/sonia/scm/lifecycle/InjectionContextRestartStrategy.java b/scm-webapp/src/main/java/sonia/scm/lifecycle/InjectionContextRestartStrategy.java index 683507c563..2db60580b1 100644 --- a/scm-webapp/src/main/java/sonia/scm/lifecycle/InjectionContextRestartStrategy.java +++ b/scm-webapp/src/main/java/sonia/scm/lifecycle/InjectionContextRestartStrategy.java @@ -5,6 +5,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import sonia.scm.event.RecreateEventBusEvent; import sonia.scm.event.ScmEventBus; +import sonia.scm.event.ShutdownEventBusEvent; import java.util.concurrent.atomic.AtomicLong; @@ -13,20 +14,47 @@ import java.util.concurrent.atomic.AtomicLong; */ public class InjectionContextRestartStrategy implements RestartStrategy { + private static final String DISABLE_RESTART_PROPERTY = "sonia.scm.restart.disable"; + private static final String WAIT_PROPERTY = "sonia.scm.restart.wait"; + private static final String DISABLE_GC_PROPERTY = "sonia.scm.restart.disable-gc"; + private static final AtomicLong INSTANCE_COUNTER = new AtomicLong(); private static final Logger LOG = LoggerFactory.getLogger(InjectionContextRestartStrategy.class); - private long waitInMs = 250L; + private boolean restartEnabled = !Boolean.getBoolean(DISABLE_RESTART_PROPERTY); + private long waitInMs = Integer.getInteger(WAIT_PROPERTY, 250); + private boolean gcEnabled = !Boolean.getBoolean(DISABLE_GC_PROPERTY); + + private final ClassLoader webAppClassLoader; + + InjectionContextRestartStrategy(ClassLoader webAppClassLoader) { + this.webAppClassLoader = webAppClassLoader; + } @VisibleForTesting void setWaitInMs(long waitInMs) { this.waitInMs = waitInMs; } + @VisibleForTesting + void setGcEnabled(boolean gcEnabled) { + this.gcEnabled = gcEnabled; + } + @Override public void restart(InjectionContext context) { - LOG.warn("destroy injection context"); - context.destroy(); + stop(context); + if (restartEnabled) { + start(context); + } else { + LOG.warn("restarting context is disabled"); + } + } + + @SuppressWarnings("squid:S1215") // suppress explicit gc call warning + private void start(InjectionContext context) { + LOG.debug("use WebAppClassLoader as ContextClassLoader, to avoid ClassLoader leaks"); + Thread.currentThread().setContextClassLoader(webAppClassLoader); LOG.warn("send recreate eventbus event"); ScmEventBus.getInstance().post(new RecreateEventBusEvent()); @@ -34,6 +62,12 @@ public class InjectionContextRestartStrategy implements RestartStrategy { // restart context delayed, to avoid timing problems new Thread(() -> { try { + if (gcEnabled){ + LOG.info("call gc to clean up memory from old instances"); + System.gc(); + } + + LOG.info("wait {}ms before re starting the context", waitInMs); Thread.sleep(waitInMs); LOG.warn("reinitialize injection context"); @@ -45,6 +79,15 @@ public class InjectionContextRestartStrategy implements RestartStrategy { LOG.error("failed to restart", ex); } }, "Delayed-Restart-" + INSTANCE_COUNTER.incrementAndGet()).start(); + } + private void stop(InjectionContext context) { + LOG.warn("destroy injection context"); + context.destroy(); + + if (!restartEnabled) { + // shutdown eventbus, but do this only if restart is disabled + ScmEventBus.getInstance().post(new ShutdownEventBusEvent()); + } } } diff --git a/scm-webapp/src/main/java/sonia/scm/lifecycle/RestartStrategy.java b/scm-webapp/src/main/java/sonia/scm/lifecycle/RestartStrategy.java index 769351a850..6c7dd69259 100644 --- a/scm-webapp/src/main/java/sonia/scm/lifecycle/RestartStrategy.java +++ b/scm-webapp/src/main/java/sonia/scm/lifecycle/RestartStrategy.java @@ -13,7 +13,6 @@ public interface RestartStrategy { * Initialize the injection context. */ void initialize(); - /** * Destroys the injection context. */ @@ -31,8 +30,8 @@ public interface RestartStrategy { * * @return configured strategy */ - static RestartStrategy get() { - return new InjectionContextRestartStrategy(); + static RestartStrategy get(ClassLoader webAppClassLoader) { + return new InjectionContextRestartStrategy(webAppClassLoader); } } diff --git a/scm-webapp/src/main/java/sonia/scm/lifecycle/classloading/BootstrapClassLoader.java b/scm-webapp/src/main/java/sonia/scm/lifecycle/classloading/BootstrapClassLoader.java index 64d9b75d36..a274dbb33c 100644 --- a/scm-webapp/src/main/java/sonia/scm/lifecycle/classloading/BootstrapClassLoader.java +++ b/scm-webapp/src/main/java/sonia/scm/lifecycle/classloading/BootstrapClassLoader.java @@ -5,7 +5,29 @@ package sonia.scm.lifecycle.classloading; * find it in a heap dump. */ class BootstrapClassLoader extends ClassLoader { + + /** + * Marker to find a BootstrapClassLoader, which is already shutdown. + */ + private boolean shutdown = false; + BootstrapClassLoader(ClassLoader webappClassLoader) { super(webappClassLoader); } + + /** + * Returns {@code true} if the classloader was shutdown. + * + * @return {@code true} if the classloader was shutdown + */ + boolean isShutdown() { + return shutdown; + } + + /** + * Mark the class loader as shutdown. + */ + void markAsShutdown() { + shutdown = true; + } } diff --git a/scm-webapp/src/main/java/sonia/scm/lifecycle/classloading/ClassLoaderLifeCycle.java b/scm-webapp/src/main/java/sonia/scm/lifecycle/classloading/ClassLoaderLifeCycle.java index de2480f563..a64ed6fa43 100644 --- a/scm-webapp/src/main/java/sonia/scm/lifecycle/classloading/ClassLoaderLifeCycle.java +++ b/scm-webapp/src/main/java/sonia/scm/lifecycle/classloading/ClassLoaderLifeCycle.java @@ -5,8 +5,13 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import se.jiderhamn.classloader.leak.prevention.ClassLoaderLeakPreventor; import se.jiderhamn.classloader.leak.prevention.ClassLoaderLeakPreventorFactory; +import se.jiderhamn.classloader.leak.prevention.cleanup.IIOServiceProviderCleanUp; import se.jiderhamn.classloader.leak.prevention.cleanup.MBeanCleanUp; +import se.jiderhamn.classloader.leak.prevention.cleanup.ShutdownHookCleanUp; import se.jiderhamn.classloader.leak.prevention.cleanup.StopThreadsCleanUp; +import se.jiderhamn.classloader.leak.prevention.preinit.AwtToolkitInitiator; +import se.jiderhamn.classloader.leak.prevention.preinit.Java2dDisposerInitiator; +import se.jiderhamn.classloader.leak.prevention.preinit.Java2dRenderQueueInitiator; import se.jiderhamn.classloader.leak.prevention.preinit.SunAwtAppContextInitiator; import sonia.scm.lifecycle.LifeCycle; import sonia.scm.plugin.ChildFirstPluginClassLoader; @@ -17,9 +22,9 @@ import java.io.IOException; import java.net.URL; import java.util.ArrayDeque; import java.util.Deque; -import java.util.function.UnaryOperator; import static com.google.common.base.Preconditions.checkState; +import static se.jiderhamn.classloader.leak.prevention.cleanup.ShutdownHookCleanUp.SHUTDOWN_HOOK_WAIT_MS_DEFAULT; /** * Creates and shutdown SCM-Manager ClassLoaders. @@ -28,26 +33,25 @@ public final class ClassLoaderLifeCycle implements LifeCycle { private static final Logger LOG = LoggerFactory.getLogger(ClassLoaderLifeCycle.class); - private final Deque classLoaders = new ArrayDeque<>(); + private Deque classLoaders = new ArrayDeque<>(); private final ClassLoaderLeakPreventorFactory classLoaderLeakPreventorFactory; private final ClassLoader webappClassLoader; - private ClassLoader bootstrapClassLoader; - private UnaryOperator classLoaderAppendListener = c -> c; + private BootstrapClassLoader bootstrapClassLoader; + + private ClassLoaderAppendListener classLoaderAppendListener = new ClassLoaderAppendListener() { + @Override + public C apply(C classLoader) { + return classLoader; + } + }; @VisibleForTesting public static ClassLoaderLifeCycle create() { - ClassLoaderLeakPreventorFactory classLoaderLeakPreventorFactory = new ClassLoaderLeakPreventorFactory(); - classLoaderLeakPreventorFactory.setLogger(new LoggingAdapter()); - // the SunAwtAppContextInitiator causes a lot of exceptions and we use no awt - classLoaderLeakPreventorFactory.removePreInitiator(SunAwtAppContextInitiator.class); - // the MBeanCleanUp causes a Exception and we use no mbeans - classLoaderLeakPreventorFactory.removeCleanUp(MBeanCleanUp.class); - // the StopThreadsCleanUp leads to timeouts on shutdown - we try to stop our threads on our own - classLoaderLeakPreventorFactory.removeCleanUp(StopThreadsCleanUp.class); - - return new ClassLoaderLifeCycle(Thread.currentThread().getContextClassLoader(), classLoaderLeakPreventorFactory); + ClassLoader webappClassLoader = Thread.currentThread().getContextClassLoader(); + ClassLoaderLeakPreventorFactory classLoaderLeakPreventorFactory = createClassLoaderLeakPreventorFactory(webappClassLoader); + return new ClassLoaderLifeCycle(webappClassLoader, classLoaderLeakPreventorFactory); } ClassLoaderLifeCycle(ClassLoader webappClassLoader, ClassLoaderLeakPreventorFactory classLoaderLeakPreventorFactory) { @@ -55,12 +59,64 @@ public final class ClassLoaderLifeCycle implements LifeCycle { this.webappClassLoader = initAndAppend(webappClassLoader); } + private static ClassLoaderLeakPreventorFactory createClassLoaderLeakPreventorFactory(ClassLoader webappClassLoader) { + // Should threads tied to the web app classloader be forced to stop at application shutdown? + boolean stopThreads = Boolean.getBoolean("ClassLoaderLeakPreventor.stopThreads"); + + // Should Timer threads tied to the web app classloader be forced to stop at application shutdown? + boolean stopTimerThreads = Boolean.getBoolean("ClassLoaderLeakPreventor.stopTimerThreads"); + + // Should shutdown hooks registered from the application be executed at application shutdown? + boolean executeShutdownHooks = Boolean.getBoolean("ClassLoaderLeakPreventor.executeShutdownHooks"); + + // No of milliseconds to wait for threads to finish execution, before stopping them. + int threadWaitMs = Integer.getInteger("ClassLoaderLeakPreventor.threadWaitMs", ClassLoaderLeakPreventor.THREAD_WAIT_MS_DEFAULT); + + /* + * No of milliseconds to wait for shutdown hooks to finish execution, before stopping them. + * If set to -1 there will be no waiting at all, but Thread is allowed to run until finished. + */ + int shutdownHookWaitMs = Integer.getInteger("ClassLoaderLeakPreventor.shutdownHookWaitMs", SHUTDOWN_HOOK_WAIT_MS_DEFAULT); + + LOG.info("Settings for {} (CL: 0x{}):", ClassLoaderLifeCycle.class.getName(), Integer.toHexString(System.identityHashCode(webappClassLoader)) ); + LOG.info(" stopThreads = {}", stopThreads); + LOG.info(" stopTimerThreads = {}", stopTimerThreads); + LOG.info(" executeShutdownHooks = {}", executeShutdownHooks); + LOG.info(" threadWaitMs = {} ms", threadWaitMs); + LOG.info(" shutdownHookWaitMs = {} ms", shutdownHookWaitMs); + + // use webapp classloader as safe base? or system? + ClassLoaderLeakPreventorFactory classLoaderLeakPreventorFactory = new ClassLoaderLeakPreventorFactory(webappClassLoader); + classLoaderLeakPreventorFactory.setLogger(new LoggingAdapter()); + + final ShutdownHookCleanUp shutdownHookCleanUp = classLoaderLeakPreventorFactory.getCleanUp(ShutdownHookCleanUp.class); + shutdownHookCleanUp.setExecuteShutdownHooks(executeShutdownHooks); + shutdownHookCleanUp.setShutdownHookWaitMs(shutdownHookWaitMs); + + final StopThreadsCleanUp stopThreadsCleanUp = classLoaderLeakPreventorFactory.getCleanUp(StopThreadsCleanUp.class); + stopThreadsCleanUp.setStopThreads(stopThreads); + stopThreadsCleanUp.setStopTimerThreads(stopTimerThreads); + stopThreadsCleanUp.setThreadWaitMs(threadWaitMs); + + // remove awt and imageio cleanup + classLoaderLeakPreventorFactory.removePreInitiator(AwtToolkitInitiator.class); + classLoaderLeakPreventorFactory.removePreInitiator(SunAwtAppContextInitiator.class); + classLoaderLeakPreventorFactory.removeCleanUp(IIOServiceProviderCleanUp.class); + classLoaderLeakPreventorFactory.removePreInitiator(Java2dRenderQueueInitiator.class); + classLoaderLeakPreventorFactory.removePreInitiator(Java2dDisposerInitiator.class); + + // the MBeanCleanUp causes a Exception and we use no mbeans + classLoaderLeakPreventorFactory.removeCleanUp(MBeanCleanUp.class); + + return classLoaderLeakPreventorFactory; + } + public void initialize() { bootstrapClassLoader = initAndAppend(new BootstrapClassLoader(webappClassLoader)); } @VisibleForTesting - void setClassLoaderAppendListener(UnaryOperator classLoaderAppendListener) { + void setClassLoaderAppendListener(ClassLoaderAppendListener classLoaderAppendListener) { this.classLoaderAppendListener = classLoaderAppendListener; } @@ -88,12 +144,17 @@ public final class ClassLoaderLifeCycle implements LifeCycle { clap.shutdown(); clap = classLoaders.poll(); } + // be sure it is realy empty + classLoaders.clear(); + classLoaders = new ArrayDeque<>(); + + bootstrapClassLoader.markAsShutdown(); bootstrapClassLoader = null; } - private ClassLoader initAndAppend(ClassLoader originalClassLoader) { + private T initAndAppend(T originalClassLoader) { LOG.debug("init classloader {}", originalClassLoader); - ClassLoader classLoader = classLoaderAppendListener.apply(originalClassLoader); + T classLoader = classLoaderAppendListener.apply(originalClassLoader); ClassLoaderLeakPreventor preventor = classLoaderLeakPreventorFactory.newLeakPreventor(classLoader); preventor.runPreClassLoaderInitiators(); @@ -102,6 +163,10 @@ public final class ClassLoaderLifeCycle implements LifeCycle { return classLoader; } + interface ClassLoaderAppendListener { + C apply(C classLoader); + } + private class ClassLoaderAndPreventor { private final ClassLoader classLoader; diff --git a/scm-webapp/src/main/java/sonia/scm/lifecycle/modules/ResteasyAllInOneServletDispatcher.java b/scm-webapp/src/main/java/sonia/scm/lifecycle/modules/ResteasyAllInOneServletDispatcher.java index a30cb20c01..8f602db766 100644 --- a/scm-webapp/src/main/java/sonia/scm/lifecycle/modules/ResteasyAllInOneServletDispatcher.java +++ b/scm-webapp/src/main/java/sonia/scm/lifecycle/modules/ResteasyAllInOneServletDispatcher.java @@ -7,6 +7,7 @@ import org.jboss.resteasy.plugins.server.servlet.ListenerBootstrap; import org.jboss.resteasy.spi.Registry; import org.jboss.resteasy.spi.ResteasyDeployment; import org.jboss.resteasy.spi.ResteasyProviderFactory; +import org.jboss.resteasy.spi.statistics.StatisticsController; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -70,10 +71,21 @@ public class ResteasyAllInOneServletDispatcher extends HttpServletDispatcher { super.destroy(); deployment.stop(); + // clear ResourceLocatorInvoker leaks + StatisticsController statisticsController = ResteasyProviderFactory.getInstance().getStatisticsController(); + if (statisticsController != null) { + statisticsController.reset(); + } + // ensure everything gets cleared, to avoid classloader leaks ResteasyProviderFactory.clearInstanceIfEqual(ResteasyProviderFactory.getInstance()); - ResteasyProviderFactory.clearContextData(); RuntimeDelegate.setInstance(null); + + removeDeploymentFromServletContext(); + } + + private void removeDeploymentFromServletContext() { + getServletContext().removeAttribute(ResteasyDeployment.class.getName()); } private ResteasyDeployment getDeploymentFromServletContext() { diff --git a/scm-webapp/src/main/java/sonia/scm/schedule/CronScheduler.java b/scm-webapp/src/main/java/sonia/scm/schedule/CronScheduler.java index 7899697746..8577768495 100644 --- a/scm-webapp/src/main/java/sonia/scm/schedule/CronScheduler.java +++ b/scm-webapp/src/main/java/sonia/scm/schedule/CronScheduler.java @@ -17,15 +17,17 @@ public class CronScheduler implements Scheduler { private final ScheduledExecutorService executorService; private final CronTaskFactory taskFactory; + private final CronThreadFactory threadFactory; @Inject public CronScheduler(CronTaskFactory taskFactory) { this.taskFactory = taskFactory; + this.threadFactory = new CronThreadFactory(); this.executorService = createExecutor(); } private ScheduledExecutorService createExecutor() { - return Executors.newScheduledThreadPool(2, new CronThreadFactory()); + return Executors.newScheduledThreadPool(2, threadFactory); } @Override @@ -52,6 +54,7 @@ public class CronScheduler implements Scheduler { @Override public void close() { LOG.debug("shutdown underlying executor service"); + threadFactory.close(); executorService.shutdown(); } } diff --git a/scm-webapp/src/main/java/sonia/scm/schedule/CronThreadFactory.java b/scm-webapp/src/main/java/sonia/scm/schedule/CronThreadFactory.java index 6519f500fa..bb732b4512 100644 --- a/scm-webapp/src/main/java/sonia/scm/schedule/CronThreadFactory.java +++ b/scm-webapp/src/main/java/sonia/scm/schedule/CronThreadFactory.java @@ -1,5 +1,6 @@ package sonia.scm.schedule; +import com.google.common.util.concurrent.ThreadFactoryBuilder; import org.apache.shiro.util.ThreadContext; import java.util.concurrent.ExecutionException; @@ -19,7 +20,10 @@ class CronThreadFactory implements ThreadFactory, AutoCloseable { private static final AtomicLong FACTORY_COUNTER = new AtomicLong(); - private final ExecutorService executorService = Executors.newSingleThreadExecutor(); + private final ExecutorService executorService = Executors.newSingleThreadExecutor( + new ThreadFactoryBuilder().setNameFormat("CronThreadFactory-%d").build() + ); + private final long factoryId = FACTORY_COUNTER.incrementAndGet(); private final AtomicLong threadCounter = new AtomicLong(); diff --git a/scm-webapp/src/main/java/sonia/scm/web/cgi/DefaultCGIExecutorFactory.java b/scm-webapp/src/main/java/sonia/scm/web/cgi/DefaultCGIExecutorFactory.java index 8da159f3c0..fa8507c917 100644 --- a/scm-webapp/src/main/java/sonia/scm/web/cgi/DefaultCGIExecutorFactory.java +++ b/scm-webapp/src/main/java/sonia/scm/web/cgi/DefaultCGIExecutorFactory.java @@ -52,7 +52,7 @@ import javax.servlet.http.HttpServletResponse; * * @author Sebastian Sdorra */ -public class DefaultCGIExecutorFactory implements CGIExecutorFactory +public class DefaultCGIExecutorFactory implements CGIExecutorFactory, AutoCloseable { /** @@ -92,6 +92,11 @@ public class DefaultCGIExecutorFactory implements CGIExecutorFactory //~--- fields --------------------------------------------------------------- + @Override + public void close() { + executor.shutdown(); + } + /** Field description */ private final ExecutorService executor; } diff --git a/scm-webapp/src/test/java/sonia/scm/api/rest/AuthorizationExceptionMapperTest.java b/scm-webapp/src/test/java/sonia/scm/api/rest/AuthorizationExceptionMapperTest.java new file mode 100644 index 0000000000..cd56d966ef --- /dev/null +++ b/scm-webapp/src/test/java/sonia/scm/api/rest/AuthorizationExceptionMapperTest.java @@ -0,0 +1,49 @@ +package sonia.scm.api.rest; + +import org.apache.shiro.authz.AuthorizationException; +import org.apache.shiro.subject.Subject; +import org.apache.shiro.subject.support.SubjectThreadState; +import org.apache.shiro.util.ThreadContext; +import org.apache.shiro.util.ThreadState; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +class AuthorizationExceptionMapperTest { + + private final Subject subject = mock(Subject.class); + private final ThreadState subjectThreadState = new SubjectThreadState(subject); + + @BeforeEach + public void init() { + subjectThreadState.bind(); + ThreadContext.bind(subject); + } + + @AfterEach + public void unbindSubject() { + ThreadContext.unbindSubject(); + } + + @Test + void shouldMapNormalUserToForbidden() { + when(subject.getPrincipal()).thenReturn("someone"); + + assertThat( + new AuthorizationExceptionMapper().toResponse(new AuthorizationException()).getStatus() + ).isEqualTo(403); + } + + @Test + void shouldMapAnonymousUserToUnauthorized() { + when(subject.getPrincipal()).thenReturn("_anonymous"); + + assertThat( + new AuthorizationExceptionMapper().toResponse(new AuthorizationException()).getStatus() + ).isEqualTo(401); + } +} diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/AuthenticationResourceTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/AuthenticationResourceTest.java index 177f975971..1c1035b53a 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/AuthenticationResourceTest.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/AuthenticationResourceTest.java @@ -2,11 +2,8 @@ package sonia.scm.api.v2.resources; import com.github.sdorra.shiro.ShiroRule; import com.github.sdorra.shiro.SubjectAware; -import org.jboss.resteasy.core.Dispatcher; -import org.jboss.resteasy.mock.MockDispatcherFactory; import org.jboss.resteasy.mock.MockHttpRequest; import org.jboss.resteasy.mock.MockHttpResponse; -import org.jboss.resteasy.spi.ResteasyProviderFactory; import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -19,18 +16,17 @@ import sonia.scm.security.AccessTokenBuilder; import sonia.scm.security.AccessTokenBuilderFactory; import sonia.scm.security.AccessTokenCookieIssuer; import sonia.scm.security.DefaultAccessTokenCookieIssuer; +import sonia.scm.web.RestDispatcher; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.ws.rs.core.MediaType; import java.io.UnsupportedEncodingException; -import java.net.URI; import java.net.URISyntaxException; import java.util.Date; import java.util.Optional; import static java.net.URI.create; -import static java.util.Optional.empty; import static java.util.Optional.of; import static org.hamcrest.Matchers.containsString; import static org.junit.Assert.assertEquals; @@ -47,7 +43,7 @@ public class AuthenticationResourceTest { @Rule public ShiroRule shiro = new ShiroRule(); - private Dispatcher dispatcher = MockDispatcherFactory.createDispatcher(); + private RestDispatcher dispatcher = new RestDispatcher(); @Mock private AccessTokenBuilderFactory accessTokenBuilderFactory; @@ -116,7 +112,7 @@ public class AuthenticationResourceTest { @Before public void prepareEnvironment() { authenticationResource = new AuthenticationResource(accessTokenBuilderFactory, cookieIssuer); - dispatcher.getRegistry().addSingletonResource(authenticationResource); + dispatcher.addSingletonResource(authenticationResource); AccessToken accessToken = mock(AccessToken.class); when(accessToken.getExpiration()).thenReturn(new Date(Long.MAX_VALUE)); @@ -125,10 +121,9 @@ public class AuthenticationResourceTest { when(accessTokenBuilderFactory.create()).thenReturn(accessTokenBuilder); HttpServletRequest servletRequest = mock(HttpServletRequest.class); - ResteasyProviderFactory.getContextDataMap().put(HttpServletRequest.class, servletRequest); - + dispatcher.putDefaultContextObject(HttpServletRequest.class, servletRequest); HttpServletResponse servletResponse = mock(HttpServletResponse.class); - ResteasyProviderFactory.getContextDataMap().put(HttpServletResponse.class, servletResponse); + dispatcher.putDefaultContextObject(HttpServletResponse.class, servletResponse); } @Test diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/AutoCompleteResourceTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/AutoCompleteResourceTest.java index ac89d02bfb..3f2473dda0 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/AutoCompleteResourceTest.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/AutoCompleteResourceTest.java @@ -5,7 +5,6 @@ import com.github.sdorra.shiro.ShiroRule; import com.github.sdorra.shiro.SubjectAware; import org.apache.shiro.util.ThreadContext; import org.assertj.core.util.Lists; -import org.jboss.resteasy.core.Dispatcher; import org.jboss.resteasy.mock.MockHttpRequest; import org.jboss.resteasy.mock.MockHttpResponse; import org.junit.After; @@ -23,6 +22,7 @@ import sonia.scm.store.ConfigurationStoreFactory; import sonia.scm.user.DefaultUserDisplayManager; import sonia.scm.user.User; import sonia.scm.user.xml.XmlUserDAO; +import sonia.scm.web.RestDispatcher; import sonia.scm.web.VndMediaType; import sonia.scm.xml.XmlDatabase; @@ -39,7 +39,6 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.when; import static org.mockito.MockitoAnnotations.initMocks; -import static sonia.scm.api.v2.resources.DispatcherMock.createDispatcher; @SubjectAware(configuration = "classpath:sonia/scm/shiro-002.ini") @RunWith(MockitoJUnitRunner.Silent.class) @@ -50,7 +49,8 @@ public class AutoCompleteResourceTest { public static final String URL = "/" + AutoCompleteResource.PATH; private final Integer defaultLimit = DisplayManager.DEFAULT_LIMIT; - private Dispatcher dispatcher; + + private RestDispatcher dispatcher = new RestDispatcher(); private XmlUserDAO userDao; private XmlGroupDAO groupDao; @@ -74,7 +74,7 @@ public class AutoCompleteResourceTest { DefaultUserDisplayManager userManager = new DefaultUserDisplayManager(this.userDao); DefaultGroupDisplayManager groupManager = new DefaultGroupDisplayManager(groupDao); AutoCompleteResource autoCompleteResource = new AutoCompleteResource(mapper, userManager, groupManager); - dispatcher = createDispatcher(autoCompleteResource); + dispatcher.addSingletonResource(autoCompleteResource); } @After diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/AvailablePluginResourceTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/AvailablePluginResourceTest.java index 32eadf7af0..cfda20f285 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/AvailablePluginResourceTest.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/AvailablePluginResourceTest.java @@ -1,14 +1,11 @@ package sonia.scm.api.v2.resources; import de.otto.edison.hal.HalRepresentation; -import org.apache.shiro.ShiroException; +import org.apache.shiro.authz.UnauthorizedException; import org.apache.shiro.subject.Subject; import org.apache.shiro.util.ThreadContext; -import org.jboss.resteasy.core.Dispatcher; -import org.jboss.resteasy.mock.MockDispatcherFactory; import org.jboss.resteasy.mock.MockHttpRequest; import org.jboss.resteasy.mock.MockHttpResponse; -import org.jboss.resteasy.spi.UnhandledException; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Nested; @@ -24,6 +21,7 @@ import sonia.scm.plugin.InstalledPluginDescriptor; import sonia.scm.plugin.PluginCondition; import sonia.scm.plugin.PluginInformation; import sonia.scm.plugin.PluginManager; +import sonia.scm.web.RestDispatcher; import sonia.scm.web.VndMediaType; import javax.inject.Provider; @@ -34,7 +32,7 @@ import java.util.Collections; import java.util.Optional; import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.Assert.assertEquals; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doThrow; @@ -46,7 +44,7 @@ import static org.mockito.Mockito.when; @ExtendWith(MockitoExtension.class) class AvailablePluginResourceTest { - private Dispatcher dispatcher; + private RestDispatcher dispatcher = new RestDispatcher(); @Mock Provider availablePluginResourceProvider; @@ -71,10 +69,9 @@ class AvailablePluginResourceTest { @BeforeEach void prepareEnvironment() { - dispatcher = MockDispatcherFactory.createDispatcher(); pluginRootResource = new PluginRootResource(null, availablePluginResourceProvider, null); when(availablePluginResourceProvider.get()).thenReturn(availablePluginResource); - dispatcher.getRegistry().addSingletonResource(pluginRootResource); + dispatcher.addSingletonResource(pluginRootResource); } @Nested @@ -195,20 +192,23 @@ class AvailablePluginResourceTest { @BeforeEach void bindSubject() { ThreadContext.bind(subject); - doThrow(new ShiroException()).when(subject).checkPermission(any(String.class)); + doThrow(new UnauthorizedException()).when(subject).checkPermission(any(String.class)); } @AfterEach public void unbindSubject() { ThreadContext.unbindSubject(); } + @Test void shouldNotGetAvailablePluginsIfMissingPermission() throws URISyntaxException { MockHttpRequest request = MockHttpRequest.get("/v2/plugins/available"); request.accept(VndMediaType.PLUGIN_COLLECTION); MockHttpResponse response = new MockHttpResponse(); - assertThrows(UnhandledException.class, () -> dispatcher.invoke(request, response)); + dispatcher.invoke(request, response); + + assertEquals(HttpServletResponse.SC_FORBIDDEN, response.getStatus()); verify(subject).checkPermission(any(String.class)); } @@ -218,7 +218,9 @@ class AvailablePluginResourceTest { request.accept(VndMediaType.PLUGIN); MockHttpResponse response = new MockHttpResponse(); - assertThrows(UnhandledException.class, () -> dispatcher.invoke(request, response)); + dispatcher.invoke(request, response); + + assertEquals(HttpServletResponse.SC_FORBIDDEN, response.getStatus()); verify(subject).checkPermission(any(String.class)); } @@ -228,7 +230,9 @@ class AvailablePluginResourceTest { request.accept(VndMediaType.PLUGIN); MockHttpResponse response = new MockHttpResponse(); - assertThrows(UnhandledException.class, () -> dispatcher.invoke(request, response)); + dispatcher.invoke(request, response); + + assertEquals(HttpServletResponse.SC_FORBIDDEN, response.getStatus()); verify(subject).checkPermission(any(String.class)); } } diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/BranchRootResourceTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/BranchRootResourceTest.java index 2c83d5e3e1..72bf9e96d6 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/BranchRootResourceTest.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/BranchRootResourceTest.java @@ -6,9 +6,8 @@ import org.apache.shiro.subject.Subject; import org.apache.shiro.subject.support.SubjectThreadState; import org.apache.shiro.util.ThreadContext; import org.apache.shiro.util.ThreadState; +import org.assertj.core.api.Assertions; import org.assertj.core.util.Lists; -import org.jboss.resteasy.core.Dispatcher; -import org.jboss.resteasy.mock.MockDispatcherFactory; import org.jboss.resteasy.mock.MockHttpRequest; import org.jboss.resteasy.mock.MockHttpResponse; import org.junit.After; @@ -30,8 +29,10 @@ import sonia.scm.repository.api.BranchesCommandBuilder; import sonia.scm.repository.api.LogCommandBuilder; import sonia.scm.repository.api.RepositoryService; import sonia.scm.repository.api.RepositoryServiceFactory; +import sonia.scm.web.RestDispatcher; import sonia.scm.web.VndMediaType; +import javax.ws.rs.core.MediaType; import java.net.URI; import java.time.Instant; import java.util.Date; @@ -54,7 +55,8 @@ public class BranchRootResourceTest extends RepositoryTestBase { public static final String BRANCH_PATH = "space/repo/branches/master"; public static final String BRANCH_URL = "/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + BRANCH_PATH; public static final String REVISION = "revision"; - private Dispatcher dispatcher; + + private RestDispatcher dispatcher = new RestDispatcher(); private final URI baseUri = URI.create("/"); private final ResourceLinks resourceLinks = ResourceLinksMock.createMock(baseUri); @@ -101,7 +103,7 @@ public class BranchRootResourceTest extends RepositoryTestBase { BranchCollectionToDtoMapper branchCollectionToDtoMapper = new BranchCollectionToDtoMapper(branchToDtoMapper, resourceLinks); branchRootResource = new BranchRootResource(serviceFactory, branchToDtoMapper, branchCollectionToDtoMapper, changesetCollectionToDtoMapper, resourceLinks); super.branchRootResource = Providers.of(branchRootResource); - dispatcher = DispatcherMock.createDispatcher(getRepositoryRootResource()); + dispatcher.addSingletonResource(getRepositoryRootResource()); when(serviceFactory.create(new NamespaceAndName("space", "repo"))).thenReturn(service); when(serviceFactory.create(any(Repository.class))).thenReturn(service); when(service.getRepository()).thenReturn(new Repository("repoId", "git", "space", "repo")); @@ -129,7 +131,8 @@ public class BranchRootResourceTest extends RepositoryTestBase { dispatcher.invoke(request, response); assertEquals(404, response.getStatus()); - assertEquals("application/vnd.scmm-error+json;v=2", response.getOutputHeaders().getFirst("Content-Type")); + MediaType contentType = (MediaType) response.getOutputHeaders().getFirst("Content-Type"); + Assertions.assertThat(response.getContentAsString()).contains("branch", "master", "space/repo"); } @Test diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ChangesetRootResourceTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ChangesetRootResourceTest.java index 952c8504f6..1e2b4a0979 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ChangesetRootResourceTest.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ChangesetRootResourceTest.java @@ -8,7 +8,6 @@ import org.apache.shiro.subject.support.SubjectThreadState; import org.apache.shiro.util.ThreadContext; import org.apache.shiro.util.ThreadState; import org.assertj.core.util.Lists; -import org.jboss.resteasy.core.Dispatcher; import org.jboss.resteasy.mock.MockHttpRequest; import org.jboss.resteasy.mock.MockHttpResponse; import org.junit.After; @@ -26,6 +25,7 @@ import sonia.scm.repository.Repository; import sonia.scm.repository.api.LogCommandBuilder; import sonia.scm.repository.api.RepositoryService; import sonia.scm.repository.api.RepositoryServiceFactory; +import sonia.scm.web.RestDispatcher; import sonia.scm.web.VndMediaType; import java.net.URI; @@ -48,7 +48,7 @@ public class ChangesetRootResourceTest extends RepositoryTestBase { public static final String CHANGESET_PATH = "space/repo/changesets/"; public static final String CHANGESET_URL = "/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + CHANGESET_PATH; - private Dispatcher dispatcher; + private RestDispatcher dispatcher = new RestDispatcher(); private final URI baseUri = URI.create("/"); private final ResourceLinks resourceLinks = ResourceLinksMock.createMock(baseUri); @@ -79,7 +79,7 @@ public class ChangesetRootResourceTest extends RepositoryTestBase { changesetCollectionToDtoMapper = new ChangesetCollectionToDtoMapper(changesetToChangesetDtoMapper, resourceLinks); changesetRootResource = new ChangesetRootResource(serviceFactory, changesetCollectionToDtoMapper, changesetToChangesetDtoMapper); super.changesetRootResource = Providers.of(changesetRootResource); - dispatcher = DispatcherMock.createDispatcher(getRepositoryRootResource()); + dispatcher.addSingletonResource(getRepositoryRootResource()); when(serviceFactory.create(new NamespaceAndName("space", "repo"))).thenReturn(repositoryService); when(serviceFactory.create(any(Repository.class))).thenReturn(repositoryService); when(repositoryService.getRepository()).thenReturn(new Repository("repoId", "git", "space", "repo")); diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ConfigResourceTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ConfigResourceTest.java index 15c23dbd3c..951f3dc24c 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ConfigResourceTest.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ConfigResourceTest.java @@ -4,18 +4,16 @@ import com.github.sdorra.shiro.ShiroRule; import com.github.sdorra.shiro.SubjectAware; import com.google.common.io.Resources; import org.apache.shiro.util.ThreadContext; -import org.jboss.resteasy.core.Dispatcher; -import org.jboss.resteasy.mock.MockDispatcherFactory; import org.jboss.resteasy.mock.MockHttpRequest; import org.jboss.resteasy.mock.MockHttpResponse; import org.junit.Before; import org.junit.Rule; import org.junit.Test; -import org.junit.rules.ExpectedException; import org.mockito.InjectMocks; import org.mockito.Mock; import sonia.scm.config.ScmConfiguration; import sonia.scm.repository.NamespaceStrategyValidator; +import sonia.scm.web.RestDispatcher; import sonia.scm.web.VndMediaType; import javax.servlet.http.HttpServletResponse; @@ -41,10 +39,7 @@ public class ConfigResourceTest { @Rule public ShiroRule shiro = new ShiroRule(); - @Rule - public ExpectedException thrown = ExpectedException.none(); - - private Dispatcher dispatcher = MockDispatcherFactory.createDispatcher(); + private RestDispatcher dispatcher = new RestDispatcher(); private final URI baseUri = URI.create("/"); @SuppressWarnings("unused") // Is injected @@ -71,7 +66,7 @@ public class ConfigResourceTest { ConfigResource configResource = new ConfigResource(dtoToConfigMapper, configToDtoMapper, createConfiguration(), namespaceStrategyValidator); - dispatcher.getRegistry().addSingletonResource(configResource); + dispatcher.addSingletonResource(configResource); } @Test @@ -88,13 +83,14 @@ public class ConfigResourceTest { @Test @SubjectAware(username = "writeOnly") - public void shouldNotGetConfigWhenNotAuthorized() throws URISyntaxException { + public void shouldNotGetConfigWhenNotAuthorized() throws URISyntaxException, UnsupportedEncodingException { MockHttpRequest request = MockHttpRequest.get("/" + ConfigResource.CONFIG_PATH_V2); MockHttpResponse response = new MockHttpResponse(); - thrown.expectMessage("Subject does not have permission [configuration:read:global]"); - dispatcher.invoke(request, response); + + assertEquals("Subject does not have permission [configuration:read:global]", response.getContentAsString()); + assertEquals(HttpServletResponse.SC_FORBIDDEN, response.getStatus()); } @Test @@ -120,9 +116,10 @@ public class ConfigResourceTest { MockHttpRequest request = post("sonia/scm/api/v2/config-test-update.json"); MockHttpResponse response = new MockHttpResponse(); - thrown.expectMessage("Subject does not have permission [configuration:write:global]"); - dispatcher.invoke(request, response); + + assertEquals("Subject does not have permission [configuration:write:global]", response.getContentAsString()); + assertEquals(HttpServletResponse.SC_FORBIDDEN, response.getStatus()); } diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/DiffResourceTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/DiffResourceTest.java index 33d4d7e9e1..f01a20f6c4 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/DiffResourceTest.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/DiffResourceTest.java @@ -7,8 +7,6 @@ import org.apache.shiro.subject.Subject; import org.apache.shiro.subject.support.SubjectThreadState; import org.apache.shiro.util.ThreadContext; import org.apache.shiro.util.ThreadState; -import org.jboss.resteasy.core.Dispatcher; -import org.jboss.resteasy.mock.MockDispatcherFactory; import org.jboss.resteasy.mock.MockHttpRequest; import org.jboss.resteasy.mock.MockHttpResponse; import org.junit.After; @@ -18,16 +16,17 @@ import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; import sonia.scm.NotFoundException; -import sonia.scm.api.rest.AuthorizationExceptionMapper; -import sonia.scm.api.v2.NotFoundExceptionMapper; import sonia.scm.repository.NamespaceAndName; import sonia.scm.repository.Repository; import sonia.scm.repository.api.DiffCommandBuilder; import sonia.scm.repository.api.DiffFormat; import sonia.scm.repository.api.RepositoryService; import sonia.scm.repository.api.RepositoryServiceFactory; +import sonia.scm.util.CRLFInjectionException; +import sonia.scm.web.RestDispatcher; import sonia.scm.web.VndMediaType; +import javax.ws.rs.core.Response; import java.net.URISyntaxException; import java.util.Arrays; @@ -46,7 +45,8 @@ public class DiffResourceTest extends RepositoryTestBase { public static final String DIFF_PATH = "space/repo/diff/"; public static final String DIFF_URL = "/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + DIFF_PATH; - private final Dispatcher dispatcher = MockDispatcherFactory.createDispatcher(); + + private RestDispatcher dispatcher = new RestDispatcher(); @Mock private RepositoryServiceFactory serviceFactory; @@ -68,14 +68,12 @@ public class DiffResourceTest extends RepositoryTestBase { public void prepareEnvironment() { diffRootResource = new DiffRootResource(serviceFactory); super.diffRootResource = Providers.of(diffRootResource); - dispatcher.getRegistry().addSingletonResource(getRepositoryRootResource()); + dispatcher.addSingletonResource(getRepositoryRootResource()); when(serviceFactory.create(new NamespaceAndName("space", "repo"))).thenReturn(service); when(serviceFactory.create(any(Repository.class))).thenReturn(service); when(service.getRepository()).thenReturn(new Repository("repoId", "git", "space", "repo")); ExceptionWithContextToErrorDtoMapperImpl mapper = new ExceptionWithContextToErrorDtoMapperImpl(); - dispatcher.getProviderFactory().register(new NotFoundExceptionMapper(mapper)); - dispatcher.getProviderFactory().registerProvider(AuthorizationExceptionMapper.class); - dispatcher.getProviderFactory().registerProvider(CRLFInjectionExceptionMapper.class); + dispatcher.registerException(CRLFInjectionException.class, Response.Status.BAD_REQUEST); when(service.getDiffCommand()).thenReturn(diffCommandBuilder); subjectThreadState.bind(); ThreadContext.bind(subject); diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/DispatcherMock.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/DispatcherMock.java deleted file mode 100644 index fe205e88a1..0000000000 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/DispatcherMock.java +++ /dev/null @@ -1,24 +0,0 @@ -package sonia.scm.api.v2.resources; - -import org.jboss.resteasy.core.Dispatcher; -import org.jboss.resteasy.mock.MockDispatcherFactory; -import sonia.scm.api.rest.AlreadyExistsExceptionMapper; -import sonia.scm.api.rest.AuthorizationExceptionMapper; -import sonia.scm.api.rest.BadRequestExceptionMapper; -import sonia.scm.api.rest.ConcurrentModificationExceptionMapper; -import sonia.scm.api.v2.NotFoundExceptionMapper; - -public class DispatcherMock { - public static Dispatcher createDispatcher(Object resource) { - Dispatcher dispatcher = MockDispatcherFactory.createDispatcher(); - dispatcher.getRegistry().addSingletonResource(resource); - ExceptionWithContextToErrorDtoMapperImpl mapper = new ExceptionWithContextToErrorDtoMapperImpl(); - dispatcher.getProviderFactory().register(new NotFoundExceptionMapper(mapper)); - dispatcher.getProviderFactory().register(new AlreadyExistsExceptionMapper(mapper)); - dispatcher.getProviderFactory().register(new ConcurrentModificationExceptionMapper(mapper)); - dispatcher.getProviderFactory().registerProvider(AuthorizationExceptionMapper.class); - dispatcher.getProviderFactory().register(new InternalRepositoryExceptionMapper(mapper)); - dispatcher.getProviderFactory().register(new BadRequestExceptionMapper(mapper)); - return dispatcher; - } -} diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/FileHistoryResourceTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/FileHistoryResourceTest.java index a8b3c15158..5803fbec9f 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/FileHistoryResourceTest.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/FileHistoryResourceTest.java @@ -7,7 +7,6 @@ import org.apache.shiro.subject.support.SubjectThreadState; import org.apache.shiro.util.ThreadContext; import org.apache.shiro.util.ThreadState; import org.assertj.core.util.Lists; -import org.jboss.resteasy.core.Dispatcher; import org.jboss.resteasy.mock.MockHttpRequest; import org.jboss.resteasy.mock.MockHttpResponse; import org.junit.After; @@ -28,6 +27,7 @@ import sonia.scm.repository.Repository; import sonia.scm.repository.api.LogCommandBuilder; import sonia.scm.repository.api.RepositoryService; import sonia.scm.repository.api.RepositoryServiceFactory; +import sonia.scm.web.RestDispatcher; import sonia.scm.web.VndMediaType; import java.net.URI; @@ -70,7 +70,7 @@ public class FileHistoryResourceTest extends RepositoryTestBase { private FileHistoryRootResource fileHistoryRootResource; - private Dispatcher dispatcher; + private RestDispatcher dispatcher = new RestDispatcher(); private final Subject subject = mock(Subject.class); private final ThreadState subjectThreadState = new SubjectThreadState(subject); @@ -80,7 +80,7 @@ public class FileHistoryResourceTest extends RepositoryTestBase { fileHistoryCollectionToDtoMapper = new FileHistoryCollectionToDtoMapper(changesetToChangesetDtoMapper, resourceLinks); fileHistoryRootResource = new FileHistoryRootResource(serviceFactory, fileHistoryCollectionToDtoMapper); super.fileHistoryRootResource = Providers.of(fileHistoryRootResource); - dispatcher = DispatcherMock.createDispatcher(getRepositoryRootResource()); + dispatcher.addSingletonResource(getRepositoryRootResource()); when(serviceFactory.create(new NamespaceAndName("space", "repo"))).thenReturn(service); when(serviceFactory.create(any(Repository.class))).thenReturn(service); when(service.getRepository()).thenReturn(new Repository("repoId", "git", "space", "repo")); diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/GroupRootResourceTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/GroupRootResourceTest.java index 09236c0e19..5f2abb85f0 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/GroupRootResourceTest.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/GroupRootResourceTest.java @@ -4,7 +4,6 @@ import com.github.sdorra.shiro.ShiroRule; import com.github.sdorra.shiro.SubjectAware; import com.google.common.io.Resources; import com.google.inject.util.Providers; -import org.jboss.resteasy.core.Dispatcher; import org.jboss.resteasy.mock.MockHttpRequest; import org.jboss.resteasy.mock.MockHttpResponse; import org.junit.Before; @@ -15,12 +14,11 @@ import org.mockito.Captor; import org.mockito.InjectMocks; import org.mockito.Mock; import sonia.scm.PageResult; -import sonia.scm.api.rest.JSONContextResolver; -import sonia.scm.api.rest.ObjectMapperProvider; import sonia.scm.group.Group; import sonia.scm.group.GroupManager; import sonia.scm.security.PermissionAssigner; import sonia.scm.security.PermissionDescriptor; +import sonia.scm.web.RestDispatcher; import sonia.scm.web.VndMediaType; import javax.servlet.http.HttpServletResponse; @@ -43,7 +41,6 @@ import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.when; import static org.mockito.MockitoAnnotations.initMocks; -import static sonia.scm.api.v2.resources.DispatcherMock.createDispatcher; @SubjectAware( username = "trillian", @@ -55,7 +52,7 @@ public class GroupRootResourceTest { @Rule public ShiroRule shiro = new ShiroRule(); - private Dispatcher dispatcher; + private RestDispatcher dispatcher = new RestDispatcher(); private final ResourceLinks resourceLinks = ResourceLinksMock.createMock(URI.create("/")); @@ -91,8 +88,7 @@ public class GroupRootResourceTest { GroupResource groupResource = new GroupResource(groupManager, groupToDtoMapper, dtoToGroupMapper, groupPermissionResource); GroupRootResource groupRootResource = new GroupRootResource(Providers.of(groupCollectionResource), Providers.of(groupResource)); - dispatcher = createDispatcher(groupRootResource); - dispatcher.getProviderFactory().registerProviderInstance(new JSONContextResolver(new ObjectMapperProvider().get())); + dispatcher.addSingletonResource(groupRootResource); } @Test diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/IncomingRootResourceTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/IncomingRootResourceTest.java index 0c1f4235b9..27a9f0d6a5 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/IncomingRootResourceTest.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/IncomingRootResourceTest.java @@ -8,7 +8,6 @@ import org.apache.shiro.subject.support.SubjectThreadState; import org.apache.shiro.util.ThreadContext; import org.apache.shiro.util.ThreadState; import org.assertj.core.util.Lists; -import org.jboss.resteasy.core.Dispatcher; import org.jboss.resteasy.mock.MockHttpRequest; import org.jboss.resteasy.mock.MockHttpResponse; import org.junit.After; @@ -28,8 +27,11 @@ import sonia.scm.repository.api.DiffCommandBuilder; import sonia.scm.repository.api.LogCommandBuilder; import sonia.scm.repository.api.RepositoryService; import sonia.scm.repository.api.RepositoryServiceFactory; +import sonia.scm.util.CRLFInjectionException; +import sonia.scm.web.RestDispatcher; import sonia.scm.web.VndMediaType; +import javax.ws.rs.core.Response; import java.net.URI; import java.net.URISyntaxException; import java.time.Instant; @@ -53,7 +55,7 @@ public class IncomingRootResourceTest extends RepositoryTestBase { public static final String INCOMING_CHANGESETS_URL = "/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + INCOMING_PATH; public static final String INCOMING_DIFF_URL = "/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + INCOMING_PATH; - private Dispatcher dispatcher; + private RestDispatcher dispatcher = new RestDispatcher(); private final URI baseUri = URI.create("/"); private final ResourceLinks resourceLinks = ResourceLinksMock.createMock(baseUri); @@ -88,13 +90,13 @@ public class IncomingRootResourceTest extends RepositoryTestBase { incomingChangesetCollectionToDtoMapper = new IncomingChangesetCollectionToDtoMapper(changesetToChangesetDtoMapper, resourceLinks); incomingRootResource = new IncomingRootResource(serviceFactory, incomingChangesetCollectionToDtoMapper); super.incomingRootResource = Providers.of(incomingRootResource); - dispatcher = DispatcherMock.createDispatcher(getRepositoryRootResource()); + dispatcher.addSingletonResource(getRepositoryRootResource()); when(serviceFactory.create(new NamespaceAndName("space", "repo"))).thenReturn(repositoryService); when(serviceFactory.create(any(Repository.class))).thenReturn(repositoryService); when(repositoryService.getRepository()).thenReturn(new Repository("repoId", "git", "space", "repo")); when(repositoryService.getLogCommand()).thenReturn(logCommandBuilder); when(repositoryService.getDiffCommand()).thenReturn(diffCommandBuilder); - dispatcher.getProviderFactory().registerProvider(CRLFInjectionExceptionMapper.class); + dispatcher.registerException(CRLFInjectionException.class, Response.Status.BAD_REQUEST); subjectThreadState.bind(); ThreadContext.bind(subject); when(subject.isPermitted(any(String.class))).thenReturn(true); diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/InstalledPluginResourceTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/InstalledPluginResourceTest.java index e2a23f0d52..d1d406c6af 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/InstalledPluginResourceTest.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/InstalledPluginResourceTest.java @@ -1,13 +1,11 @@ package sonia.scm.api.v2.resources; import de.otto.edison.hal.HalRepresentation; +import org.apache.shiro.authz.UnauthorizedException; import org.apache.shiro.subject.Subject; import org.apache.shiro.util.ThreadContext; -import org.jboss.resteasy.core.Dispatcher; -import org.jboss.resteasy.mock.MockDispatcherFactory; import org.jboss.resteasy.mock.MockHttpRequest; import org.jboss.resteasy.mock.MockHttpResponse; -import org.jboss.resteasy.spi.UnhandledException; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Nested; @@ -17,9 +15,9 @@ import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import sonia.scm.plugin.InstalledPlugin; -import sonia.scm.plugin.InstalledPluginDescriptor; import sonia.scm.plugin.PluginInformation; import sonia.scm.plugin.PluginManager; +import sonia.scm.web.RestDispatcher; import sonia.scm.web.VndMediaType; import javax.inject.Provider; @@ -31,15 +29,17 @@ import java.util.Optional; import static java.util.Collections.emptyList; import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.Assert.assertEquals; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.*; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; import static sonia.scm.plugin.PluginTestHelper.createInstalled; @ExtendWith(MockitoExtension.class) class InstalledPluginResourceTest { - private Dispatcher dispatcher; + private RestDispatcher dispatcher = new RestDispatcher(); @Mock Provider installedPluginResourceProvider; @@ -65,10 +65,9 @@ class InstalledPluginResourceTest { @BeforeEach void prepareEnvironment() { - dispatcher = MockDispatcherFactory.createDispatcher(); pluginRootResource = new PluginRootResource(installedPluginResourceProvider, null, null); when(installedPluginResourceProvider.get()).thenReturn(installedPluginResource); - dispatcher.getRegistry().addSingletonResource(pluginRootResource); + dispatcher.addSingletonResource(pluginRootResource); } @Nested @@ -77,7 +76,6 @@ class InstalledPluginResourceTest { @BeforeEach void bindSubject() { ThreadContext.bind(subject); - when(subject.isPermitted(any(String.class))).thenReturn(true); } @AfterEach @@ -129,7 +127,13 @@ class InstalledPluginResourceTest { class WithoutAuthorization { @BeforeEach - void unbindSubject() { + void bindSubject() { + ThreadContext.bind(subject); + doThrow(new UnauthorizedException()).when(subject).checkPermission(any(String.class)); + } + + @AfterEach + public void unbindSubject() { ThreadContext.unbindSubject(); } @@ -139,7 +143,9 @@ class InstalledPluginResourceTest { request.accept(VndMediaType.PLUGIN_COLLECTION); MockHttpResponse response = new MockHttpResponse(); - assertThrows(UnhandledException.class, () -> dispatcher.invoke(request, response)); + dispatcher.invoke(request, response); + + assertEquals(HttpServletResponse.SC_FORBIDDEN, response.getStatus()); } @Test @@ -148,7 +154,9 @@ class InstalledPluginResourceTest { request.accept(VndMediaType.PLUGIN); MockHttpResponse response = new MockHttpResponse(); - assertThrows(UnhandledException.class, () -> dispatcher.invoke(request, response)); + dispatcher.invoke(request, response); + + assertEquals(HttpServletResponse.SC_FORBIDDEN, response.getStatus()); } } diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/MeResourceTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/MeResourceTest.java index 7a3d1b4304..433a17b49a 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/MeResourceTest.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/MeResourceTest.java @@ -7,7 +7,6 @@ import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.credential.PasswordService; import org.apache.shiro.subject.PrincipalCollection; import org.apache.shiro.subject.Subject; -import org.jboss.resteasy.core.Dispatcher; import org.jboss.resteasy.mock.MockHttpRequest; import org.jboss.resteasy.mock.MockHttpResponse; import org.junit.Before; @@ -21,6 +20,7 @@ import sonia.scm.group.GroupCollector; import sonia.scm.user.InvalidPasswordException; import sonia.scm.user.User; import sonia.scm.user.UserManager; +import sonia.scm.web.RestDispatcher; import sonia.scm.web.VndMediaType; import javax.servlet.http.HttpServletResponse; @@ -37,7 +37,6 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.when; import static org.mockito.MockitoAnnotations.initMocks; -import static sonia.scm.api.v2.resources.DispatcherMock.createDispatcher; @SubjectAware( username = "trillian", @@ -49,9 +48,10 @@ public class MeResourceTest { @Rule public ShiroRule shiro = new ShiroRule(); - private Dispatcher dispatcher; + private RestDispatcher dispatcher = new RestDispatcher(); private final ResourceLinks resourceLinks = ResourceLinksMock.createMock(URI.create("/")); + @Mock private ScmPathInfo uriInfo; @Mock @@ -85,7 +85,7 @@ public class MeResourceTest { MeResource meResource = new MeResource(meDtoFactory, userManager, passwordService); when(uriInfo.getApiRestUri()).thenReturn(URI.create("/")); when(scmPathInfoStore.get()).thenReturn(uriInfo); - dispatcher = createDispatcher(meResource); + dispatcher.addSingletonResource(meResource); } @Test diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ModificationsResourceTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ModificationsResourceTest.java index fc4598081d..41217f9fb0 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ModificationsResourceTest.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ModificationsResourceTest.java @@ -7,7 +7,6 @@ import org.apache.shiro.subject.support.SubjectThreadState; import org.apache.shiro.util.ThreadContext; import org.apache.shiro.util.ThreadState; import org.assertj.core.util.Lists; -import org.jboss.resteasy.core.Dispatcher; import org.jboss.resteasy.mock.MockHttpRequest; import org.jboss.resteasy.mock.MockHttpResponse; import org.junit.After; @@ -24,6 +23,7 @@ import sonia.scm.repository.Repository; import sonia.scm.repository.api.ModificationsCommandBuilder; import sonia.scm.repository.api.RepositoryService; import sonia.scm.repository.api.RepositoryServiceFactory; +import sonia.scm.web.RestDispatcher; import sonia.scm.web.VndMediaType; import java.net.URI; @@ -45,7 +45,7 @@ public class ModificationsResourceTest extends RepositoryTestBase { public static final String MODIFICATIONS_PATH = "space/repo/modifications/"; public static final String MODIFICATIONS_URL = "/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + MODIFICATIONS_PATH; - private Dispatcher dispatcher; + private RestDispatcher dispatcher = new RestDispatcher(); private final URI baseUri = URI.create("/"); private final ResourceLinks resourceLinks = ResourceLinksMock.createMock(baseUri); @@ -73,7 +73,7 @@ public class ModificationsResourceTest extends RepositoryTestBase { public void prepareEnvironment() { modificationsRootResource = new ModificationsRootResource(serviceFactory, modificationsToDtoMapper); super.modificationsRootResource = Providers.of(modificationsRootResource); - dispatcher = DispatcherMock.createDispatcher(getRepositoryRootResource()); + dispatcher.addSingletonResource(getRepositoryRootResource()); when(serviceFactory.create(new NamespaceAndName("space", "repo"))).thenReturn(repositoryService); when(serviceFactory.create(any(Repository.class))).thenReturn(repositoryService); when(repositoryService.getRepository()).thenReturn(new Repository("repoId", "git", "space", "repo")); diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/PendingPluginResourceTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/PendingPluginResourceTest.java index da6e1b65d7..17e16729b8 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/PendingPluginResourceTest.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/PendingPluginResourceTest.java @@ -4,8 +4,6 @@ import com.google.inject.util.Providers; import org.apache.shiro.ShiroException; import org.apache.shiro.subject.Subject; import org.apache.shiro.util.ThreadContext; -import org.jboss.resteasy.core.Dispatcher; -import org.jboss.resteasy.mock.MockDispatcherFactory; import org.jboss.resteasy.mock.MockHttpRequest; import org.jboss.resteasy.mock.MockHttpResponse; import org.junit.jupiter.api.AfterEach; @@ -22,10 +20,10 @@ import sonia.scm.plugin.InstalledPlugin; import sonia.scm.plugin.InstalledPluginDescriptor; import sonia.scm.plugin.PluginInformation; import sonia.scm.plugin.PluginManager; +import sonia.scm.web.RestDispatcher; import javax.servlet.http.HttpServletResponse; import javax.ws.rs.core.Response; -import javax.ws.rs.ext.ExceptionMapper; import java.io.UnsupportedEncodingException; import java.net.URISyntaxException; @@ -42,7 +40,7 @@ import static org.mockito.Mockito.when; @ExtendWith(MockitoExtension.class) class PendingPluginResourceTest { - Dispatcher dispatcher = MockDispatcherFactory.createDispatcher(); + private RestDispatcher dispatcher = new RestDispatcher(); ResourceLinks resourceLinks = ResourceLinksMock.createMock(create("/")); @@ -61,10 +59,9 @@ class PendingPluginResourceTest { @BeforeEach void prepareEnvironment() { - dispatcher = MockDispatcherFactory.createDispatcher(); - dispatcher.getProviderFactory().register(new PermissionExceptionMapper()); + dispatcher.registerException(ShiroException.class, Response.Status.UNAUTHORIZED); PluginRootResource pluginRootResource = new PluginRootResource(null, null, Providers.of(pendingPluginResource)); - dispatcher.getRegistry().addSingletonResource(pluginRootResource); + dispatcher.addSingletonResource(pluginRootResource); } @BeforeEach @@ -207,14 +204,6 @@ class PendingPluginResourceTest { } } - static class PermissionExceptionMapper implements ExceptionMapper { - - @Override - public Response toResponse(ShiroException exception) { - return Response.status(401).entity(exception.getMessage()).build(); - } - } - private AvailablePlugin createAvailablePlugin(String name) { PluginInformation pluginInformation = new PluginInformation(); pluginInformation.setName(name); diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryPermissionRootResourceTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryPermissionRootResourceTest.java index f2b835a0a1..294f55f5d2 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryPermissionRootResourceTest.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryPermissionRootResourceTest.java @@ -13,7 +13,6 @@ import org.apache.shiro.subject.support.SubjectThreadState; import org.apache.shiro.util.ThreadContext; import org.apache.shiro.util.ThreadState; import org.assertj.core.util.Lists; -import org.jboss.resteasy.core.Dispatcher; import org.jboss.resteasy.mock.MockHttpRequest; import org.jboss.resteasy.mock.MockHttpResponse; import org.jboss.resteasy.spi.HttpRequest; @@ -30,6 +29,7 @@ import sonia.scm.repository.NamespaceAndName; import sonia.scm.repository.Repository; import sonia.scm.repository.RepositoryManager; import sonia.scm.repository.RepositoryPermission; +import sonia.scm.web.RestDispatcher; import sonia.scm.web.VndMediaType; import javax.ws.rs.HttpMethod; @@ -58,7 +58,6 @@ import static org.mockito.Mockito.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import static org.mockito.MockitoAnnotations.initMocks; -import static sonia.scm.api.v2.resources.DispatcherMock.createDispatcher; import static sonia.scm.api.v2.resources.RepositoryPermissionDto.GROUP_PREFIX; @Slf4j @@ -105,7 +104,7 @@ public class RepositoryPermissionRootResourceTest extends RepositoryTestBase { .content(PERMISSION_TEST_PAYLOAD) .path(PATH_OF_ONE_PERMISSION); - private Dispatcher dispatcher; + private RestDispatcher dispatcher = new RestDispatcher(); @Mock private RepositoryManager repositoryManager; @@ -133,7 +132,7 @@ public class RepositoryPermissionRootResourceTest extends RepositoryTestBase { repositoryPermissionCollectionToDtoMapper = new RepositoryPermissionCollectionToDtoMapper(permissionToPermissionDtoMapper, resourceLinks); repositoryPermissionRootResource = new RepositoryPermissionRootResource(permissionDtoToPermissionMapper, permissionToPermissionDtoMapper, repositoryPermissionCollectionToDtoMapper, resourceLinks, repositoryManager); super.permissionRootResource = Providers.of(repositoryPermissionRootResource); - dispatcher = createDispatcher(getRepositoryRootResource()); + dispatcher.addSingletonResource(getRepositoryRootResource()); subjectThreadState.bind(); ThreadContext.bind(subject); } @@ -180,19 +179,6 @@ public class RepositoryPermissionRootResourceTest extends RepositoryTestBase { requestPUTPermission.expectedResponseStatus(403)); } - @TestFactory - @DisplayName("test endpoints on missing permissions and is _anonymous") - Stream missedPermissionAnonymousUnauthorizedTestFactory() { - when(subject.getPrincipal()).thenReturn("_anonymous"); - doThrow(AuthorizationException.class).when(repositoryManager).get(any(NamespaceAndName.class)); - return createDynamicTestsToAssertResponses( - requestGETPermission.expectedResponseStatus(401), - requestPOSTPermission.expectedResponseStatus(401), - requestGETAllPermissions.expectedResponseStatus(401), - requestDELETEPermission.expectedResponseStatus(401), - requestPUTPermission.expectedResponseStatus(401)); - } - @Test public void userWithPermissionWritePermissionShouldGetAllPermissionsWithCreateAndUpdateLinks() throws URISyntaxException { createUserWithRepositoryAndPermissions(TEST_PERMISSIONS, PERMISSION_WRITE); diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryRoleRootResourceTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryRoleRootResourceTest.java index af968efd1f..f288b12006 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryRoleRootResourceTest.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryRoleRootResourceTest.java @@ -3,7 +3,6 @@ package sonia.scm.api.v2.resources; import com.github.sdorra.shiro.ShiroRule; import com.github.sdorra.shiro.SubjectAware; import com.google.inject.util.Providers; -import org.jboss.resteasy.core.Dispatcher; import org.jboss.resteasy.mock.MockHttpRequest; import org.jboss.resteasy.mock.MockHttpResponse; import org.junit.Before; @@ -16,10 +15,9 @@ import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; import sonia.scm.PageResult; -import sonia.scm.api.rest.JSONContextResolver; -import sonia.scm.api.rest.ObjectMapperProvider; import sonia.scm.repository.RepositoryRole; import sonia.scm.repository.RepositoryRoleManager; +import sonia.scm.web.RestDispatcher; import sonia.scm.web.VndMediaType; import javax.servlet.http.HttpServletResponse; @@ -36,7 +34,6 @@ import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import static sonia.scm.api.v2.resources.DispatcherMock.createDispatcher; @SubjectAware( username = "trillian", @@ -66,7 +63,7 @@ public class RepositoryRoleRootResourceTest { private RepositoryRoleCollectionToDtoMapper collectionToDtoMapper; - private Dispatcher dispatcher; + private RestDispatcher dispatcher = new RestDispatcher(); @Captor private ArgumentCaptor modifyCaptor; @@ -87,8 +84,7 @@ public class RepositoryRoleRootResourceTest { when(repositoryRoleManager.create(createCaptor.capture())).thenAnswer(invocation -> invocation.getArguments()[0]); doNothing().when(repositoryRoleManager).delete(deleteCaptor.capture()); - dispatcher = createDispatcher(rootResource); - dispatcher.getProviderFactory().registerProviderInstance(new JSONContextResolver(new ObjectMapperProvider().get())); + dispatcher.addSingletonResource(rootResource); when(repositoryRoleManager.get(CUSTOM_ROLE)).thenReturn(CUSTOM_REPOSITORY_ROLE); when(repositoryRoleManager.get(SYSTEM_ROLE)).thenReturn(SYSTEM_REPOSITORY_ROLE); diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryRootResourceTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryRootResourceTest.java index af1e91344a..f4c49be693 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryRootResourceTest.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryRootResourceTest.java @@ -6,7 +6,6 @@ import com.google.common.io.Resources; import com.google.inject.util.Providers; import org.apache.shiro.subject.SimplePrincipalCollection; import org.apache.shiro.subject.Subject; -import org.jboss.resteasy.core.Dispatcher; import org.jboss.resteasy.mock.MockHttpRequest; import org.jboss.resteasy.mock.MockHttpResponse; import org.junit.Before; @@ -23,6 +22,7 @@ import sonia.scm.repository.RepositoryManager; import sonia.scm.repository.api.RepositoryService; import sonia.scm.repository.api.RepositoryServiceFactory; import sonia.scm.user.User; +import sonia.scm.web.RestDispatcher; import sonia.scm.web.VndMediaType; import javax.servlet.http.HttpServletResponse; @@ -47,12 +47,10 @@ import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyObject; import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.mockito.MockitoAnnotations.initMocks; -import static sonia.scm.api.v2.resources.DispatcherMock.createDispatcher; @SubjectAware( username = "trillian", @@ -63,7 +61,7 @@ public class RepositoryRootResourceTest extends RepositoryTestBase { private static final String REALM = "AdminRealm"; - private Dispatcher dispatcher; + private RestDispatcher dispatcher = new RestDispatcher(); @Rule public ShiroRule shiro = new ShiroRule(); @@ -98,7 +96,7 @@ public class RepositoryRootResourceTest extends RepositoryTestBase { super.manager = repositoryManager; RepositoryCollectionToDtoMapper repositoryCollectionToDtoMapper = new RepositoryCollectionToDtoMapper(repositoryToDtoMapper, resourceLinks); super.repositoryCollectionResource = Providers.of(new RepositoryCollectionResource(repositoryManager, repositoryCollectionToDtoMapper, dtoToRepositoryMapper, resourceLinks)); - dispatcher = createDispatcher(getRepositoryRootResource()); + dispatcher.addSingletonResource(getRepositoryRootResource()); when(serviceFactory.create(any(Repository.class))).thenReturn(service); when(scmPathInfoStore.get()).thenReturn(uriInfo); when(uriInfo.getApiRestUri()).thenReturn(URI.create("/x/y")); diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryTypeRootResourceTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryTypeRootResourceTest.java index 2476785d70..fcbff438a2 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryTypeRootResourceTest.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryTypeRootResourceTest.java @@ -3,8 +3,6 @@ package sonia.scm.api.v2.resources; import com.google.common.collect.Lists; import com.google.common.collect.Sets; import com.google.inject.util.Providers; -import org.jboss.resteasy.core.Dispatcher; -import org.jboss.resteasy.mock.MockDispatcherFactory; import org.jboss.resteasy.mock.MockHttpRequest; import org.jboss.resteasy.mock.MockHttpResponse; import org.junit.Before; @@ -15,6 +13,7 @@ import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; import sonia.scm.repository.RepositoryManager; import sonia.scm.repository.RepositoryType; +import sonia.scm.web.RestDispatcher; import sonia.scm.web.VndMediaType; import java.net.URI; @@ -32,7 +31,7 @@ import static org.mockito.Mockito.when; @RunWith(MockitoJUnitRunner.Silent.class) public class RepositoryTypeRootResourceTest { - private final Dispatcher dispatcher = MockDispatcherFactory.createDispatcher(); + private RestDispatcher dispatcher = new RestDispatcher(); @Mock private RepositoryManager repositoryManager; @@ -56,7 +55,7 @@ public class RepositoryTypeRootResourceTest { RepositoryTypeCollectionResource collectionResource = new RepositoryTypeCollectionResource(repositoryManager, collectionMapper); RepositoryTypeResource resource = new RepositoryTypeResource(repositoryManager, mapper); RepositoryTypeRootResource rootResource = new RepositoryTypeRootResource(Providers.of(collectionResource), Providers.of(resource)); - dispatcher.getRegistry().addSingletonResource(rootResource); + dispatcher.addSingletonResource(rootResource); } @Test diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/SourceRootResourceTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/SourceRootResourceTest.java index 1a45d3b233..37c7659b1c 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/SourceRootResourceTest.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/SourceRootResourceTest.java @@ -1,7 +1,6 @@ package sonia.scm.api.v2.resources; import com.google.inject.util.Providers; -import org.jboss.resteasy.core.Dispatcher; import org.jboss.resteasy.mock.MockHttpRequest; import org.jboss.resteasy.mock.MockHttpResponse; import org.junit.Before; @@ -18,6 +17,7 @@ import sonia.scm.repository.NamespaceAndName; import sonia.scm.repository.api.BrowseCommandBuilder; import sonia.scm.repository.api.RepositoryService; import sonia.scm.repository.api.RepositoryServiceFactory; +import sonia.scm.web.RestDispatcher; import java.io.IOException; import java.net.URI; @@ -25,13 +25,12 @@ import java.net.URISyntaxException; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.when; -import static sonia.scm.api.v2.resources.DispatcherMock.createDispatcher; @RunWith(MockitoJUnitRunner.Silent.class) public class SourceRootResourceTest extends RepositoryTestBase { - private Dispatcher dispatcher; + private RestDispatcher dispatcher = new RestDispatcher(); private final URI baseUri = URI.create("/"); private final ResourceLinks resourceLinks = ResourceLinksMock.createMock(baseUri); @@ -58,7 +57,7 @@ public class SourceRootResourceTest extends RepositoryTestBase { SourceRootResource sourceRootResource = new SourceRootResource(serviceFactory, browserResultToFileObjectDtoMapper); super.sourceRootResource = Providers.of(sourceRootResource); - dispatcher = createDispatcher(getRepositoryRootResource()); + dispatcher.addSingletonResource(getRepositoryRootResource()); } @Test diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/TagRootResourceTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/TagRootResourceTest.java index 803f2b106c..8a61dc8e25 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/TagRootResourceTest.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/TagRootResourceTest.java @@ -7,7 +7,6 @@ import org.apache.shiro.subject.support.SubjectThreadState; import org.apache.shiro.util.ThreadContext; import org.apache.shiro.util.ThreadState; import org.assertj.core.util.Lists; -import org.jboss.resteasy.core.Dispatcher; import org.jboss.resteasy.mock.MockHttpRequest; import org.jboss.resteasy.mock.MockHttpResponse; import org.junit.After; @@ -17,8 +16,6 @@ import org.junit.runner.RunWith; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; -import sonia.scm.api.rest.AuthorizationExceptionMapper; -import sonia.scm.api.v2.NotFoundExceptionMapper; import sonia.scm.repository.NamespaceAndName; import sonia.scm.repository.Repository; import sonia.scm.repository.Tag; @@ -26,6 +23,7 @@ import sonia.scm.repository.Tags; import sonia.scm.repository.api.RepositoryService; import sonia.scm.repository.api.RepositoryServiceFactory; import sonia.scm.repository.api.TagsCommandBuilder; +import sonia.scm.web.RestDispatcher; import sonia.scm.web.VndMediaType; import java.net.URI; @@ -36,7 +34,6 @@ import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; -import static sonia.scm.api.v2.resources.DispatcherMock.createDispatcher; @Slf4j @RunWith(MockitoJUnitRunner.Silent.class) @@ -44,7 +41,8 @@ public class TagRootResourceTest extends RepositoryTestBase { public static final String TAG_PATH = "space/repo/tags/"; public static final String TAG_URL = "/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + TAG_PATH; - private Dispatcher dispatcher ; + + private RestDispatcher dispatcher = new RestDispatcher(); private final URI baseUri = URI.create("/"); private final ResourceLinks resourceLinks = ResourceLinksMock.createMock(baseUri); @@ -74,12 +72,10 @@ public class TagRootResourceTest extends RepositoryTestBase { tagCollectionToDtoMapper = new TagCollectionToDtoMapper(resourceLinks, tagToTagDtoMapper); tagRootResource = new TagRootResource(serviceFactory, tagCollectionToDtoMapper, tagToTagDtoMapper); super.tagRootResource = Providers.of(tagRootResource); - dispatcher = createDispatcher(getRepositoryRootResource()); + dispatcher.addSingletonResource(getRepositoryRootResource()); when(serviceFactory.create(new NamespaceAndName("space", "repo"))).thenReturn(repositoryService); when(serviceFactory.create(any(Repository.class))).thenReturn(repositoryService); when(repositoryService.getRepository()).thenReturn(new Repository("repoId", "git", "space", "repo")); - dispatcher.getProviderFactory().registerProvider(NotFoundExceptionMapper.class); - dispatcher.getProviderFactory().registerProvider(AuthorizationExceptionMapper.class); when(repositoryService.getTagsCommand()).thenReturn(tagsCommandBuilder); subjectThreadState.bind(); ThreadContext.bind(subject); diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/UIRootResourceTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/UIRootResourceTest.java index 4987dec644..23e914da35 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/UIRootResourceTest.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/UIRootResourceTest.java @@ -3,8 +3,6 @@ package sonia.scm.api.v2.resources; import com.google.common.collect.Lists; import com.google.common.collect.Sets; import com.google.inject.util.Providers; -import org.jboss.resteasy.core.Dispatcher; -import org.jboss.resteasy.mock.MockDispatcherFactory; import org.jboss.resteasy.mock.MockHttpRequest; import org.jboss.resteasy.mock.MockHttpResponse; import org.junit.Before; @@ -12,7 +10,12 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; -import sonia.scm.plugin.*; +import sonia.scm.plugin.InstalledPlugin; +import sonia.scm.plugin.InstalledPluginDescriptor; +import sonia.scm.plugin.PluginInformation; +import sonia.scm.plugin.PluginLoader; +import sonia.scm.plugin.PluginResources; +import sonia.scm.web.RestDispatcher; import sonia.scm.web.VndMediaType; import javax.servlet.http.HttpServletRequest; @@ -24,14 +27,17 @@ import java.util.HashSet; import static javax.servlet.http.HttpServletResponse.SC_NOT_FOUND; import static javax.servlet.http.HttpServletResponse.SC_OK; import static org.hamcrest.Matchers.equalToIgnoringCase; -import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @RunWith(MockitoJUnitRunner.Silent.class) public class UIRootResourceTest { - private final Dispatcher dispatcher = MockDispatcherFactory.createDispatcher(); + private RestDispatcher dispatcher = new RestDispatcher(); @Mock private PluginLoader pluginLoader; @@ -50,7 +56,7 @@ public class UIRootResourceTest { UIPluginResource pluginResource = new UIPluginResource(pluginLoader, collectionMapper, mapper); UIRootResource rootResource = new UIRootResource(Providers.of(pluginResource)); - dispatcher.getRegistry().addSingletonResource(rootResource); + dispatcher.addSingletonResource(rootResource); } @Test diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/UserRootResourceTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/UserRootResourceTest.java index 06d9b89120..d49d1a82b9 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/UserRootResourceTest.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/UserRootResourceTest.java @@ -5,7 +5,6 @@ import com.github.sdorra.shiro.SubjectAware; import com.google.common.io.Resources; import com.google.inject.util.Providers; import org.apache.shiro.authc.credential.PasswordService; -import org.jboss.resteasy.core.Dispatcher; import org.jboss.resteasy.mock.MockHttpRequest; import org.jboss.resteasy.mock.MockHttpResponse; import org.junit.Before; @@ -23,6 +22,7 @@ import sonia.scm.security.PermissionDescriptor; import sonia.scm.user.ChangePasswordNotAllowedException; import sonia.scm.user.User; import sonia.scm.user.UserManager; +import sonia.scm.web.RestDispatcher; import sonia.scm.web.VndMediaType; import javax.servlet.http.HttpServletResponse; @@ -45,7 +45,6 @@ import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.mockito.MockitoAnnotations.initMocks; -import static sonia.scm.api.v2.resources.DispatcherMock.createDispatcher; @SubjectAware( username = "trillian", @@ -57,7 +56,7 @@ public class UserRootResourceTest { @Rule public ShiroRule shiro = new ShiroRule(); - private Dispatcher dispatcher; + private RestDispatcher dispatcher = new RestDispatcher(); private final ResourceLinks resourceLinks = ResourceLinksMock.createMock(URI.create("/")); @@ -99,7 +98,7 @@ public class UserRootResourceTest { UserRootResource userRootResource = new UserRootResource(Providers.of(userCollectionResource), Providers.of(userResource)); - dispatcher = createDispatcher(userRootResource); + dispatcher.addSingletonResource(userRootResource); } @Test diff --git a/scm-webapp/src/test/java/sonia/scm/lifecycle/InjectionContextRestartStrategyTest.java b/scm-webapp/src/test/java/sonia/scm/lifecycle/InjectionContextRestartStrategyTest.java index 355dca4a16..ddd691e5da 100644 --- a/scm-webapp/src/test/java/sonia/scm/lifecycle/InjectionContextRestartStrategyTest.java +++ b/scm-webapp/src/test/java/sonia/scm/lifecycle/InjectionContextRestartStrategyTest.java @@ -18,11 +18,13 @@ class InjectionContextRestartStrategyTest { @Mock private RestartStrategy.InjectionContext context; - private InjectionContextRestartStrategy strategy = new InjectionContextRestartStrategy(); + private InjectionContextRestartStrategy strategy = new InjectionContextRestartStrategy(Thread.currentThread().getContextClassLoader()); @BeforeEach void setWaitToZero() { strategy.setWaitInMs(0L); + // disable gc during tests + strategy.setGcEnabled(false); } @Test @@ -47,7 +49,6 @@ class InjectionContextRestartStrategyTest { @Test void shouldRegisterContextAfterRestart() throws InterruptedException { TestingInjectionContext ctx = new TestingInjectionContext(); - strategy.restart(ctx); Thread.sleep(50L); diff --git a/scm-webapp/src/test/java/sonia/scm/lifecycle/classloading/ClassLoaderLifeCycleTest.java b/scm-webapp/src/test/java/sonia/scm/lifecycle/classloading/ClassLoaderLifeCycleTest.java index 0ab98039d1..a8f37777d7 100644 --- a/scm-webapp/src/test/java/sonia/scm/lifecycle/classloading/ClassLoaderLifeCycleTest.java +++ b/scm-webapp/src/test/java/sonia/scm/lifecycle/classloading/ClassLoaderLifeCycleTest.java @@ -70,7 +70,12 @@ class ClassLoaderLifeCycleTest { URLClassLoader webappClassLoader = spy(new URLClassLoader(new URL[0], Thread.currentThread().getContextClassLoader())); ClassLoaderLifeCycle lifeCycle = createMockedLifeCycle(webappClassLoader); - lifeCycle.setClassLoaderAppendListener(c -> spy(c)); + lifeCycle.setClassLoaderAppendListener(new ClassLoaderLifeCycle.ClassLoaderAppendListener() { + @Override + public C apply(C classLoader) { + return spy(classLoader); + } + }); lifeCycle.initialize(); ClassLoader pluginA = lifeCycle.createChildFirstPluginClassLoader(new URL[0], null, "a");