From 5a2a5e8ef0e5bfe9374e326b0ddf93fc9e24ade2 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Wed, 6 Nov 2019 15:55:07 +0100 Subject: [PATCH 01/53] added development mode for ui --- scm-ui/pom.xml | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/scm-ui/pom.xml b/scm-ui/pom.xml index ffc727f10d..8bddc41fa9 100644 --- a/scm-ui/pom.xml +++ b/scm-ui/pom.xml @@ -68,7 +68,7 @@ run - + @@ -118,4 +118,32 @@ + + + + default + + + + + + + build + + + + dev + + + + development + + + + + build:dev + + + + From 5b5f71a837f4fa00d13052de00cf2f9d32f9b827 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Thu, 7 Nov 2019 10:22:01 +0100 Subject: [PATCH 02/53] do simple property override instead of default profile, to fix ci build --- scm-ui/pom.xml | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/scm-ui/pom.xml b/scm-ui/pom.xml index 8bddc41fa9..e8277fae6e 100644 --- a/scm-ui/pom.xml +++ b/scm-ui/pom.xml @@ -17,6 +17,7 @@ scm-ui + build typescript ui-extensions/src,ui-components/src,ui-webapp/src **/*.test.js,src/tests/** @@ -120,17 +121,6 @@ - - default - - - - - - - build - - dev From 5bc8eedfb28d7cb8a8133bd68cb44890abd98a9b Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Tue, 12 Nov 2019 08:42:01 +0100 Subject: [PATCH 03/53] update prettier and typescript in order to get support for optional chaining --- scm-ui/prettier-config/package.json | 2 +- scm-ui/tsconfig/package.json | 2 +- scm-ui/ui-components/package.json | 2 +- scm-ui/ui-extensions/package.json | 2 +- scm-ui/ui-tests/package.json | 2 +- scm-ui/ui-types/package.json | 2 +- scm-ui/ui-webapp/package.json | 3 +-- yarn.lock | 12 +++++++++++- 8 files changed, 18 insertions(+), 9 deletions(-) diff --git a/scm-ui/prettier-config/package.json b/scm-ui/prettier-config/package.json index 584081645c..097b07ee2b 100644 --- a/scm-ui/prettier-config/package.json +++ b/scm-ui/prettier-config/package.json @@ -7,7 +7,7 @@ "private": false, "main": "index.js", "dependencies": { - "prettier": "^1.18.2" + "prettier": "^1.19.1" }, "publishConfig": { "access": "public" diff --git a/scm-ui/tsconfig/package.json b/scm-ui/tsconfig/package.json index 4ee8613469..0ddbfc5975 100644 --- a/scm-ui/tsconfig/package.json +++ b/scm-ui/tsconfig/package.json @@ -7,7 +7,7 @@ "private": false, "main": "tsconfig.json", "dependencies": { - "typescript": "^3.6.4" + "typescript": "^3.7.2" }, "publishConfig": { "access": "public" diff --git a/scm-ui/ui-components/package.json b/scm-ui/ui-components/package.json index e7266aae6f..946416ca96 100644 --- a/scm-ui/ui-components/package.json +++ b/scm-ui/ui-components/package.json @@ -42,7 +42,7 @@ "raf": "^3.4.0", "react-test-renderer": "^16.10.2", "storybook-addon-i18next": "^1.2.1", - "typescript": "^3.6.4" + "typescript": "^3.7.2" }, "dependencies": { "@scm-manager/ui-extensions": "^2.0.0-SNAPSHOT", diff --git a/scm-ui/ui-extensions/package.json b/scm-ui/ui-extensions/package.json index 28e1d99905..cedc53fe47 100644 --- a/scm-ui/ui-extensions/package.json +++ b/scm-ui/ui-extensions/package.json @@ -16,7 +16,7 @@ "@types/enzyme": "^3.10.3", "@types/jest": "^24.0.19", "@types/react": "^16.9.9", - "typescript": "^3.6.4" + "typescript": "^3.7.2" }, "babel": { "presets": [ diff --git a/scm-ui/ui-tests/package.json b/scm-ui/ui-tests/package.json index 45978d3836..9958841160 100644 --- a/scm-ui/ui-tests/package.json +++ b/scm-ui/ui-tests/package.json @@ -26,6 +26,6 @@ "@types/enzyme": "^3.10.3", "@types/enzyme-adapter-react-16": "^1.0.5", "@types/jest": "^24.0.19", - "typescript": "^3.6.4" + "typescript": "^3.7.2" } } diff --git a/scm-ui/ui-types/package.json b/scm-ui/ui-types/package.json index fb971290a8..c872d70463 100644 --- a/scm-ui/ui-types/package.json +++ b/scm-ui/ui-types/package.json @@ -14,7 +14,7 @@ "typecheck": "tsc" }, "devDependencies": { - "typescript": "^3.6.4" + "typescript": "^3.7.2" }, "babel": { "presets": [ diff --git a/scm-ui/ui-webapp/package.json b/scm-ui/ui-webapp/package.json index af33163aeb..0c2b5e4821 100644 --- a/scm-ui/ui-webapp/package.json +++ b/scm-ui/ui-webapp/package.json @@ -26,8 +26,7 @@ "systemjs": "0.21.6" }, "scripts": { - "test": "jest", - "flow": "flow" + "test": "jest" }, "devDependencies": { "@scm-manager/ui-tests": "^2.0.0-SNAPSHOT", diff --git a/yarn.lock b/yarn.lock index 464bc0495c..650308884e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -11199,6 +11199,11 @@ prettier@^1.16.4, prettier@^1.18.2: resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.18.2.tgz#6823e7c5900017b4bd3acf46fe9ac4b4d7bda9ea" integrity sha512-OeHeMc0JhFE9idD4ZdtNibzY0+TPHSpSSb9h8FqtP+YnoZZ1sl8Vc9b1sasjfymH3SonAF4QcA2+mzHPhMvIiw== +prettier@^1.19.1: + version "1.19.1" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.19.1.tgz#f7d7f5ff8a9cd872a7be4ca142095956a60797cb" + integrity sha512-s7PoyDv/II1ObgQunCbB9PdLmUcBZcnWOcxDh7O0N/UwDEsHyqkW+Qh28jW+mVuCdx7gLB0BotYI1Y6uI9iyew== + pretty-error@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/pretty-error/-/pretty-error-2.1.1.tgz#5f4f87c8f91e5ae3f3ba87ab4cf5e03b1a17f1a3" @@ -13887,11 +13892,16 @@ typescript-compiler@^1.4.1-2: resolved "https://registry.yarnpkg.com/typescript-compiler/-/typescript-compiler-1.4.1-2.tgz#ba4f7db22d91534a1929d90009dce161eb72fd3f" integrity sha1-uk99si2RU0oZKdkACdzhYety/T8= -typescript@^3.4, typescript@^3.6.4: +typescript@^3.4: version "3.6.4" resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.6.4.tgz#b18752bb3792bc1a0281335f7f6ebf1bbfc5b91d" integrity sha512-unoCll1+l+YK4i4F8f22TaNVPRHcD9PA3yCuZ8g5e0qGqlVlJ/8FSateOLLSagn+Yg5+ZwuPkL8LFUc0Jcvksg== +typescript@^3.7.2: + version "3.7.2" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.7.2.tgz#27e489b95fa5909445e9fef5ee48d81697ad18fb" + integrity sha512-ml7V7JfiN2Xwvcer+XAf2csGO1bPBdRbFCkYBczNZggrBZ9c7G3riSUeJmqEU5uOtXNPMhE3n+R4FA/3YOAWOQ== + ua-parser-js@^0.7.18: version "0.7.20" resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.20.tgz#7527178b82f6a62a0f243d1f94fd30e3e3c21098" From 9e9df35fdba8abe6b9974aae333814417311c783 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Wed, 20 Nov 2019 10:57:32 +0100 Subject: [PATCH 04/53] upgrade resteasy, legman and hibernate-validator * resteasy from 3.6.2.Final to 4.4.1.Final * legman from 1.5.1 to 1.6.0 * hibernate-validator from 5.3.6.Final to 6.1.0.Final --- pom.xml | 12 ++++++--- scm-core/pom.xml | 2 +- scm-plugins/pom.xml | 8 +++++- .../v2/resources/GitConfigResourceTest.java | 2 +- ...HgConfigAutoConfigurationResourceTest.java | 2 +- .../HgConfigInstallationsResourceTest.java | 2 +- .../HgConfigPackageResourceTest.java | 2 +- .../v2/resources/HgConfigResourceTest.java | 2 +- .../v2/resources/SvnConfigResourceTest.java | 2 +- scm-webapp/pom.xml | 26 ++++++++++++++----- .../ResteasyAllInOneServletDispatcher.java | 7 ++++- .../resources/AuthenticationResourceTest.java | 7 +++-- .../resources/AutoCompleteResourceTest.java | 2 +- .../AvailablePluginResourceTest.java | 2 +- .../v2/resources/BranchRootResourceTest.java | 16 +++++------- .../resources/ChangesetRootResourceTest.java | 2 +- .../api/v2/resources/ConfigResourceTest.java | 2 +- .../api/v2/resources/DiffResourceTest.java | 2 +- .../scm/api/v2/resources/DispatcherMock.java | 2 +- .../v2/resources/FileHistoryResourceTest.java | 2 +- .../v2/resources/GroupRootResourceTest.java | 2 +- .../resources/IncomingRootResourceTest.java | 2 +- .../InstalledPluginResourceTest.java | 2 +- .../scm/api/v2/resources/MeResourceTest.java | 2 +- .../resources/ModificationsResourceTest.java | 2 +- .../resources/PendingPluginResourceTest.java | 2 +- .../RepositoryPermissionRootResourceTest.java | 2 +- .../RepositoryRoleRootResourceTest.java | 2 +- .../resources/RepositoryRootResourceTest.java | 2 +- .../RepositoryTypeRootResourceTest.java | 2 +- .../v2/resources/SourceRootResourceTest.java | 2 +- .../api/v2/resources/TagRootResourceTest.java | 2 +- .../api/v2/resources/UIRootResourceTest.java | 2 +- .../v2/resources/UserRootResourceTest.java | 2 +- 34 files changed, 78 insertions(+), 54 deletions(-) diff --git a/pom.xml b/pom.xml index 910287abf0..9fa7045684 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.5.1 + 1.6.0 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..7cfc79835b 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,7 +2,7 @@ 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.spi.Dispatcher; import org.jboss.resteasy.mock.MockDispatcherFactory; import org.jboss.resteasy.mock.MockHttpRequest; import org.jboss.resteasy.mock.MockHttpResponse; 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..21da84e7ec 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,7 +2,7 @@ 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.spi.Dispatcher; import org.jboss.resteasy.mock.MockDispatcherFactory; import org.jboss.resteasy.mock.MockHttpRequest; import org.jboss.resteasy.mock.MockHttpResponse; 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..2293d1ddb7 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,7 +2,7 @@ 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.spi.Dispatcher; import org.jboss.resteasy.mock.MockDispatcherFactory; import org.jboss.resteasy.mock.MockHttpRequest; import org.jboss.resteasy.mock.MockHttpResponse; 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..29032ac8de 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,7 +5,7 @@ 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.spi.Dispatcher; import org.jboss.resteasy.mock.MockDispatcherFactory; import org.jboss.resteasy.mock.MockHttpRequest; import org.jboss.resteasy.mock.MockHttpResponse; 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..910f5721e9 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,7 +4,7 @@ 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.spi.Dispatcher; import org.jboss.resteasy.mock.MockDispatcherFactory; import org.jboss.resteasy.mock.MockHttpRequest; import org.jboss.resteasy.mock.MockHttpResponse; 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..6a854471dd 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,7 +4,7 @@ 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.spi.Dispatcher; import org.jboss.resteasy.mock.MockDispatcherFactory; import org.jboss.resteasy.mock.MockHttpRequest; import org.jboss.resteasy.mock.MockHttpResponse; 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/lifecycle/modules/ResteasyAllInOneServletDispatcher.java b/scm-webapp/src/main/java/sonia/scm/lifecycle/modules/ResteasyAllInOneServletDispatcher.java index a30cb20c01..0cbb3cd671 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 @@ -72,8 +72,13 @@ public class ResteasyAllInOneServletDispatcher extends HttpServletDispatcher { // 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/test/java/sonia/scm/api/v2/resources/AuthenticationResourceTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/AuthenticationResourceTest.java index 177f975971..c5756fadd6 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,7 +2,7 @@ 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.spi.Dispatcher; import org.jboss.resteasy.mock.MockDispatcherFactory; import org.jboss.resteasy.mock.MockHttpRequest; import org.jboss.resteasy.mock.MockHttpResponse; @@ -125,10 +125,9 @@ public class AuthenticationResourceTest { when(accessTokenBuilderFactory.create()).thenReturn(accessTokenBuilder); HttpServletRequest servletRequest = mock(HttpServletRequest.class); - ResteasyProviderFactory.getContextDataMap().put(HttpServletRequest.class, servletRequest); - + dispatcher.getDefaultContextObjects().put(HttpServletRequest.class, servletRequest); HttpServletResponse servletResponse = mock(HttpServletResponse.class); - ResteasyProviderFactory.getContextDataMap().put(HttpServletResponse.class, servletResponse); + dispatcher.getDefaultContextObjects().put(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..f8b4236b41 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,7 @@ 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.spi.Dispatcher; import org.jboss.resteasy.mock.MockHttpRequest; import org.jboss.resteasy.mock.MockHttpResponse; import org.junit.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..99190d942d 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 @@ -4,7 +4,7 @@ import de.otto.edison.hal.HalRepresentation; 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.spi.Dispatcher; import org.jboss.resteasy.mock.MockDispatcherFactory; import org.jboss.resteasy.mock.MockHttpRequest; import org.jboss.resteasy.mock.MockHttpResponse; 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..45a2d1613c 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 @@ -7,10 +7,9 @@ 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.MockDispatcherFactory; import org.jboss.resteasy.mock.MockHttpRequest; import org.jboss.resteasy.mock.MockHttpResponse; +import org.jboss.resteasy.spi.Dispatcher; import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -32,6 +31,7 @@ import sonia.scm.repository.api.RepositoryService; import sonia.scm.repository.api.RepositoryServiceFactory; import sonia.scm.web.VndMediaType; +import javax.ws.rs.core.MediaType; import java.net.URI; import java.time.Instant; import java.util.Date; @@ -39,13 +39,8 @@ import java.util.List; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyInt; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.*; @RunWith(MockitoJUnitRunner.Silent.class) @Slf4j @@ -129,7 +124,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"); + assertEquals("application/vnd.scmm-error+json;v=2", contentType.toString()); } @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..601a4a4652 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,7 @@ 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.spi.Dispatcher; import org.jboss.resteasy.mock.MockHttpRequest; import org.jboss.resteasy.mock.MockHttpResponse; import org.junit.After; 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..74bb0ab38a 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,7 +4,7 @@ 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.spi.Dispatcher; import org.jboss.resteasy.mock.MockDispatcherFactory; import org.jboss.resteasy.mock.MockHttpRequest; import org.jboss.resteasy.mock.MockHttpResponse; 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..c3d4b2fffa 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,7 +7,7 @@ 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.spi.Dispatcher; import org.jboss.resteasy.mock.MockDispatcherFactory; import org.jboss.resteasy.mock.MockHttpRequest; import org.jboss.resteasy.mock.MockHttpResponse; 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 index fe205e88a1..40b2ac54b7 100644 --- 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 @@ -1,6 +1,6 @@ package sonia.scm.api.v2.resources; -import org.jboss.resteasy.core.Dispatcher; +import org.jboss.resteasy.spi.Dispatcher; import org.jboss.resteasy.mock.MockDispatcherFactory; import sonia.scm.api.rest.AlreadyExistsExceptionMapper; import sonia.scm.api.rest.AuthorizationExceptionMapper; 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..b5b903508a 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,7 @@ 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.spi.Dispatcher; import org.jboss.resteasy.mock.MockHttpRequest; import org.jboss.resteasy.mock.MockHttpResponse; import org.junit.After; 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..bb024eb67c 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,7 @@ 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.spi.Dispatcher; import org.jboss.resteasy.mock.MockHttpRequest; import org.jboss.resteasy.mock.MockHttpResponse; import org.junit.Before; 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..de0eaeb992 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,7 @@ 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.spi.Dispatcher; import org.jboss.resteasy.mock.MockHttpRequest; import org.jboss.resteasy.mock.MockHttpResponse; import org.junit.After; 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..8c9ed5ef2a 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 @@ -3,7 +3,7 @@ package sonia.scm.api.v2.resources; import de.otto.edison.hal.HalRepresentation; import org.apache.shiro.subject.Subject; import org.apache.shiro.util.ThreadContext; -import org.jboss.resteasy.core.Dispatcher; +import org.jboss.resteasy.spi.Dispatcher; import org.jboss.resteasy.mock.MockDispatcherFactory; import org.jboss.resteasy.mock.MockHttpRequest; import org.jboss.resteasy.mock.MockHttpResponse; 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..7aeabe6bec 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,7 @@ 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.spi.Dispatcher; import org.jboss.resteasy.mock.MockHttpRequest; import org.jboss.resteasy.mock.MockHttpResponse; import org.junit.Before; 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..8e84f9d1bb 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,7 @@ 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.spi.Dispatcher; import org.jboss.resteasy.mock.MockHttpRequest; import org.jboss.resteasy.mock.MockHttpResponse; import org.junit.After; 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..3613069d63 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,7 +4,7 @@ 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.spi.Dispatcher; import org.jboss.resteasy.mock.MockDispatcherFactory; import org.jboss.resteasy.mock.MockHttpRequest; import org.jboss.resteasy.mock.MockHttpResponse; 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..a2e63dd50e 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,7 @@ 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.spi.Dispatcher; import org.jboss.resteasy.mock.MockHttpRequest; import org.jboss.resteasy.mock.MockHttpResponse; import org.jboss.resteasy.spi.HttpRequest; 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..d60a32f5dc 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,7 @@ 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.spi.Dispatcher; import org.jboss.resteasy.mock.MockHttpRequest; import org.jboss.resteasy.mock.MockHttpResponse; import org.junit.Before; 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..fcd34334b2 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,7 @@ 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.spi.Dispatcher; import org.jboss.resteasy.mock.MockHttpRequest; import org.jboss.resteasy.mock.MockHttpResponse; import org.junit.Before; 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..17ea3248c2 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,7 +3,7 @@ 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.spi.Dispatcher; import org.jboss.resteasy.mock.MockDispatcherFactory; import org.jboss.resteasy.mock.MockHttpRequest; import org.jboss.resteasy.mock.MockHttpResponse; 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..a25aabaca1 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,7 @@ package sonia.scm.api.v2.resources; import com.google.inject.util.Providers; -import org.jboss.resteasy.core.Dispatcher; +import org.jboss.resteasy.spi.Dispatcher; import org.jboss.resteasy.mock.MockHttpRequest; import org.jboss.resteasy.mock.MockHttpResponse; import org.junit.Before; 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..e97f6c726f 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,7 @@ 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.spi.Dispatcher; import org.jboss.resteasy.mock.MockHttpRequest; import org.jboss.resteasy.mock.MockHttpResponse; import org.junit.After; 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..0391e43c81 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,7 +3,7 @@ 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.spi.Dispatcher; import org.jboss.resteasy.mock.MockDispatcherFactory; import org.jboss.resteasy.mock.MockHttpRequest; import org.jboss.resteasy.mock.MockHttpResponse; 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..e055bbf3b5 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,7 @@ 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.spi.Dispatcher; import org.jboss.resteasy.mock.MockHttpRequest; import org.jboss.resteasy.mock.MockHttpResponse; import org.junit.Before; From c944f234472a891d7f72b66d8d034043fb4f9508 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Thu, 21 Nov 2019 16:13:25 +0100 Subject: [PATCH 05/53] update legman to version 1.6.1, in order to fix a ExecutorService leak --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 9fa7045684..98ddfde58a 100644 --- a/pom.xml +++ b/pom.xml @@ -845,7 +845,7 @@ 2.3.0 - 1.6.0 + 1.6.1 9.4.22.v20191022 From ff7b8ca842097dd0e4a5de0a6cde847ed910d85e Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Thu, 21 Nov 2019 16:16:15 +0100 Subject: [PATCH 06/53] make ClassLoaderLeakPreventorFactory configurable and mark BootstrapClassLoader as shutdown --- .../classloading/BootstrapClassLoader.java | 22 +++++ .../classloading/ClassLoaderLifeCycle.java | 97 ++++++++++++++++--- .../ClassLoaderLifeCycleTest.java | 7 +- 3 files changed, 111 insertions(+), 15 deletions(-) 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 24d1c239b4..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,7 +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; @@ -16,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. @@ -27,23 +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); - 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) { @@ -51,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; } @@ -84,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(); @@ -98,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/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"); From 755b99f52447d2879cd4612c82b2ee2ba0b60a1b Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Thu, 21 Nov 2019 16:17:30 +0100 Subject: [PATCH 07/53] close CronThreadFactory to avoid thread leak --- .../src/main/java/sonia/scm/schedule/CronScheduler.java | 5 ++++- .../src/main/java/sonia/scm/schedule/CronThreadFactory.java | 6 +++++- 2 files changed, 9 insertions(+), 2 deletions(-) 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(); From d1a5f6f24b33405f5882f8761e63dfa974b208fa Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Thu, 21 Nov 2019 16:20:55 +0100 Subject: [PATCH 08/53] fix wrong ClassLoader for Delayed-Restart Thread, which has caused an ClassLoader leak. Also added system properties to configure shutdown only, wait between stop and start and possibility to disable gc. --- .../sonia/scm/event/LegmanScmEventBus.java | 52 +++++++++++++------ .../scm/event/ShutdownEventBusEvent.java | 4 ++ .../scm/lifecycle/BootstrapContextFilter.java | 7 ++- .../InjectionContextRestartStrategy.java | 49 +++++++++++++++-- .../sonia/scm/lifecycle/RestartStrategy.java | 5 +- .../InjectionContextRestartStrategyTest.java | 5 +- 6 files changed, 97 insertions(+), 25 deletions(-) create mode 100644 scm-webapp/src/main/java/sonia/scm/event/ShutdownEventBusEvent.java 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/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/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); From 571f5aa4212536900b90f6101affe679feb7fdc9 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Thu, 21 Nov 2019 16:21:52 +0100 Subject: [PATCH 09/53] destroy guice context from BootstrapContextListener --- .../java/sonia/scm/lifecycle/BootstrapContextListener.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) 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 223e3cf65b..5b9f40dbc2 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; @@ -110,6 +110,8 @@ public class BootstrapContextListener extends GuiceServletContextListener { injectionLifeCycle.shutdown(); injectionLifeCycle = null; classLoaderLifeCycle.shutdown(); + + super.contextDestroyed(sce); } private Injector createStageTwoInjector() { From 2fde62256063931de8c79726aa31ba1a4db1fa1b Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Thu, 21 Nov 2019 16:22:39 +0100 Subject: [PATCH 10/53] reset resteasy StatisticsController to avoid ClassLoader leaks --- .../modules/ResteasyAllInOneServletDispatcher.java | 7 +++++++ 1 file changed, 7 insertions(+) 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 0cbb3cd671..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,6 +71,12 @@ 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()); RuntimeDelegate.setInstance(null); From ca8d5956e0c3246f78e5930cf28587f17a451821 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Thu, 21 Nov 2019 16:23:04 +0100 Subject: [PATCH 11/53] shutdown executor on context destroy --- .../java/sonia/scm/web/cgi/DefaultCGIExecutorFactory.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) 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; } From 6bf86fab8d98ccd35afd5c00f2ace5e9730051ab Mon Sep 17 00:00:00 2001 From: Rene Pfeuffer Date: Mon, 25 Nov 2019 17:04:58 +0100 Subject: [PATCH 12/53] Introduce abstraction layer for RESTeasy mock dispatcher --- .../v2/resources/GitConfigResourceTest.java | 25 ++-- ...HgConfigAutoConfigurationResourceTest.java | 25 ++-- .../HgConfigInstallationsResourceTest.java | 21 ++-- .../HgConfigPackageResourceTest.java | 21 ++-- .../v2/resources/HgConfigResourceTest.java | 25 ++-- .../v2/resources/SvnConfigResourceTest.java | 25 ++-- scm-test/pom.xml | 14 +++ .../java/sonia/scm/web/ScmTestDispatcher.java | 109 ++++++++++++++++++ .../AuthorizationExceptionMapperTest.java | 5 + .../resources/AuthenticationResourceTest.java | 14 +-- .../resources/AutoCompleteResourceTest.java | 8 +- .../AvailablePluginResourceTest.java | 28 +++-- .../v2/resources/BranchRootResourceTest.java | 19 ++- .../resources/ChangesetRootResourceTest.java | 6 +- .../api/v2/resources/ConfigResourceTest.java | 23 ++-- .../api/v2/resources/DiffResourceTest.java | 16 ++- .../scm/api/v2/resources/DispatcherMock.java | 24 ---- .../v2/resources/FileHistoryResourceTest.java | 6 +- .../v2/resources/GroupRootResourceTest.java | 10 +- .../resources/IncomingRootResourceTest.java | 10 +- .../InstalledPluginResourceTest.java | 34 +++--- .../scm/api/v2/resources/MeResourceTest.java | 8 +- .../resources/ModificationsResourceTest.java | 6 +- .../resources/PendingPluginResourceTest.java | 19 +-- .../RepositoryPermissionRootResourceTest.java | 20 +--- .../RepositoryRoleRootResourceTest.java | 10 +- .../resources/RepositoryRootResourceTest.java | 8 +- .../RepositoryTypeRootResourceTest.java | 7 +- .../v2/resources/SourceRootResourceTest.java | 7 +- .../api/v2/resources/TagRootResourceTest.java | 12 +- .../api/v2/resources/UIRootResourceTest.java | 18 ++- .../v2/resources/UserRootResourceTest.java | 7 +- 32 files changed, 327 insertions(+), 263 deletions(-) create mode 100644 scm-test/src/main/java/sonia/scm/web/ScmTestDispatcher.java create mode 100644 scm-webapp/src/test/java/sonia/scm/api/rest/AuthorizationExceptionMapperTest.java delete mode 100644 scm-webapp/src/test/java/sonia/scm/api/v2/resources/DispatcherMock.java 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 7cfc79835b..3d3987aca3 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.spi.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.ScmTestDispatcher; 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 ScmTestDispatcher dispatcher = new ScmTestDispatcher(); 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 21da84e7ec..fa2b559041 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.spi.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.ScmTestDispatcher; 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 ScmTestDispatcher dispatcher = new ScmTestDispatcher(); @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 2293d1ddb7..827c69d57f 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.spi.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.ScmTestDispatcher; 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 ScmTestDispatcher dispatcher = new ScmTestDispatcher(); 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 29032ac8de..6b77b7a0fc 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.spi.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.ScmTestDispatcher; 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 ScmTestDispatcher dispatcher = new ScmTestDispatcher(); 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 910f5721e9..f03c894b13 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.spi.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.ScmTestDispatcher; 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 ScmTestDispatcher dispatcher = new ScmTestDispatcher(); 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 6a854471dd..225c838a6c 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.spi.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.ScmTestDispatcher; 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 ScmTestDispatcher dispatcher = new ScmTestDispatcher(); 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/ScmTestDispatcher.java b/scm-test/src/main/java/sonia/scm/web/ScmTestDispatcher.java new file mode 100644 index 0000000000..f8b3040f73 --- /dev/null +++ b/scm-test/src/main/java/sonia/scm/web/ScmTestDispatcher.java @@ -0,0 +1,109 @@ +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 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 ScmTestDispatcher { + + private static final Logger LOG = LoggerFactory.getLogger(ScmTestDispatcher.class); + + private final Dispatcher dispatcher; + private final EnhanceableExceptionMapper exceptionMapper; + + public ScmTestDispatcher() { + 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); + } + + 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/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..f546a7cf34 --- /dev/null +++ b/scm-webapp/src/test/java/sonia/scm/api/rest/AuthorizationExceptionMapperTest.java @@ -0,0 +1,5 @@ +package sonia.scm.api.rest; + +class AuthorizationExceptionMapperTest { +// TODO verify differentiation between normal user and anonymous +} 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 c5756fadd6..e96301ad2a 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.spi.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.ScmTestDispatcher; 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 ScmTestDispatcher dispatcher = new ScmTestDispatcher(); @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,9 +121,9 @@ public class AuthenticationResourceTest { when(accessTokenBuilderFactory.create()).thenReturn(accessTokenBuilder); HttpServletRequest servletRequest = mock(HttpServletRequest.class); - dispatcher.getDefaultContextObjects().put(HttpServletRequest.class, servletRequest); + dispatcher.putDefaultContextObject(HttpServletRequest.class, servletRequest); HttpServletResponse servletResponse = mock(HttpServletResponse.class); - dispatcher.getDefaultContextObjects().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 f8b4236b41..7356feafe1 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.spi.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.ScmTestDispatcher; 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 ScmTestDispatcher dispatcher = new ScmTestDispatcher(); 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 99190d942d..fd8b8b9fe9 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.spi.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.ScmTestDispatcher; 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 ScmTestDispatcher dispatcher = new ScmTestDispatcher(); @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 45a2d1613c..43f3d9f74e 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,10 +6,10 @@ 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.mock.MockHttpRequest; import org.jboss.resteasy.mock.MockHttpResponse; -import org.jboss.resteasy.spi.Dispatcher; import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -29,6 +29,7 @@ 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.ScmTestDispatcher; import sonia.scm.web.VndMediaType; import javax.ws.rs.core.MediaType; @@ -39,8 +40,13 @@ import java.util.List; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; -import static org.mockito.ArgumentMatchers.*; -import static org.mockito.Mockito.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; @RunWith(MockitoJUnitRunner.Silent.class) @Slf4j @@ -49,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 ScmTestDispatcher dispatcher = new ScmTestDispatcher(); private final URI baseUri = URI.create("/"); private final ResourceLinks resourceLinks = ResourceLinksMock.createMock(baseUri); @@ -96,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")); @@ -125,7 +132,7 @@ public class BranchRootResourceTest extends RepositoryTestBase { assertEquals(404, response.getStatus()); MediaType contentType = (MediaType) response.getOutputHeaders().getFirst("Content-Type"); - assertEquals("application/vnd.scmm-error+json;v=2", contentType.toString()); + 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 601a4a4652..5e890e36e6 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.spi.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.ScmTestDispatcher; 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 ScmTestDispatcher dispatcher = new ScmTestDispatcher(); 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 74bb0ab38a..68a743dab3 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.spi.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.ScmTestDispatcher; 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 ScmTestDispatcher dispatcher = new ScmTestDispatcher(); 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 c3d4b2fffa..fd41150aa8 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.spi.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.ScmTestDispatcher; 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 ScmTestDispatcher dispatcher = new ScmTestDispatcher(); @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 40b2ac54b7..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.spi.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 b5b903508a..907729d161 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.spi.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.ScmTestDispatcher; 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 ScmTestDispatcher dispatcher = new ScmTestDispatcher(); 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 bb024eb67c..97088f9d6f 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.spi.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.ScmTestDispatcher; 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 ScmTestDispatcher dispatcher = new ScmTestDispatcher(); 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 de0eaeb992..481960e8c7 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.spi.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.ScmTestDispatcher; 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 ScmTestDispatcher dispatcher = new ScmTestDispatcher(); 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 8c9ed5ef2a..5319725c9d 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.spi.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.ScmTestDispatcher; 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 ScmTestDispatcher dispatcher = new ScmTestDispatcher(); @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 7aeabe6bec..1c876755a6 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.spi.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.ScmTestDispatcher; 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 ScmTestDispatcher dispatcher = new ScmTestDispatcher(); 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 8e84f9d1bb..6806dd248e 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.spi.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.ScmTestDispatcher; 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 ScmTestDispatcher dispatcher = new ScmTestDispatcher(); 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 3613069d63..be5374472f 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.spi.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.ScmTestDispatcher; 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 ScmTestDispatcher dispatcher = new ScmTestDispatcher(); 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 a2e63dd50e..8ad4522b4d 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.spi.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.ScmTestDispatcher; 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 ScmTestDispatcher dispatcher = new ScmTestDispatcher(); @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 d60a32f5dc..e458af6a11 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.spi.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.ScmTestDispatcher; 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 ScmTestDispatcher dispatcher = new ScmTestDispatcher(); @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 fcd34334b2..206f7713a3 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.spi.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.ScmTestDispatcher; 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 ScmTestDispatcher dispatcher = new ScmTestDispatcher(); @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 17ea3248c2..3dd30e21c0 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.spi.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.ScmTestDispatcher; 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 ScmTestDispatcher dispatcher = new ScmTestDispatcher(); @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 a25aabaca1..4dd694c092 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.spi.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.ScmTestDispatcher; 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 ScmTestDispatcher dispatcher = new ScmTestDispatcher(); 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 e97f6c726f..29616db703 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.spi.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.ScmTestDispatcher; 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 ScmTestDispatcher dispatcher = new ScmTestDispatcher(); 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 0391e43c81..da0c862168 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.spi.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.ScmTestDispatcher; 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 ScmTestDispatcher dispatcher = new ScmTestDispatcher(); @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 e055bbf3b5..6426007556 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.spi.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.ScmTestDispatcher; 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 ScmTestDispatcher dispatcher = new ScmTestDispatcher(); 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 From 9c4d49b39f4a86cc8e44cec80e05c5b5e970adc3 Mon Sep 17 00:00:00 2001 From: Rene Pfeuffer Date: Fri, 29 Nov 2019 11:41:04 +0100 Subject: [PATCH 13/53] Add script to change versions in package.json with lerna --- package.json | 3 ++- scm-ui/ui-scripts/bin/ui-scripts.js | 2 +- scm-ui/ui-scripts/src/commands/publish.js | 2 +- scm-ui/ui-scripts/src/commands/version.js | 12 ++++++++++++ 4 files changed, 16 insertions(+), 3 deletions(-) create mode 100644 scm-ui/ui-scripts/src/commands/version.js diff --git a/package.json b/package.json index b2f14826c5..8034538536 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,8 @@ "test": "lerna run --scope '@scm-manager/ui-*' test", "typecheck": "lerna run --scope '@scm-manager/ui-*' typecheck", "serve": "webpack-dev-server --mode=development --config=scm-ui/ui-scripts/src/webpack.config.js", - "deploy": "ui-scripts publish" + "deploy": "ui-scripts publish", + "version": "ui-scripts version" }, "devDependencies": { "babel-plugin-reflow": "^0.2.7", diff --git a/scm-ui/ui-scripts/bin/ui-scripts.js b/scm-ui/ui-scripts/bin/ui-scripts.js index 41549ac27a..28f5ef3315 100755 --- a/scm-ui/ui-scripts/bin/ui-scripts.js +++ b/scm-ui/ui-scripts/bin/ui-scripts.js @@ -1,7 +1,7 @@ #!/usr/bin/env node const { spawnSync } = require("child_process"); -const commands = ["plugin", "plugin-watch", "publish"]; +const commands = ["plugin", "plugin-watch", "publish", "version"]; const args = process.argv.slice(2); diff --git a/scm-ui/ui-scripts/src/commands/publish.js b/scm-ui/ui-scripts/src/commands/publish.js index d3cdc60724..2e5ad90440 100644 --- a/scm-ui/ui-scripts/src/commands/publish.js +++ b/scm-ui/ui-scripts/src/commands/publish.js @@ -4,7 +4,7 @@ const versions = require("../versions"); const args = process.argv.slice(2); if (args.length < 1) { - console.log("usage ui-scripts publish version"); + console.log("usage ui-scripts publish "); process.exit(1); } diff --git a/scm-ui/ui-scripts/src/commands/version.js b/scm-ui/ui-scripts/src/commands/version.js new file mode 100644 index 0000000000..c1feb85318 --- /dev/null +++ b/scm-ui/ui-scripts/src/commands/version.js @@ -0,0 +1,12 @@ +const lerna = require("../lerna"); + +const args = process.argv.slice(2); + +if (args.length < 1) { + console.log("usage ui-scripts version "); + process.exit(1); +} + +const version = args[0]; + +lerna.version(version); From 14786d7a1ad9a6957cf76818b0832af385cfd169 Mon Sep 17 00:00:00 2001 From: Eduard Heimbuch Date: Fri, 29 Nov 2019 15:57:36 +0100 Subject: [PATCH 14/53] create sortable table --- scm-ui/ui-components/src/table/Column.tsx | 12 ++++ .../ui-components/src/table/Table.stories.tsx | 19 ++++++ scm-ui/ui-components/src/table/Table.tsx | 64 +++++++++++++++++++ scm-ui/ui-components/src/table/TextColumn.tsx | 26 ++++++++ scm-ui/ui-components/src/table/types.ts | 9 +++ 5 files changed, 130 insertions(+) create mode 100644 scm-ui/ui-components/src/table/Column.tsx create mode 100644 scm-ui/ui-components/src/table/Table.stories.tsx create mode 100644 scm-ui/ui-components/src/table/Table.tsx create mode 100644 scm-ui/ui-components/src/table/TextColumn.tsx create mode 100644 scm-ui/ui-components/src/table/types.ts diff --git a/scm-ui/ui-components/src/table/Column.tsx b/scm-ui/ui-components/src/table/Column.tsx new file mode 100644 index 0000000000..e1e7a33156 --- /dev/null +++ b/scm-ui/ui-components/src/table/Column.tsx @@ -0,0 +1,12 @@ +import React, { FC, ReactNode } from "react"; +import { ColumnProps } from "./types"; + +type Props = ColumnProps & { + children: (row: any) => ReactNode; +}; + +const Column: FC = ({ row, children }) => { + return <>{children(row)}; +}; + +export default Column; diff --git a/scm-ui/ui-components/src/table/Table.stories.tsx b/scm-ui/ui-components/src/table/Table.stories.tsx new file mode 100644 index 0000000000..dafc60f5b6 --- /dev/null +++ b/scm-ui/ui-components/src/table/Table.stories.tsx @@ -0,0 +1,19 @@ +import React from "react"; +import { storiesOf } from "@storybook/react"; +import Table from "./Table"; +import Column from "./Column"; +import TextColumn from "./TextColumn"; + +storiesOf("Table|Table", module) + .add("Default", () => ( + + {(row: any) =>

{row.first}

}
+ {(row: any) =>

{row.second}

}
+
+ )) + .add("TextColumn", () => ( + + + +
+ )); diff --git a/scm-ui/ui-components/src/table/Table.tsx b/scm-ui/ui-components/src/table/Table.tsx new file mode 100644 index 0000000000..766ebf9317 --- /dev/null +++ b/scm-ui/ui-components/src/table/Table.tsx @@ -0,0 +1,64 @@ +import React, { FC, useState } from "react"; + +type SortableTableProps = { + data: any[]; +}; + +// @ts-ignore +const Table: FC = ({ data, children }) => { + const [tableData, setTableData] = useState(data); + const [ascending, setAscending] = useState(true); + const [lastSortBy, setlastSortBy] = useState(0); + + // @ts-ignore + const sortFunctions = React.Children.map(children, child => child.props.createComparator(child.props)); + + const mapDataToColumns = (row: any) => { + return ( + + {React.Children.map(children, child => { + // @ts-ignore + return {React.cloneElement(child, { ...child.props, row })}; + })} + + ); + }; + + const sortDescending = (sortAscending: (a: any, b: any) => number) => { + return (a: any, b: any) => { + return sortAscending(a, b) * -1; + }; + }; + + const tableSort = (index: number) => { + const sortableData = [...tableData]; + let sortOrder = ascending; + if (lastSortBy !== index) { + setAscending(true); + sortOrder = true; + } + const sortFunction = sortOrder ? sortFunctions[index] : sortDescending(sortFunctions[index]); + sortableData.sort(sortFunction); + setTableData(sortableData); + setAscending(!sortOrder); + setlastSortBy(index); + }; + + return ( + tableData.length > 0 && ( + + + + {React.Children.map(children, (child, index) => ( + // @ts-ignore + + ))} + + + {tableData.map(mapDataToColumns)} +
tableSort(index)}>{child.props.header}
+ ) + ); +}; + +export default Table; diff --git a/scm-ui/ui-components/src/table/TextColumn.tsx b/scm-ui/ui-components/src/table/TextColumn.tsx new file mode 100644 index 0000000000..eeca01eea7 --- /dev/null +++ b/scm-ui/ui-components/src/table/TextColumn.tsx @@ -0,0 +1,26 @@ +import React, { FC } from "react"; +import {ColumnProps} from "./types"; + +type Props = ColumnProps & { + dataKey: string; +}; + +const TextColumn: FC = ({ row, dataKey }) => { + return row[dataKey]; +}; + +TextColumn.defaultProps = { + createComparator: (props: Props) => { + return (a: any, b: any) => { + if (a[props.dataKey] < b[props.dataKey]) { + return -1; + } else if (a[props.dataKey] > b[props.dataKey]) { + return 1; + } else { + return 0; + } + }; + } +}; + +export default TextColumn; diff --git a/scm-ui/ui-components/src/table/types.ts b/scm-ui/ui-components/src/table/types.ts new file mode 100644 index 0000000000..b05a402711 --- /dev/null +++ b/scm-ui/ui-components/src/table/types.ts @@ -0,0 +1,9 @@ +import { ReactNode } from "react"; + +export type Comparator = (a: any, b: any) => number; + +export type ColumnProps = { + header: ReactNode; + row?: any; + createComparator?: (props: any) => Comparator; +}; From dc169ecff9c937d53b14a61370bddf1195fbe285 Mon Sep 17 00:00:00 2001 From: Eduard Heimbuch Date: Fri, 29 Nov 2019 17:13:39 +0100 Subject: [PATCH 15/53] style table --- scm-ui/ui-components/src/table/Table.tsx | 39 ++++++++++++++++--- scm-ui/ui-components/src/table/TextColumn.tsx | 7 ++-- scm-ui/ui-components/src/table/types.ts | 6 +++ 3 files changed, 44 insertions(+), 8 deletions(-) diff --git a/scm-ui/ui-components/src/table/Table.tsx b/scm-ui/ui-components/src/table/Table.tsx index 766ebf9317..847fe05d05 100644 --- a/scm-ui/ui-components/src/table/Table.tsx +++ b/scm-ui/ui-components/src/table/Table.tsx @@ -1,4 +1,15 @@ import React, { FC, useState } from "react"; +import styled from "styled-components"; +import { SortTypes } from "./types"; +import Icon from "../Icon"; + +const StyledTable = styled.table.attrs(() => ({ + className: "table content is-hoverable" +}))``; + +const IconWithMarginLeft = styled(Icon)` + margin-left: 0.25em; +`; type SortableTableProps = { data: any[]; @@ -7,11 +18,14 @@ type SortableTableProps = { // @ts-ignore const Table: FC = ({ data, children }) => { const [tableData, setTableData] = useState(data); - const [ascending, setAscending] = useState(true); + const [ascending, setAscending] = useState(false); const [lastSortBy, setlastSortBy] = useState(0); // @ts-ignore - const sortFunctions = React.Children.map(children, child => child.props.createComparator(child.props)); + const sortFunctions = React.Children.map(children, child => + // @ts-ignore + child.props.createComparator ? child.props.createComparator(child.props) : undefined + ); const mapDataToColumns = (row: any) => { return ( @@ -46,19 +60,34 @@ const Table: FC = ({ data, children }) => { return ( tableData.length > 0 && ( - + {React.Children.map(children, (child, index) => ( // @ts-ignore - + ))} {tableData.map(mapDataToColumns)} -
tableSort(index)}>{child.props.header} tableSort(index) : undefined} + > + {child.props.header} + + {child.props.createComparator && renderSortIcon(child.props.sortType, ascending)} +
+ ) ); }; +const renderSortIcon = (contentType: string, ascending: boolean) => { + if (contentType === SortTypes.Text) { + return ; + } else { + return ; + } +}; + export default Table; diff --git a/scm-ui/ui-components/src/table/TextColumn.tsx b/scm-ui/ui-components/src/table/TextColumn.tsx index eeca01eea7..0ccc69be81 100644 --- a/scm-ui/ui-components/src/table/TextColumn.tsx +++ b/scm-ui/ui-components/src/table/TextColumn.tsx @@ -1,5 +1,5 @@ -import React, { FC } from "react"; -import {ColumnProps} from "./types"; +import React, {FC} from "react"; +import {ColumnProps, SortTypes} from "./types"; type Props = ColumnProps & { dataKey: string; @@ -20,7 +20,8 @@ TextColumn.defaultProps = { return 0; } }; - } + }, + sortType: SortTypes.Text }; export default TextColumn; diff --git a/scm-ui/ui-components/src/table/types.ts b/scm-ui/ui-components/src/table/types.ts index b05a402711..683566da6d 100644 --- a/scm-ui/ui-components/src/table/types.ts +++ b/scm-ui/ui-components/src/table/types.ts @@ -6,4 +6,10 @@ export type ColumnProps = { header: ReactNode; row?: any; createComparator?: (props: any) => Comparator; + sortType: SortTypes; }; + +export enum SortTypes { + Text = "text", + Other = "other" +} From fc8677617b13ebd7c0e20a3b93ce2cbf6cefebe6 Mon Sep 17 00:00:00 2001 From: Rene Pfeuffer Date: Mon, 2 Dec 2019 08:49:31 +0100 Subject: [PATCH 16/53] Disable StopThreadsCleanUp When this is enabled, we get timeouts on some machines (seems to depend on the number of cores) when the server is stopped which leads to an error when the integration tests are run (the server cannot be stopped fast enough when the integration tests are finished in webapp and shall be started for scm-it). --- .../scm/lifecycle/classloading/ClassLoaderLifeCycle.java | 4 ++++ 1 file changed, 4 insertions(+) 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 24d1c239b4..de2480f563 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 @@ -6,6 +6,7 @@ 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.MBeanCleanUp; +import se.jiderhamn.classloader.leak.prevention.cleanup.StopThreadsCleanUp; import se.jiderhamn.classloader.leak.prevention.preinit.SunAwtAppContextInitiator; import sonia.scm.lifecycle.LifeCycle; import sonia.scm.plugin.ChildFirstPluginClassLoader; @@ -43,6 +44,9 @@ public final class ClassLoaderLifeCycle implements LifeCycle { 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); } From 1c3d3057354a6e5a0dffb8021e41a3f1ee382b3d Mon Sep 17 00:00:00 2001 From: Rene Pfeuffer Date: Mon, 2 Dec 2019 11:56:08 +0100 Subject: [PATCH 17/53] Update to new legman version 1.6.0 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 910287abf0..7a8e16b869 100644 --- a/pom.xml +++ b/pom.xml @@ -839,7 +839,7 @@ 2.3.0 - 1.5.1 + 1.6.0 9.4.22.v20191022 From 2d08ba4adc73f00714cdadcb23357c05ad740c39 Mon Sep 17 00:00:00 2001 From: Rene Pfeuffer Date: Mon, 2 Dec 2019 12:52:33 +0100 Subject: [PATCH 18/53] Release version 2.0.0-rc1 --- lerna.json | 2 +- pom.xml | 2 +- scm-annotation-processor/pom.xml | 6 ++--- scm-annotations/pom.xml | 4 ++-- scm-core/pom.xml | 8 +++---- scm-dao-xml/pom.xml | 8 +++---- scm-it/pom.xml | 20 ++++++++--------- scm-plugins/pom.xml | 10 ++++----- scm-plugins/scm-git-plugin/package.json | 4 ++-- scm-plugins/scm-git-plugin/pom.xml | 2 +- scm-plugins/scm-hg-plugin/package.json | 4 ++-- scm-plugins/scm-hg-plugin/pom.xml | 2 +- scm-plugins/scm-legacy-plugin/package.json | 4 ++-- scm-plugins/scm-legacy-plugin/pom.xml | 4 ++-- scm-plugins/scm-svn-plugin/package.json | 4 ++-- scm-plugins/scm-svn-plugin/pom.xml | 2 +- scm-server/pom.xml | 4 ++-- scm-test/pom.xml | 6 ++--- scm-ui/babel-preset/package.json | 2 +- scm-ui/eslint-config/package.json | 2 +- scm-ui/jest-preset/package.json | 2 +- scm-ui/pom.xml | 4 ++-- scm-ui/prettier-config/package.json | 2 +- scm-ui/tsconfig/package.json | 2 +- scm-ui/ui-components/package.json | 8 +++---- scm-ui/ui-extensions/package.json | 2 +- scm-ui/ui-plugins/package.json | 22 +++++++++--------- scm-ui/ui-polyfill/package.json | 2 +- scm-ui/ui-scripts/package.json | 2 +- scm-ui/ui-styles/package.json | 2 +- scm-ui/ui-tests/package.json | 2 +- scm-ui/ui-types/package.json | 2 +- scm-ui/ui-webapp/package.json | 8 +++---- scm-webapp/pom.xml | 26 +++++++++++----------- 34 files changed, 93 insertions(+), 93 deletions(-) diff --git a/lerna.json b/lerna.json index 405d16a397..e7765ac966 100644 --- a/lerna.json +++ b/lerna.json @@ -5,5 +5,5 @@ ], "npmClient": "yarn", "useWorkspaces": true, - "version": "2.0.0-SNAPSHOT" + "version": "2.0.0-rc1" } diff --git a/pom.xml b/pom.xml index 7a8e16b869..07c7602fdf 100644 --- a/pom.xml +++ b/pom.xml @@ -7,7 +7,7 @@ sonia.scm scm pom - 2.0.0-SNAPSHOT + 2.0.0-rc1 The easiest way to share your Git, Mercurial and Subversion repositories over http. diff --git a/scm-annotation-processor/pom.xml b/scm-annotation-processor/pom.xml index fc4419ba41..843b635a17 100644 --- a/scm-annotation-processor/pom.xml +++ b/scm-annotation-processor/pom.xml @@ -6,12 +6,12 @@ sonia.scm scm - 2.0.0-SNAPSHOT + 2.0.0-rc1 sonia.scm scm-annotation-processor - 2.0.0-SNAPSHOT + 2.0.0-rc1 scm-annotation-processor @@ -21,7 +21,7 @@ sonia.scm scm-annotations - 2.0.0-SNAPSHOT + 2.0.0-rc1 diff --git a/scm-annotations/pom.xml b/scm-annotations/pom.xml index c5a892af95..86981b1d97 100644 --- a/scm-annotations/pom.xml +++ b/scm-annotations/pom.xml @@ -6,11 +6,11 @@ sonia.scm scm - 2.0.0-SNAPSHOT + 2.0.0-rc1 scm-annotations - 2.0.0-SNAPSHOT + 2.0.0-rc1 scm-annotations diff --git a/scm-core/pom.xml b/scm-core/pom.xml index 8437b256b2..a8addc9779 100644 --- a/scm-core/pom.xml +++ b/scm-core/pom.xml @@ -6,12 +6,12 @@ scm sonia.scm - 2.0.0-SNAPSHOT + 2.0.0-rc1 sonia.scm scm-core - 2.0.0-SNAPSHOT + 2.0.0-rc1 scm-core @@ -30,7 +30,7 @@ sonia.scm scm-annotations - 2.0.0-SNAPSHOT + 2.0.0-rc1 @@ -192,7 +192,7 @@ sonia.scm scm-annotation-processor - 2.0.0-SNAPSHOT + 2.0.0-rc1 provided diff --git a/scm-dao-xml/pom.xml b/scm-dao-xml/pom.xml index 5424d38f26..de8323e323 100644 --- a/scm-dao-xml/pom.xml +++ b/scm-dao-xml/pom.xml @@ -6,12 +6,12 @@ sonia.scm scm - 2.0.0-SNAPSHOT + 2.0.0-rc1 sonia.scm scm-dao-xml - 2.0.0-SNAPSHOT + 2.0.0-rc1 scm-dao-xml @@ -26,7 +26,7 @@ sonia.scm scm-core - 2.0.0-SNAPSHOT + 2.0.0-rc1 @@ -34,7 +34,7 @@ sonia.scm scm-test - 2.0.0-SNAPSHOT + 2.0.0-rc1 test diff --git a/scm-it/pom.xml b/scm-it/pom.xml index c38c73ce3e..30503d974f 100644 --- a/scm-it/pom.xml +++ b/scm-it/pom.xml @@ -6,40 +6,40 @@ sonia.scm scm - 2.0.0-SNAPSHOT + 2.0.0-rc1 sonia.scm scm-it war - 2.0.0-SNAPSHOT + 2.0.0-rc1 scm-it sonia.scm scm-core - 2.0.0-SNAPSHOT + 2.0.0-rc1 sonia.scm scm-test - 2.0.0-SNAPSHOT + 2.0.0-rc1 sonia.scm.plugins scm-git-plugin - 2.0.0-SNAPSHOT + 2.0.0-rc1 test sonia.scm.plugins scm-git-plugin - 2.0.0-SNAPSHOT + 2.0.0-rc1 tests test @@ -47,14 +47,14 @@ sonia.scm.plugins scm-hg-plugin - 2.0.0-SNAPSHOT + 2.0.0-rc1 test sonia.scm.plugins scm-hg-plugin - 2.0.0-SNAPSHOT + 2.0.0-rc1 tests test @@ -62,14 +62,14 @@ sonia.scm.plugins scm-svn-plugin - 2.0.0-SNAPSHOT + 2.0.0-rc1 test sonia.scm.plugins scm-svn-plugin - 2.0.0-SNAPSHOT + 2.0.0-rc1 tests test diff --git a/scm-plugins/pom.xml b/scm-plugins/pom.xml index 1611633449..710054ab58 100644 --- a/scm-plugins/pom.xml +++ b/scm-plugins/pom.xml @@ -6,13 +6,13 @@ sonia.scm scm - 2.0.0-SNAPSHOT + 2.0.0-rc1 sonia.scm.plugins scm-plugins pom - 2.0.0-SNAPSHOT + 2.0.0-rc1 scm-plugins @@ -34,7 +34,7 @@ sonia.scm scm-core - 2.0.0-SNAPSHOT + 2.0.0-rc1 provided @@ -43,7 +43,7 @@ sonia.scm scm-annotation-processor - 2.0.0-SNAPSHOT + 2.0.0-rc1 provided @@ -66,7 +66,7 @@ sonia.scm scm-test - 2.0.0-SNAPSHOT + 2.0.0-rc1 test diff --git a/scm-plugins/scm-git-plugin/package.json b/scm-plugins/scm-git-plugin/package.json index 8ca5d633e4..5e30664d48 100644 --- a/scm-plugins/scm-git-plugin/package.json +++ b/scm-plugins/scm-git-plugin/package.json @@ -1,7 +1,7 @@ { "name": "@scm-manager/scm-git-plugin", "private": true, - "version": "2.0.0-SNAPSHOT", + "version": "2.0.0-rc1", "license": "BSD-3-Clause", "main": "./src/main/js/index.ts", "scripts": { @@ -20,6 +20,6 @@ }, "prettier": "@scm-manager/prettier-config", "dependencies": { - "@scm-manager/ui-plugins": "^2.0.0-SNAPSHOT" + "@scm-manager/ui-plugins": "^2.0.0-rc1" } } diff --git a/scm-plugins/scm-git-plugin/pom.xml b/scm-plugins/scm-git-plugin/pom.xml index 0cace5e94d..fecd88fd26 100644 --- a/scm-plugins/scm-git-plugin/pom.xml +++ b/scm-plugins/scm-git-plugin/pom.xml @@ -6,7 +6,7 @@ scm-plugins sonia.scm.plugins - 2.0.0-SNAPSHOT + 2.0.0-rc1 scm-git-plugin diff --git a/scm-plugins/scm-hg-plugin/package.json b/scm-plugins/scm-hg-plugin/package.json index 8baaa729cb..2d0e91a436 100644 --- a/scm-plugins/scm-hg-plugin/package.json +++ b/scm-plugins/scm-hg-plugin/package.json @@ -1,7 +1,7 @@ { "name": "@scm-manager/scm-hg-plugin", "private": true, - "version": "2.0.0-SNAPSHOT", + "version": "2.0.0-rc1", "license": "BSD-3-Clause", "main": "./src/main/js/index.ts", "scripts": { @@ -19,6 +19,6 @@ }, "prettier": "@scm-manager/prettier-config", "dependencies": { - "@scm-manager/ui-plugins": "^2.0.0-SNAPSHOT" + "@scm-manager/ui-plugins": "^2.0.0-rc1" } } diff --git a/scm-plugins/scm-hg-plugin/pom.xml b/scm-plugins/scm-hg-plugin/pom.xml index e57652bf0f..725f649607 100644 --- a/scm-plugins/scm-hg-plugin/pom.xml +++ b/scm-plugins/scm-hg-plugin/pom.xml @@ -6,7 +6,7 @@ sonia.scm.plugins scm-plugins - 2.0.0-SNAPSHOT + 2.0.0-rc1 scm-hg-plugin diff --git a/scm-plugins/scm-legacy-plugin/package.json b/scm-plugins/scm-legacy-plugin/package.json index 2e8ed49862..6bf028aa0f 100644 --- a/scm-plugins/scm-legacy-plugin/package.json +++ b/scm-plugins/scm-legacy-plugin/package.json @@ -1,7 +1,7 @@ { "name": "@scm-manager/scm-legacy-plugin", "private": true, - "version": "2.0.0-SNAPSHOT", + "version": "2.0.0-rc1", "license": "BSD-3-Clause", "main": "./src/main/js/index.tsx", "scripts": { @@ -19,6 +19,6 @@ }, "prettier": "@scm-manager/prettier-config", "dependencies": { - "@scm-manager/ui-plugins": "^2.0.0-SNAPSHOT" + "@scm-manager/ui-plugins": "^2.0.0-rc1" } } diff --git a/scm-plugins/scm-legacy-plugin/pom.xml b/scm-plugins/scm-legacy-plugin/pom.xml index 1a12234014..1b35c958e7 100644 --- a/scm-plugins/scm-legacy-plugin/pom.xml +++ b/scm-plugins/scm-legacy-plugin/pom.xml @@ -4,12 +4,12 @@ sonia.scm.plugins scm-plugins - 2.0.0-SNAPSHOT + 2.0.0-rc1 scm-legacy-plugin Support migrated repository urls and v1 passwords - 2.0.0-SNAPSHOT + 2.0.0-rc1 smp diff --git a/scm-plugins/scm-svn-plugin/package.json b/scm-plugins/scm-svn-plugin/package.json index c7b0c125b3..f2d28f5a5e 100644 --- a/scm-plugins/scm-svn-plugin/package.json +++ b/scm-plugins/scm-svn-plugin/package.json @@ -1,7 +1,7 @@ { "name": "@scm-manager/scm-svn-plugin", "private": true, - "version": "2.0.0-SNAPSHOT", + "version": "2.0.0-rc1", "license": "BSD-3-Clause", "main": "./src/main/js/index.ts", "scripts": { @@ -19,6 +19,6 @@ }, "prettier": "@scm-manager/prettier-config", "dependencies": { - "@scm-manager/ui-plugins": "^2.0.0-SNAPSHOT" + "@scm-manager/ui-plugins": "^2.0.0-rc1" } } diff --git a/scm-plugins/scm-svn-plugin/pom.xml b/scm-plugins/scm-svn-plugin/pom.xml index 8ed7a7bef4..64b461a8bd 100644 --- a/scm-plugins/scm-svn-plugin/pom.xml +++ b/scm-plugins/scm-svn-plugin/pom.xml @@ -6,7 +6,7 @@ scm-plugins sonia.scm.plugins - 2.0.0-SNAPSHOT + 2.0.0-rc1 scm-svn-plugin diff --git a/scm-server/pom.xml b/scm-server/pom.xml index 74f6466821..e982fe66ce 100644 --- a/scm-server/pom.xml +++ b/scm-server/pom.xml @@ -6,12 +6,12 @@ scm sonia.scm - 2.0.0-SNAPSHOT + 2.0.0-rc1 sonia.scm scm-server - 2.0.0-SNAPSHOT + 2.0.0-rc1 scm-server jar diff --git a/scm-test/pom.xml b/scm-test/pom.xml index bddbcf2c85..d92fc2d7d5 100644 --- a/scm-test/pom.xml +++ b/scm-test/pom.xml @@ -6,12 +6,12 @@ scm sonia.scm - 2.0.0-SNAPSHOT + 2.0.0-rc1 sonia.scm scm-test - 2.0.0-SNAPSHOT + 2.0.0-rc1 scm-test @@ -25,7 +25,7 @@ sonia.scm scm-core - 2.0.0-SNAPSHOT + 2.0.0-rc1 diff --git a/scm-ui/babel-preset/package.json b/scm-ui/babel-preset/package.json index 4751670f65..39f8d0882b 100644 --- a/scm-ui/babel-preset/package.json +++ b/scm-ui/babel-preset/package.json @@ -1,6 +1,6 @@ { "name": "@scm-manager/babel-preset", - "version": "2.0.0-SNAPSHOT", + "version": "2.0.0-rc1", "license": "BSD-3-Clause", "description": "Babel configuration for scm-manager and its plugins", "main": "index.js", diff --git a/scm-ui/eslint-config/package.json b/scm-ui/eslint-config/package.json index b819ad5bd5..e946237d9d 100644 --- a/scm-ui/eslint-config/package.json +++ b/scm-ui/eslint-config/package.json @@ -1,6 +1,6 @@ { "name": "@scm-manager/eslint-config", - "version": "2.0.0-SNAPSHOT", + "version": "2.0.0-rc1", "description": "ESLint configuration for scm-manager and its plugins", "main": "index.js", "author": "Sebastian Sdorra ", diff --git a/scm-ui/jest-preset/package.json b/scm-ui/jest-preset/package.json index c9efe9fe38..e7ad08782e 100644 --- a/scm-ui/jest-preset/package.json +++ b/scm-ui/jest-preset/package.json @@ -1,6 +1,6 @@ { "name": "@scm-manager/jest-preset", - "version": "2.0.0-SNAPSHOT", + "version": "2.0.0-rc1", "description": "Jest presets for SCM-Manager and its plugins", "main": "src/index.js", "author": "Sebastian Sdorra ", diff --git a/scm-ui/pom.xml b/scm-ui/pom.xml index ffc727f10d..fdaec15328 100644 --- a/scm-ui/pom.xml +++ b/scm-ui/pom.xml @@ -7,13 +7,13 @@ sonia.scm scm - 2.0.0-SNAPSHOT + 2.0.0-rc1 sonia.scm scm-ui war - 2.0.0-SNAPSHOT + 2.0.0-rc1 scm-ui diff --git a/scm-ui/prettier-config/package.json b/scm-ui/prettier-config/package.json index 584081645c..8298a4c0f0 100644 --- a/scm-ui/prettier-config/package.json +++ b/scm-ui/prettier-config/package.json @@ -1,6 +1,6 @@ { "name": "@scm-manager/prettier-config", - "version": "2.0.0-SNAPSHOT", + "version": "2.0.0-rc1", "license": "BSD-3-Clause", "description": "Prettier configuration", "author": "Sebastian Sdorra ", diff --git a/scm-ui/tsconfig/package.json b/scm-ui/tsconfig/package.json index 4ee8613469..5843558a5e 100644 --- a/scm-ui/tsconfig/package.json +++ b/scm-ui/tsconfig/package.json @@ -1,6 +1,6 @@ { "name": "@scm-manager/tsconfig", - "version": "2.0.0-SNAPSHOT", + "version": "2.0.0-rc1", "license": "BSD-3-Clause", "description": "TypeScript configuration", "author": "Sebastian Sdorra ", diff --git a/scm-ui/ui-components/package.json b/scm-ui/ui-components/package.json index e7266aae6f..d7ab44b959 100644 --- a/scm-ui/ui-components/package.json +++ b/scm-ui/ui-components/package.json @@ -1,6 +1,6 @@ { "name": "@scm-manager/ui-components", - "version": "2.0.0-SNAPSHOT", + "version": "2.0.0-rc1", "description": "UI Components for SCM-Manager and its plugins", "main": "src/index.ts", "files": [ @@ -18,7 +18,7 @@ "update-storyshots": "jest --testPathPattern=\"storyshots.test.ts\" --collectCoverage=false -u" }, "devDependencies": { - "@scm-manager/ui-tests": "^2.0.0-SNAPSHOT", + "@scm-manager/ui-tests": "^2.0.0-rc1", "@storybook/addon-actions": "^5.2.3", "@storybook/addon-storyshots": "^5.2.3", "@storybook/react": "^5.2.3", @@ -45,8 +45,8 @@ "typescript": "^3.6.4" }, "dependencies": { - "@scm-manager/ui-extensions": "^2.0.0-SNAPSHOT", - "@scm-manager/ui-types": "^2.0.0-SNAPSHOT", + "@scm-manager/ui-extensions": "^2.0.0-rc1", + "@scm-manager/ui-types": "^2.0.0-rc1", "classnames": "^2.2.6", "date-fns": "^2.4.1", "query-string": "5", diff --git a/scm-ui/ui-extensions/package.json b/scm-ui/ui-extensions/package.json index 28e1d99905..242e60c9fb 100644 --- a/scm-ui/ui-extensions/package.json +++ b/scm-ui/ui-extensions/package.json @@ -1,6 +1,6 @@ { "name": "@scm-manager/ui-extensions", - "version": "2.0.0-SNAPSHOT", + "version": "2.0.0-rc1", "main": "src/index.ts", "license": "BSD-3-Clause", "private": false, diff --git a/scm-ui/ui-plugins/package.json b/scm-ui/ui-plugins/package.json index 2e1fdb119f..537217a098 100644 --- a/scm-ui/ui-plugins/package.json +++ b/scm-ui/ui-plugins/package.json @@ -1,18 +1,18 @@ { "name": "@scm-manager/ui-plugins", - "version": "2.0.0-SNAPSHOT", + "version": "2.0.0-rc1", "license": "BSD-3-Clause", "dependencies": { - "@scm-manager/babel-preset": "^2.0.0-SNAPSHOT", - "@scm-manager/eslint-config": "^2.0.0-SNAPSHOT", - "@scm-manager/jest-preset": "^2.0.0-SNAPSHOT", - "@scm-manager/prettier-config": "^2.0.0-SNAPSHOT", - "@scm-manager/tsconfig": "^2.0.0-SNAPSHOT", - "@scm-manager/ui-components": "^2.0.0-SNAPSHOT", - "@scm-manager/ui-extensions": "^2.0.0-SNAPSHOT", - "@scm-manager/ui-scripts": "^2.0.0-SNAPSHOT", - "@scm-manager/ui-tests": "^2.0.0-SNAPSHOT", - "@scm-manager/ui-types": "^2.0.0-SNAPSHOT", + "@scm-manager/babel-preset": "^2.0.0-rc1", + "@scm-manager/eslint-config": "^2.0.0-rc1", + "@scm-manager/jest-preset": "^2.0.0-rc1", + "@scm-manager/prettier-config": "^2.0.0-rc1", + "@scm-manager/tsconfig": "^2.0.0-rc1", + "@scm-manager/ui-components": "^2.0.0-rc1", + "@scm-manager/ui-extensions": "^2.0.0-rc1", + "@scm-manager/ui-scripts": "^2.0.0-rc1", + "@scm-manager/ui-tests": "^2.0.0-rc1", + "@scm-manager/ui-types": "^2.0.0-rc1", "@types/classnames": "^2.2.9", "@types/enzyme": "^3.10.3", "@types/fetch-mock": "^7.3.1", diff --git a/scm-ui/ui-polyfill/package.json b/scm-ui/ui-polyfill/package.json index 81922e37c8..3181ee078f 100644 --- a/scm-ui/ui-polyfill/package.json +++ b/scm-ui/ui-polyfill/package.json @@ -1,6 +1,6 @@ { "name": "@scm-manager/ui-polyfill", - "version": "2.0.0-SNAPSHOT", + "version": "2.0.0-rc1", "description": "Polyfills for SCM-Manager UI", "main": "src/index.js", "author": "Sebastian Sdorra ", diff --git a/scm-ui/ui-scripts/package.json b/scm-ui/ui-scripts/package.json index 7ef5d2819c..fd3d9a491e 100644 --- a/scm-ui/ui-scripts/package.json +++ b/scm-ui/ui-scripts/package.json @@ -1,6 +1,6 @@ { "name": "@scm-manager/ui-scripts", - "version": "2.0.0-SNAPSHOT", + "version": "2.0.0-rc1", "description": "Build scripts for SCM-Manager", "main": "src/index.js", "author": "Sebastian Sdorra ", diff --git a/scm-ui/ui-styles/package.json b/scm-ui/ui-styles/package.json index 16fdb5b307..4e0856a3ad 100644 --- a/scm-ui/ui-styles/package.json +++ b/scm-ui/ui-styles/package.json @@ -1,6 +1,6 @@ { "name": "@scm-manager/ui-styles", - "version": "2.0.0-SNAPSHOT", + "version": "2.0.0-rc1", "description": "Styles for SCM-Manager", "main": "src/scm.scss", "license": "BSD-3-Clause", diff --git a/scm-ui/ui-tests/package.json b/scm-ui/ui-tests/package.json index 45978d3836..cfb5b35628 100644 --- a/scm-ui/ui-tests/package.json +++ b/scm-ui/ui-tests/package.json @@ -1,6 +1,6 @@ { "name": "@scm-manager/ui-tests", - "version": "2.0.0-SNAPSHOT", + "version": "2.0.0-rc1", "description": "UI-Tests helpers", "author": "Sebastian Sdorra ", "license": "BSD-3-Clause", diff --git a/scm-ui/ui-types/package.json b/scm-ui/ui-types/package.json index fb971290a8..1c2a96d574 100644 --- a/scm-ui/ui-types/package.json +++ b/scm-ui/ui-types/package.json @@ -1,6 +1,6 @@ { "name": "@scm-manager/ui-types", - "version": "2.0.0-SNAPSHOT", + "version": "2.0.0-rc1", "description": "Flow types for SCM-Manager related Objects", "main": "src/index.ts", "files": [ diff --git a/scm-ui/ui-webapp/package.json b/scm-ui/ui-webapp/package.json index af33163aeb..322b09d07e 100644 --- a/scm-ui/ui-webapp/package.json +++ b/scm-ui/ui-webapp/package.json @@ -1,10 +1,10 @@ { "name": "@scm-manager/ui-webapp", - "version": "2.0.0-SNAPSHOT", + "version": "2.0.0-rc1", "private": true, "dependencies": { - "@scm-manager/ui-components": "^2.0.0-SNAPSHOT", - "@scm-manager/ui-extensions": "^2.0.0-SNAPSHOT", + "@scm-manager/ui-components": "^2.0.0-rc1", + "@scm-manager/ui-extensions": "^2.0.0-rc1", "classnames": "^2.2.5", "history": "^4.10.1", "i18next": "^17.3.0", @@ -30,7 +30,7 @@ "flow": "flow" }, "devDependencies": { - "@scm-manager/ui-tests": "^2.0.0-SNAPSHOT", + "@scm-manager/ui-tests": "^2.0.0-rc1", "@types/classnames": "^2.2.9", "@types/enzyme": "^3.10.3", "@types/fetch-mock": "^7.3.1", diff --git a/scm-webapp/pom.xml b/scm-webapp/pom.xml index 571c3b5370..9bc163d0a9 100644 --- a/scm-webapp/pom.xml +++ b/scm-webapp/pom.xml @@ -6,13 +6,13 @@ sonia.scm scm - 2.0.0-SNAPSHOT + 2.0.0-rc1 sonia.scm scm-webapp war - 2.0.0-SNAPSHOT + 2.0.0-rc1 scm-webapp @@ -22,7 +22,7 @@ sonia.scm scm-annotation-processor - 2.0.0-SNAPSHOT + 2.0.0-rc1 provided @@ -47,13 +47,13 @@ sonia.scm scm-core - 2.0.0-SNAPSHOT + 2.0.0-rc1 sonia.scm scm-dao-xml - 2.0.0-SNAPSHOT + 2.0.0-rc1 @@ -288,7 +288,7 @@ sonia.scm scm-test - 2.0.0-SNAPSHOT + 2.0.0-rc1 test @@ -344,7 +344,7 @@ sonia.scm.plugins scm-git-plugin - 2.0.0-SNAPSHOT + 2.0.0-rc1 tests test @@ -352,14 +352,14 @@ sonia.scm.plugins scm-git-plugin - 2.0.0-SNAPSHOT + 2.0.0-rc1 test sonia.scm.plugins scm-hg-plugin - 2.0.0-SNAPSHOT + 2.0.0-rc1 tests test @@ -367,14 +367,14 @@ sonia.scm.plugins scm-hg-plugin - 2.0.0-SNAPSHOT + 2.0.0-rc1 test sonia.scm.plugins scm-svn-plugin - 2.0.0-SNAPSHOT + 2.0.0-rc1 tests test @@ -382,7 +382,7 @@ sonia.scm.plugins scm-svn-plugin - 2.0.0-SNAPSHOT + 2.0.0-rc1 test @@ -606,7 +606,7 @@ sonia.scm scm-ui - 2.0.0-SNAPSHOT + 2.0.0-rc1 war From 426a616a513744783d6eabf2908771b01c17828f Mon Sep 17 00:00:00 2001 From: Rene Pfeuffer Date: Mon, 2 Dec 2019 12:53:19 +0100 Subject: [PATCH 19/53] Added tag 2.0.0-rc1 for changeset 93d7a07f2524 From 52057abef5008ef71fa6393a47c04b429383867a Mon Sep 17 00:00:00 2001 From: Eduard Heimbuch Date: Mon, 2 Dec 2019 13:58:56 +0100 Subject: [PATCH 20/53] refactor table --- scm-ui/ui-components/src/table/Column.tsx | 12 +++- scm-ui/ui-components/src/table/SortIcon.tsx | 19 ++++++ .../ui-components/src/table/Table.stories.tsx | 20 +++++- scm-ui/ui-components/src/table/Table.tsx | 62 +++++++++++-------- scm-ui/ui-components/src/table/TextColumn.tsx | 9 +-- scm-ui/ui-components/src/table/types.ts | 11 ++-- 6 files changed, 92 insertions(+), 41 deletions(-) create mode 100644 scm-ui/ui-components/src/table/SortIcon.tsx diff --git a/scm-ui/ui-components/src/table/Column.tsx b/scm-ui/ui-components/src/table/Column.tsx index e1e7a33156..7f95535c36 100644 --- a/scm-ui/ui-components/src/table/Column.tsx +++ b/scm-ui/ui-components/src/table/Column.tsx @@ -2,11 +2,17 @@ import React, { FC, ReactNode } from "react"; import { ColumnProps } from "./types"; type Props = ColumnProps & { - children: (row: any) => ReactNode; + children: (row: any, columnIndex: number) => ReactNode; }; -const Column: FC = ({ row, children }) => { - return <>{children(row)}; +const Column: FC = ({ row, columnIndex, children }) => { + if (row === undefined) { + throw new Error("missing row, use column only as child of Table"); + } + if (columnIndex === undefined) { + throw new Error("missing row, use column only as child of Table"); + } + return <>{children(row, columnIndex)}; }; export default Column; diff --git a/scm-ui/ui-components/src/table/SortIcon.tsx b/scm-ui/ui-components/src/table/SortIcon.tsx new file mode 100644 index 0000000000..6f823d3d95 --- /dev/null +++ b/scm-ui/ui-components/src/table/SortIcon.tsx @@ -0,0 +1,19 @@ +import React, { FC } from "react"; +import styled from "styled-components"; +import Icon from "../Icon"; + +type Props = { + name: string; + isHidden: boolean; +}; + +const IconWithMarginLeft = styled(Icon)` + visibility: ${(props: Props) => (props.isHidden ? "hidden" : "visible")}; + margin-left: 0.25em; +`; + +const SortIcon: FC = (props: Props) => { + return ; +}; + +export default SortIcon; diff --git a/scm-ui/ui-components/src/table/Table.stories.tsx b/scm-ui/ui-components/src/table/Table.stories.tsx index dafc60f5b6..84b80f001f 100644 --- a/scm-ui/ui-components/src/table/Table.stories.tsx +++ b/scm-ui/ui-components/src/table/Table.stories.tsx @@ -3,12 +3,28 @@ import { storiesOf } from "@storybook/react"; import Table from "./Table"; import Column from "./Column"; import TextColumn from "./TextColumn"; +import { ColumnProps } from "./types"; storiesOf("Table|Table", module) .add("Default", () => ( - +
{(row: any) =>

{row.first}

}
- {(row: any) =>

{row.second}

}
+ { + return (a: any, b: any) => { + if (a.second > b.second) { + return -1; + } else if (a.second < b.second) { + return 1; + } else { + return 0; + } + }; + }} + > + {(row: any) =>

{row.second}

} +
)) .add("TextColumn", () => ( diff --git a/scm-ui/ui-components/src/table/Table.tsx b/scm-ui/ui-components/src/table/Table.tsx index 847fe05d05..a36481005e 100644 --- a/scm-ui/ui-components/src/table/Table.tsx +++ b/scm-ui/ui-components/src/table/Table.tsx @@ -1,38 +1,44 @@ import React, { FC, useState } from "react"; import styled from "styled-components"; -import { SortTypes } from "./types"; -import Icon from "../Icon"; +import { Comparator } from "./types"; +import SortIcon from "./SortIcon"; const StyledTable = styled.table.attrs(() => ({ className: "table content is-hoverable" }))``; -const IconWithMarginLeft = styled(Icon)` - margin-left: 0.25em; -`; - -type SortableTableProps = { +type Props = { data: any[]; + sortable?: boolean; }; // @ts-ignore -const Table: FC = ({ data, children }) => { +const Table: FC = ({ data, sortable, children }) => { const [tableData, setTableData] = useState(data); const [ascending, setAscending] = useState(false); - const [lastSortBy, setlastSortBy] = useState(0); + const [lastSortBy, setlastSortBy] = useState(); + const [hoveredIndex, setHoveredIndex] = useState(); - // @ts-ignore - const sortFunctions = React.Children.map(children, child => - // @ts-ignore - child.props.createComparator ? child.props.createComparator(child.props) : undefined - ); + const isSortable = (child: any) => { + return sortable && child.props.createComparator; + }; + + const sortFunctions: Comparator | undefined[] = []; + React.Children.forEach(children, (child, index) => { + if (isSortable(child)) { + // @ts-ignore + sortFunctions.push(child.props.createComparator(child.props, index)); + } else { + sortFunctions.push(undefined); + } + }); const mapDataToColumns = (row: any) => { return ( - {React.Children.map(children, child => { + {React.Children.map(children, (child, columnIndex) => { // @ts-ignore - return {React.cloneElement(child, { ...child.props, row })}; + return {React.cloneElement(child, { ...child.props, columnIndex, row })}; })} ); @@ -51,6 +57,7 @@ const Table: FC = ({ data, children }) => { setAscending(true); sortOrder = true; } + // @ts-ignore const sortFunction = sortOrder ? sortFunctions[index] : sortDescending(sortFunctions[index]); sortableData.sort(sortFunction); setTableData(sortableData); @@ -58,20 +65,21 @@ const Table: FC = ({ data, children }) => { setlastSortBy(index); }; + // @ts-ignore return ( tableData.length > 0 && ( {React.Children.map(children, (child, index) => ( - // @ts-ignore tableSort(index) : undefined} + className={isSortable(child) && "has-cursor-pointer"} + onClick={isSortable(child) ? () => tableSort(index) : undefined} + onMouseEnter={() => setHoveredIndex(index)} + onMouseLeave={() => setHoveredIndex(undefined)} > {child.props.header} - - {child.props.createComparator && renderSortIcon(child.props.sortType, ascending)} + {isSortable(child) && renderSortIcon(child, ascending, index === lastSortBy || index === hoveredIndex)} ))} @@ -82,11 +90,15 @@ const Table: FC = ({ data, children }) => { ); }; -const renderSortIcon = (contentType: string, ascending: boolean) => { - if (contentType === SortTypes.Text) { - return ; +Table.defaultProps = { + sortable: true +}; + +const renderSortIcon = (child: any, ascending: boolean, showIcon: boolean) => { + if (child.props.ascendingIcon && child.props.descendingIcon) { + return ; } else { - return ; + return ; } }; diff --git a/scm-ui/ui-components/src/table/TextColumn.tsx b/scm-ui/ui-components/src/table/TextColumn.tsx index 0ccc69be81..c3184741da 100644 --- a/scm-ui/ui-components/src/table/TextColumn.tsx +++ b/scm-ui/ui-components/src/table/TextColumn.tsx @@ -1,5 +1,5 @@ -import React, {FC} from "react"; -import {ColumnProps, SortTypes} from "./types"; +import React, { FC } from "react"; +import { ColumnProps } from "./types"; type Props = ColumnProps & { dataKey: string; @@ -10,7 +10,7 @@ const TextColumn: FC = ({ row, dataKey }) => { }; TextColumn.defaultProps = { - createComparator: (props: Props) => { + createComparator: (props: Props, columnIndex) => { return (a: any, b: any) => { if (a[props.dataKey] < b[props.dataKey]) { return -1; @@ -21,7 +21,8 @@ TextColumn.defaultProps = { } }; }, - sortType: SortTypes.Text + ascendingIcon: "sort-alpha-down-alt", + descendingIcon: "sort-alpha-down" }; export default TextColumn; diff --git a/scm-ui/ui-components/src/table/types.ts b/scm-ui/ui-components/src/table/types.ts index 683566da6d..9af3e872cb 100644 --- a/scm-ui/ui-components/src/table/types.ts +++ b/scm-ui/ui-components/src/table/types.ts @@ -5,11 +5,8 @@ export type Comparator = (a: any, b: any) => number; export type ColumnProps = { header: ReactNode; row?: any; - createComparator?: (props: any) => Comparator; - sortType: SortTypes; + columnIndex?: number; + createComparator?: (props: any, columnIndex: number) => Comparator; + ascendingIcon?: string; + descendingIcon?: string; }; - -export enum SortTypes { - Text = "text", - Other = "other" -} From 75468483cfd4953b597aa393d8c69f8b8aa92893 Mon Sep 17 00:00:00 2001 From: Eduard Heimbuch Date: Mon, 2 Dec 2019 14:03:50 +0100 Subject: [PATCH 21/53] refactor --- scm-ui/ui-components/src/table/SortIcon.tsx | 6 +++--- scm-ui/ui-components/src/table/Table.tsx | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/scm-ui/ui-components/src/table/SortIcon.tsx b/scm-ui/ui-components/src/table/SortIcon.tsx index 6f823d3d95..0d553041c6 100644 --- a/scm-ui/ui-components/src/table/SortIcon.tsx +++ b/scm-ui/ui-components/src/table/SortIcon.tsx @@ -4,16 +4,16 @@ import Icon from "../Icon"; type Props = { name: string; - isHidden: boolean; + isVisible: boolean; }; const IconWithMarginLeft = styled(Icon)` - visibility: ${(props: Props) => (props.isHidden ? "hidden" : "visible")}; + visibility: ${(props: Props) => (props.isVisible ? "visible" : "hidden")}; margin-left: 0.25em; `; const SortIcon: FC = (props: Props) => { - return ; + return ; }; export default SortIcon; diff --git a/scm-ui/ui-components/src/table/Table.tsx b/scm-ui/ui-components/src/table/Table.tsx index a36481005e..d9e3c80124 100644 --- a/scm-ui/ui-components/src/table/Table.tsx +++ b/scm-ui/ui-components/src/table/Table.tsx @@ -17,7 +17,7 @@ const Table: FC = ({ data, sortable, children }) => { const [tableData, setTableData] = useState(data); const [ascending, setAscending] = useState(false); const [lastSortBy, setlastSortBy] = useState(); - const [hoveredIndex, setHoveredIndex] = useState(); + const [hoveredColumnIndex, setHoveredColumnIndex] = useState(); const isSortable = (child: any) => { return sortable && child.props.createComparator; @@ -75,11 +75,11 @@ const Table: FC = ({ data, sortable, children }) => { tableSort(index) : undefined} - onMouseEnter={() => setHoveredIndex(index)} - onMouseLeave={() => setHoveredIndex(undefined)} + onMouseEnter={() => setHoveredColumnIndex(index)} + onMouseLeave={() => setHoveredColumnIndex(undefined)} > {child.props.header} - {isSortable(child) && renderSortIcon(child, ascending, index === lastSortBy || index === hoveredIndex)} + {isSortable(child) && renderSortIcon(child, ascending, index === lastSortBy || index === hoveredColumnIndex)} ))} @@ -96,9 +96,9 @@ Table.defaultProps = { const renderSortIcon = (child: any, ascending: boolean, showIcon: boolean) => { if (child.props.ascendingIcon && child.props.descendingIcon) { - return ; + return ; } else { - return ; + return ; } }; From ef74a54b9136395173a8be188f653ae44e8871c2 Mon Sep 17 00:00:00 2001 From: Eduard Heimbuch Date: Mon, 2 Dec 2019 14:10:04 +0100 Subject: [PATCH 22/53] fix typescript error --- scm-ui/ui-components/src/table/Table.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/scm-ui/ui-components/src/table/Table.tsx b/scm-ui/ui-components/src/table/Table.tsx index d9e3c80124..98f9d9f038 100644 --- a/scm-ui/ui-components/src/table/Table.tsx +++ b/scm-ui/ui-components/src/table/Table.tsx @@ -66,6 +66,7 @@ const Table: FC = ({ data, sortable, children }) => { }; // @ts-ignore + const { header } = child.props; return ( tableData.length > 0 && ( @@ -78,8 +79,9 @@ const Table: FC = ({ data, sortable, children }) => { onMouseEnter={() => setHoveredColumnIndex(index)} onMouseLeave={() => setHoveredColumnIndex(undefined)} > - {child.props.header} - {isSortable(child) && renderSortIcon(child, ascending, index === lastSortBy || index === hoveredColumnIndex)} + {header} + {isSortable(child) && + renderSortIcon(child, ascending, index === lastSortBy || index === hoveredColumnIndex)} ))} From 516bbe6f26fb2e7c89f2ab4a880a0a5a6f5125d7 Mon Sep 17 00:00:00 2001 From: Rene Pfeuffer Date: Mon, 2 Dec 2019 15:15:34 +0100 Subject: [PATCH 23/53] Prepare for next development iteration --- lerna.json | 2 +- pom.xml | 2 +- scm-annotation-processor/pom.xml | 6 ++--- scm-annotations/pom.xml | 4 ++-- scm-core/pom.xml | 8 +++---- scm-dao-xml/pom.xml | 8 +++---- scm-it/pom.xml | 20 ++++++++--------- scm-plugins/pom.xml | 10 ++++----- scm-plugins/scm-git-plugin/package.json | 4 ++-- scm-plugins/scm-git-plugin/pom.xml | 2 +- scm-plugins/scm-hg-plugin/package.json | 4 ++-- scm-plugins/scm-hg-plugin/pom.xml | 2 +- scm-plugins/scm-legacy-plugin/package.json | 4 ++-- scm-plugins/scm-legacy-plugin/pom.xml | 4 ++-- scm-plugins/scm-svn-plugin/package.json | 4 ++-- scm-plugins/scm-svn-plugin/pom.xml | 2 +- scm-server/pom.xml | 4 ++-- scm-test/pom.xml | 6 ++--- scm-ui/babel-preset/package.json | 2 +- scm-ui/eslint-config/package.json | 2 +- scm-ui/jest-preset/package.json | 2 +- scm-ui/pom.xml | 4 ++-- scm-ui/prettier-config/package.json | 2 +- scm-ui/tsconfig/package.json | 2 +- scm-ui/ui-components/package.json | 8 +++---- scm-ui/ui-extensions/package.json | 2 +- scm-ui/ui-plugins/package.json | 22 +++++++++--------- scm-ui/ui-polyfill/package.json | 2 +- scm-ui/ui-scripts/package.json | 2 +- scm-ui/ui-styles/package.json | 2 +- scm-ui/ui-tests/package.json | 2 +- scm-ui/ui-types/package.json | 2 +- scm-ui/ui-webapp/package.json | 8 +++---- scm-webapp/pom.xml | 26 +++++++++++----------- 34 files changed, 93 insertions(+), 93 deletions(-) diff --git a/lerna.json b/lerna.json index e7765ac966..405d16a397 100644 --- a/lerna.json +++ b/lerna.json @@ -5,5 +5,5 @@ ], "npmClient": "yarn", "useWorkspaces": true, - "version": "2.0.0-rc1" + "version": "2.0.0-SNAPSHOT" } diff --git a/pom.xml b/pom.xml index 07c7602fdf..7a8e16b869 100644 --- a/pom.xml +++ b/pom.xml @@ -7,7 +7,7 @@ sonia.scm scm pom - 2.0.0-rc1 + 2.0.0-SNAPSHOT The easiest way to share your Git, Mercurial and Subversion repositories over http. diff --git a/scm-annotation-processor/pom.xml b/scm-annotation-processor/pom.xml index 843b635a17..fc4419ba41 100644 --- a/scm-annotation-processor/pom.xml +++ b/scm-annotation-processor/pom.xml @@ -6,12 +6,12 @@ sonia.scm scm - 2.0.0-rc1 + 2.0.0-SNAPSHOT sonia.scm scm-annotation-processor - 2.0.0-rc1 + 2.0.0-SNAPSHOT scm-annotation-processor @@ -21,7 +21,7 @@ sonia.scm scm-annotations - 2.0.0-rc1 + 2.0.0-SNAPSHOT diff --git a/scm-annotations/pom.xml b/scm-annotations/pom.xml index 86981b1d97..c5a892af95 100644 --- a/scm-annotations/pom.xml +++ b/scm-annotations/pom.xml @@ -6,11 +6,11 @@ sonia.scm scm - 2.0.0-rc1 + 2.0.0-SNAPSHOT scm-annotations - 2.0.0-rc1 + 2.0.0-SNAPSHOT scm-annotations diff --git a/scm-core/pom.xml b/scm-core/pom.xml index a8addc9779..8437b256b2 100644 --- a/scm-core/pom.xml +++ b/scm-core/pom.xml @@ -6,12 +6,12 @@ scm sonia.scm - 2.0.0-rc1 + 2.0.0-SNAPSHOT sonia.scm scm-core - 2.0.0-rc1 + 2.0.0-SNAPSHOT scm-core @@ -30,7 +30,7 @@ sonia.scm scm-annotations - 2.0.0-rc1 + 2.0.0-SNAPSHOT @@ -192,7 +192,7 @@ sonia.scm scm-annotation-processor - 2.0.0-rc1 + 2.0.0-SNAPSHOT provided diff --git a/scm-dao-xml/pom.xml b/scm-dao-xml/pom.xml index de8323e323..5424d38f26 100644 --- a/scm-dao-xml/pom.xml +++ b/scm-dao-xml/pom.xml @@ -6,12 +6,12 @@ sonia.scm scm - 2.0.0-rc1 + 2.0.0-SNAPSHOT sonia.scm scm-dao-xml - 2.0.0-rc1 + 2.0.0-SNAPSHOT scm-dao-xml @@ -26,7 +26,7 @@ sonia.scm scm-core - 2.0.0-rc1 + 2.0.0-SNAPSHOT @@ -34,7 +34,7 @@ sonia.scm scm-test - 2.0.0-rc1 + 2.0.0-SNAPSHOT test diff --git a/scm-it/pom.xml b/scm-it/pom.xml index 30503d974f..c38c73ce3e 100644 --- a/scm-it/pom.xml +++ b/scm-it/pom.xml @@ -6,40 +6,40 @@ sonia.scm scm - 2.0.0-rc1 + 2.0.0-SNAPSHOT sonia.scm scm-it war - 2.0.0-rc1 + 2.0.0-SNAPSHOT scm-it sonia.scm scm-core - 2.0.0-rc1 + 2.0.0-SNAPSHOT sonia.scm scm-test - 2.0.0-rc1 + 2.0.0-SNAPSHOT sonia.scm.plugins scm-git-plugin - 2.0.0-rc1 + 2.0.0-SNAPSHOT test sonia.scm.plugins scm-git-plugin - 2.0.0-rc1 + 2.0.0-SNAPSHOT tests test @@ -47,14 +47,14 @@ sonia.scm.plugins scm-hg-plugin - 2.0.0-rc1 + 2.0.0-SNAPSHOT test sonia.scm.plugins scm-hg-plugin - 2.0.0-rc1 + 2.0.0-SNAPSHOT tests test @@ -62,14 +62,14 @@ sonia.scm.plugins scm-svn-plugin - 2.0.0-rc1 + 2.0.0-SNAPSHOT test sonia.scm.plugins scm-svn-plugin - 2.0.0-rc1 + 2.0.0-SNAPSHOT tests test diff --git a/scm-plugins/pom.xml b/scm-plugins/pom.xml index 710054ab58..1611633449 100644 --- a/scm-plugins/pom.xml +++ b/scm-plugins/pom.xml @@ -6,13 +6,13 @@ sonia.scm scm - 2.0.0-rc1 + 2.0.0-SNAPSHOT sonia.scm.plugins scm-plugins pom - 2.0.0-rc1 + 2.0.0-SNAPSHOT scm-plugins @@ -34,7 +34,7 @@ sonia.scm scm-core - 2.0.0-rc1 + 2.0.0-SNAPSHOT provided @@ -43,7 +43,7 @@ sonia.scm scm-annotation-processor - 2.0.0-rc1 + 2.0.0-SNAPSHOT provided @@ -66,7 +66,7 @@ sonia.scm scm-test - 2.0.0-rc1 + 2.0.0-SNAPSHOT test diff --git a/scm-plugins/scm-git-plugin/package.json b/scm-plugins/scm-git-plugin/package.json index 5e30664d48..8ca5d633e4 100644 --- a/scm-plugins/scm-git-plugin/package.json +++ b/scm-plugins/scm-git-plugin/package.json @@ -1,7 +1,7 @@ { "name": "@scm-manager/scm-git-plugin", "private": true, - "version": "2.0.0-rc1", + "version": "2.0.0-SNAPSHOT", "license": "BSD-3-Clause", "main": "./src/main/js/index.ts", "scripts": { @@ -20,6 +20,6 @@ }, "prettier": "@scm-manager/prettier-config", "dependencies": { - "@scm-manager/ui-plugins": "^2.0.0-rc1" + "@scm-manager/ui-plugins": "^2.0.0-SNAPSHOT" } } diff --git a/scm-plugins/scm-git-plugin/pom.xml b/scm-plugins/scm-git-plugin/pom.xml index fecd88fd26..0cace5e94d 100644 --- a/scm-plugins/scm-git-plugin/pom.xml +++ b/scm-plugins/scm-git-plugin/pom.xml @@ -6,7 +6,7 @@ scm-plugins sonia.scm.plugins - 2.0.0-rc1 + 2.0.0-SNAPSHOT scm-git-plugin diff --git a/scm-plugins/scm-hg-plugin/package.json b/scm-plugins/scm-hg-plugin/package.json index 2d0e91a436..8baaa729cb 100644 --- a/scm-plugins/scm-hg-plugin/package.json +++ b/scm-plugins/scm-hg-plugin/package.json @@ -1,7 +1,7 @@ { "name": "@scm-manager/scm-hg-plugin", "private": true, - "version": "2.0.0-rc1", + "version": "2.0.0-SNAPSHOT", "license": "BSD-3-Clause", "main": "./src/main/js/index.ts", "scripts": { @@ -19,6 +19,6 @@ }, "prettier": "@scm-manager/prettier-config", "dependencies": { - "@scm-manager/ui-plugins": "^2.0.0-rc1" + "@scm-manager/ui-plugins": "^2.0.0-SNAPSHOT" } } diff --git a/scm-plugins/scm-hg-plugin/pom.xml b/scm-plugins/scm-hg-plugin/pom.xml index 725f649607..e57652bf0f 100644 --- a/scm-plugins/scm-hg-plugin/pom.xml +++ b/scm-plugins/scm-hg-plugin/pom.xml @@ -6,7 +6,7 @@ sonia.scm.plugins scm-plugins - 2.0.0-rc1 + 2.0.0-SNAPSHOT scm-hg-plugin diff --git a/scm-plugins/scm-legacy-plugin/package.json b/scm-plugins/scm-legacy-plugin/package.json index 6bf028aa0f..2e8ed49862 100644 --- a/scm-plugins/scm-legacy-plugin/package.json +++ b/scm-plugins/scm-legacy-plugin/package.json @@ -1,7 +1,7 @@ { "name": "@scm-manager/scm-legacy-plugin", "private": true, - "version": "2.0.0-rc1", + "version": "2.0.0-SNAPSHOT", "license": "BSD-3-Clause", "main": "./src/main/js/index.tsx", "scripts": { @@ -19,6 +19,6 @@ }, "prettier": "@scm-manager/prettier-config", "dependencies": { - "@scm-manager/ui-plugins": "^2.0.0-rc1" + "@scm-manager/ui-plugins": "^2.0.0-SNAPSHOT" } } diff --git a/scm-plugins/scm-legacy-plugin/pom.xml b/scm-plugins/scm-legacy-plugin/pom.xml index 1b35c958e7..1a12234014 100644 --- a/scm-plugins/scm-legacy-plugin/pom.xml +++ b/scm-plugins/scm-legacy-plugin/pom.xml @@ -4,12 +4,12 @@ sonia.scm.plugins scm-plugins - 2.0.0-rc1 + 2.0.0-SNAPSHOT scm-legacy-plugin Support migrated repository urls and v1 passwords - 2.0.0-rc1 + 2.0.0-SNAPSHOT smp diff --git a/scm-plugins/scm-svn-plugin/package.json b/scm-plugins/scm-svn-plugin/package.json index f2d28f5a5e..c7b0c125b3 100644 --- a/scm-plugins/scm-svn-plugin/package.json +++ b/scm-plugins/scm-svn-plugin/package.json @@ -1,7 +1,7 @@ { "name": "@scm-manager/scm-svn-plugin", "private": true, - "version": "2.0.0-rc1", + "version": "2.0.0-SNAPSHOT", "license": "BSD-3-Clause", "main": "./src/main/js/index.ts", "scripts": { @@ -19,6 +19,6 @@ }, "prettier": "@scm-manager/prettier-config", "dependencies": { - "@scm-manager/ui-plugins": "^2.0.0-rc1" + "@scm-manager/ui-plugins": "^2.0.0-SNAPSHOT" } } diff --git a/scm-plugins/scm-svn-plugin/pom.xml b/scm-plugins/scm-svn-plugin/pom.xml index 64b461a8bd..8ed7a7bef4 100644 --- a/scm-plugins/scm-svn-plugin/pom.xml +++ b/scm-plugins/scm-svn-plugin/pom.xml @@ -6,7 +6,7 @@ scm-plugins sonia.scm.plugins - 2.0.0-rc1 + 2.0.0-SNAPSHOT scm-svn-plugin diff --git a/scm-server/pom.xml b/scm-server/pom.xml index e982fe66ce..74f6466821 100644 --- a/scm-server/pom.xml +++ b/scm-server/pom.xml @@ -6,12 +6,12 @@ scm sonia.scm - 2.0.0-rc1 + 2.0.0-SNAPSHOT sonia.scm scm-server - 2.0.0-rc1 + 2.0.0-SNAPSHOT scm-server jar diff --git a/scm-test/pom.xml b/scm-test/pom.xml index d92fc2d7d5..bddbcf2c85 100644 --- a/scm-test/pom.xml +++ b/scm-test/pom.xml @@ -6,12 +6,12 @@ scm sonia.scm - 2.0.0-rc1 + 2.0.0-SNAPSHOT sonia.scm scm-test - 2.0.0-rc1 + 2.0.0-SNAPSHOT scm-test @@ -25,7 +25,7 @@ sonia.scm scm-core - 2.0.0-rc1 + 2.0.0-SNAPSHOT diff --git a/scm-ui/babel-preset/package.json b/scm-ui/babel-preset/package.json index 39f8d0882b..4751670f65 100644 --- a/scm-ui/babel-preset/package.json +++ b/scm-ui/babel-preset/package.json @@ -1,6 +1,6 @@ { "name": "@scm-manager/babel-preset", - "version": "2.0.0-rc1", + "version": "2.0.0-SNAPSHOT", "license": "BSD-3-Clause", "description": "Babel configuration for scm-manager and its plugins", "main": "index.js", diff --git a/scm-ui/eslint-config/package.json b/scm-ui/eslint-config/package.json index e946237d9d..b819ad5bd5 100644 --- a/scm-ui/eslint-config/package.json +++ b/scm-ui/eslint-config/package.json @@ -1,6 +1,6 @@ { "name": "@scm-manager/eslint-config", - "version": "2.0.0-rc1", + "version": "2.0.0-SNAPSHOT", "description": "ESLint configuration for scm-manager and its plugins", "main": "index.js", "author": "Sebastian Sdorra ", diff --git a/scm-ui/jest-preset/package.json b/scm-ui/jest-preset/package.json index e7ad08782e..c9efe9fe38 100644 --- a/scm-ui/jest-preset/package.json +++ b/scm-ui/jest-preset/package.json @@ -1,6 +1,6 @@ { "name": "@scm-manager/jest-preset", - "version": "2.0.0-rc1", + "version": "2.0.0-SNAPSHOT", "description": "Jest presets for SCM-Manager and its plugins", "main": "src/index.js", "author": "Sebastian Sdorra ", diff --git a/scm-ui/pom.xml b/scm-ui/pom.xml index fdaec15328..ffc727f10d 100644 --- a/scm-ui/pom.xml +++ b/scm-ui/pom.xml @@ -7,13 +7,13 @@ sonia.scm scm - 2.0.0-rc1 + 2.0.0-SNAPSHOT sonia.scm scm-ui war - 2.0.0-rc1 + 2.0.0-SNAPSHOT scm-ui diff --git a/scm-ui/prettier-config/package.json b/scm-ui/prettier-config/package.json index 8298a4c0f0..584081645c 100644 --- a/scm-ui/prettier-config/package.json +++ b/scm-ui/prettier-config/package.json @@ -1,6 +1,6 @@ { "name": "@scm-manager/prettier-config", - "version": "2.0.0-rc1", + "version": "2.0.0-SNAPSHOT", "license": "BSD-3-Clause", "description": "Prettier configuration", "author": "Sebastian Sdorra ", diff --git a/scm-ui/tsconfig/package.json b/scm-ui/tsconfig/package.json index 5843558a5e..4ee8613469 100644 --- a/scm-ui/tsconfig/package.json +++ b/scm-ui/tsconfig/package.json @@ -1,6 +1,6 @@ { "name": "@scm-manager/tsconfig", - "version": "2.0.0-rc1", + "version": "2.0.0-SNAPSHOT", "license": "BSD-3-Clause", "description": "TypeScript configuration", "author": "Sebastian Sdorra ", diff --git a/scm-ui/ui-components/package.json b/scm-ui/ui-components/package.json index d7ab44b959..e7266aae6f 100644 --- a/scm-ui/ui-components/package.json +++ b/scm-ui/ui-components/package.json @@ -1,6 +1,6 @@ { "name": "@scm-manager/ui-components", - "version": "2.0.0-rc1", + "version": "2.0.0-SNAPSHOT", "description": "UI Components for SCM-Manager and its plugins", "main": "src/index.ts", "files": [ @@ -18,7 +18,7 @@ "update-storyshots": "jest --testPathPattern=\"storyshots.test.ts\" --collectCoverage=false -u" }, "devDependencies": { - "@scm-manager/ui-tests": "^2.0.0-rc1", + "@scm-manager/ui-tests": "^2.0.0-SNAPSHOT", "@storybook/addon-actions": "^5.2.3", "@storybook/addon-storyshots": "^5.2.3", "@storybook/react": "^5.2.3", @@ -45,8 +45,8 @@ "typescript": "^3.6.4" }, "dependencies": { - "@scm-manager/ui-extensions": "^2.0.0-rc1", - "@scm-manager/ui-types": "^2.0.0-rc1", + "@scm-manager/ui-extensions": "^2.0.0-SNAPSHOT", + "@scm-manager/ui-types": "^2.0.0-SNAPSHOT", "classnames": "^2.2.6", "date-fns": "^2.4.1", "query-string": "5", diff --git a/scm-ui/ui-extensions/package.json b/scm-ui/ui-extensions/package.json index 242e60c9fb..28e1d99905 100644 --- a/scm-ui/ui-extensions/package.json +++ b/scm-ui/ui-extensions/package.json @@ -1,6 +1,6 @@ { "name": "@scm-manager/ui-extensions", - "version": "2.0.0-rc1", + "version": "2.0.0-SNAPSHOT", "main": "src/index.ts", "license": "BSD-3-Clause", "private": false, diff --git a/scm-ui/ui-plugins/package.json b/scm-ui/ui-plugins/package.json index 537217a098..2e1fdb119f 100644 --- a/scm-ui/ui-plugins/package.json +++ b/scm-ui/ui-plugins/package.json @@ -1,18 +1,18 @@ { "name": "@scm-manager/ui-plugins", - "version": "2.0.0-rc1", + "version": "2.0.0-SNAPSHOT", "license": "BSD-3-Clause", "dependencies": { - "@scm-manager/babel-preset": "^2.0.0-rc1", - "@scm-manager/eslint-config": "^2.0.0-rc1", - "@scm-manager/jest-preset": "^2.0.0-rc1", - "@scm-manager/prettier-config": "^2.0.0-rc1", - "@scm-manager/tsconfig": "^2.0.0-rc1", - "@scm-manager/ui-components": "^2.0.0-rc1", - "@scm-manager/ui-extensions": "^2.0.0-rc1", - "@scm-manager/ui-scripts": "^2.0.0-rc1", - "@scm-manager/ui-tests": "^2.0.0-rc1", - "@scm-manager/ui-types": "^2.0.0-rc1", + "@scm-manager/babel-preset": "^2.0.0-SNAPSHOT", + "@scm-manager/eslint-config": "^2.0.0-SNAPSHOT", + "@scm-manager/jest-preset": "^2.0.0-SNAPSHOT", + "@scm-manager/prettier-config": "^2.0.0-SNAPSHOT", + "@scm-manager/tsconfig": "^2.0.0-SNAPSHOT", + "@scm-manager/ui-components": "^2.0.0-SNAPSHOT", + "@scm-manager/ui-extensions": "^2.0.0-SNAPSHOT", + "@scm-manager/ui-scripts": "^2.0.0-SNAPSHOT", + "@scm-manager/ui-tests": "^2.0.0-SNAPSHOT", + "@scm-manager/ui-types": "^2.0.0-SNAPSHOT", "@types/classnames": "^2.2.9", "@types/enzyme": "^3.10.3", "@types/fetch-mock": "^7.3.1", diff --git a/scm-ui/ui-polyfill/package.json b/scm-ui/ui-polyfill/package.json index 3181ee078f..81922e37c8 100644 --- a/scm-ui/ui-polyfill/package.json +++ b/scm-ui/ui-polyfill/package.json @@ -1,6 +1,6 @@ { "name": "@scm-manager/ui-polyfill", - "version": "2.0.0-rc1", + "version": "2.0.0-SNAPSHOT", "description": "Polyfills for SCM-Manager UI", "main": "src/index.js", "author": "Sebastian Sdorra ", diff --git a/scm-ui/ui-scripts/package.json b/scm-ui/ui-scripts/package.json index fd3d9a491e..7ef5d2819c 100644 --- a/scm-ui/ui-scripts/package.json +++ b/scm-ui/ui-scripts/package.json @@ -1,6 +1,6 @@ { "name": "@scm-manager/ui-scripts", - "version": "2.0.0-rc1", + "version": "2.0.0-SNAPSHOT", "description": "Build scripts for SCM-Manager", "main": "src/index.js", "author": "Sebastian Sdorra ", diff --git a/scm-ui/ui-styles/package.json b/scm-ui/ui-styles/package.json index 4e0856a3ad..16fdb5b307 100644 --- a/scm-ui/ui-styles/package.json +++ b/scm-ui/ui-styles/package.json @@ -1,6 +1,6 @@ { "name": "@scm-manager/ui-styles", - "version": "2.0.0-rc1", + "version": "2.0.0-SNAPSHOT", "description": "Styles for SCM-Manager", "main": "src/scm.scss", "license": "BSD-3-Clause", diff --git a/scm-ui/ui-tests/package.json b/scm-ui/ui-tests/package.json index cfb5b35628..45978d3836 100644 --- a/scm-ui/ui-tests/package.json +++ b/scm-ui/ui-tests/package.json @@ -1,6 +1,6 @@ { "name": "@scm-manager/ui-tests", - "version": "2.0.0-rc1", + "version": "2.0.0-SNAPSHOT", "description": "UI-Tests helpers", "author": "Sebastian Sdorra ", "license": "BSD-3-Clause", diff --git a/scm-ui/ui-types/package.json b/scm-ui/ui-types/package.json index 1c2a96d574..fb971290a8 100644 --- a/scm-ui/ui-types/package.json +++ b/scm-ui/ui-types/package.json @@ -1,6 +1,6 @@ { "name": "@scm-manager/ui-types", - "version": "2.0.0-rc1", + "version": "2.0.0-SNAPSHOT", "description": "Flow types for SCM-Manager related Objects", "main": "src/index.ts", "files": [ diff --git a/scm-ui/ui-webapp/package.json b/scm-ui/ui-webapp/package.json index 322b09d07e..af33163aeb 100644 --- a/scm-ui/ui-webapp/package.json +++ b/scm-ui/ui-webapp/package.json @@ -1,10 +1,10 @@ { "name": "@scm-manager/ui-webapp", - "version": "2.0.0-rc1", + "version": "2.0.0-SNAPSHOT", "private": true, "dependencies": { - "@scm-manager/ui-components": "^2.0.0-rc1", - "@scm-manager/ui-extensions": "^2.0.0-rc1", + "@scm-manager/ui-components": "^2.0.0-SNAPSHOT", + "@scm-manager/ui-extensions": "^2.0.0-SNAPSHOT", "classnames": "^2.2.5", "history": "^4.10.1", "i18next": "^17.3.0", @@ -30,7 +30,7 @@ "flow": "flow" }, "devDependencies": { - "@scm-manager/ui-tests": "^2.0.0-rc1", + "@scm-manager/ui-tests": "^2.0.0-SNAPSHOT", "@types/classnames": "^2.2.9", "@types/enzyme": "^3.10.3", "@types/fetch-mock": "^7.3.1", diff --git a/scm-webapp/pom.xml b/scm-webapp/pom.xml index 9bc163d0a9..571c3b5370 100644 --- a/scm-webapp/pom.xml +++ b/scm-webapp/pom.xml @@ -6,13 +6,13 @@ sonia.scm scm - 2.0.0-rc1 + 2.0.0-SNAPSHOT sonia.scm scm-webapp war - 2.0.0-rc1 + 2.0.0-SNAPSHOT scm-webapp @@ -22,7 +22,7 @@ sonia.scm scm-annotation-processor - 2.0.0-rc1 + 2.0.0-SNAPSHOT provided @@ -47,13 +47,13 @@ sonia.scm scm-core - 2.0.0-rc1 + 2.0.0-SNAPSHOT sonia.scm scm-dao-xml - 2.0.0-rc1 + 2.0.0-SNAPSHOT @@ -288,7 +288,7 @@ sonia.scm scm-test - 2.0.0-rc1 + 2.0.0-SNAPSHOT test @@ -344,7 +344,7 @@ sonia.scm.plugins scm-git-plugin - 2.0.0-rc1 + 2.0.0-SNAPSHOT tests test @@ -352,14 +352,14 @@ sonia.scm.plugins scm-git-plugin - 2.0.0-rc1 + 2.0.0-SNAPSHOT test sonia.scm.plugins scm-hg-plugin - 2.0.0-rc1 + 2.0.0-SNAPSHOT tests test @@ -367,14 +367,14 @@ sonia.scm.plugins scm-hg-plugin - 2.0.0-rc1 + 2.0.0-SNAPSHOT test sonia.scm.plugins scm-svn-plugin - 2.0.0-rc1 + 2.0.0-SNAPSHOT tests test @@ -382,7 +382,7 @@ sonia.scm.plugins scm-svn-plugin - 2.0.0-rc1 + 2.0.0-SNAPSHOT test @@ -606,7 +606,7 @@ sonia.scm scm-ui - 2.0.0-rc1 + 2.0.0-SNAPSHOT war From 2e676a8f5153f8e410d77683ced2700b578c391c Mon Sep 17 00:00:00 2001 From: Eduard Heimbuch Date: Mon, 2 Dec 2019 15:59:32 +0100 Subject: [PATCH 24/53] export table components --- scm-ui/ui-components/src/index.ts | 1 + scm-ui/ui-components/src/table/Column.tsx | 2 +- scm-ui/ui-components/src/table/Table.stories.tsx | 2 +- scm-ui/ui-components/src/table/Table.tsx | 2 +- scm-ui/ui-components/src/table/TextColumn.tsx | 2 +- scm-ui/ui-components/src/table/index.ts | 4 ++++ scm-ui/ui-components/src/table/{types.ts => table.ts} | 0 7 files changed, 9 insertions(+), 4 deletions(-) create mode 100644 scm-ui/ui-components/src/table/index.ts rename scm-ui/ui-components/src/table/{types.ts => table.ts} (100%) diff --git a/scm-ui/ui-components/src/index.ts b/scm-ui/ui-components/src/index.ts index f5c7f62418..d37a04b2e5 100644 --- a/scm-ui/ui-components/src/index.ts +++ b/scm-ui/ui-components/src/index.ts @@ -63,6 +63,7 @@ export * from "./layout"; export * from "./modals"; export * from "./navigation"; export * from "./repos"; +export * from "./table"; export { File, diff --git a/scm-ui/ui-components/src/table/Column.tsx b/scm-ui/ui-components/src/table/Column.tsx index 7f95535c36..4cd0f5d30d 100644 --- a/scm-ui/ui-components/src/table/Column.tsx +++ b/scm-ui/ui-components/src/table/Column.tsx @@ -1,5 +1,5 @@ import React, { FC, ReactNode } from "react"; -import { ColumnProps } from "./types"; +import { ColumnProps } from "./table"; type Props = ColumnProps & { children: (row: any, columnIndex: number) => ReactNode; diff --git a/scm-ui/ui-components/src/table/Table.stories.tsx b/scm-ui/ui-components/src/table/Table.stories.tsx index 84b80f001f..b29db2c965 100644 --- a/scm-ui/ui-components/src/table/Table.stories.tsx +++ b/scm-ui/ui-components/src/table/Table.stories.tsx @@ -3,7 +3,7 @@ import { storiesOf } from "@storybook/react"; import Table from "./Table"; import Column from "./Column"; import TextColumn from "./TextColumn"; -import { ColumnProps } from "./types"; +import { ColumnProps } from "./table"; storiesOf("Table|Table", module) .add("Default", () => ( diff --git a/scm-ui/ui-components/src/table/Table.tsx b/scm-ui/ui-components/src/table/Table.tsx index 98f9d9f038..a41ad84d5a 100644 --- a/scm-ui/ui-components/src/table/Table.tsx +++ b/scm-ui/ui-components/src/table/Table.tsx @@ -1,6 +1,6 @@ import React, { FC, useState } from "react"; import styled from "styled-components"; -import { Comparator } from "./types"; +import { Comparator } from "./table"; import SortIcon from "./SortIcon"; const StyledTable = styled.table.attrs(() => ({ diff --git a/scm-ui/ui-components/src/table/TextColumn.tsx b/scm-ui/ui-components/src/table/TextColumn.tsx index c3184741da..3b55a78edf 100644 --- a/scm-ui/ui-components/src/table/TextColumn.tsx +++ b/scm-ui/ui-components/src/table/TextColumn.tsx @@ -1,5 +1,5 @@ import React, { FC } from "react"; -import { ColumnProps } from "./types"; +import { ColumnProps } from "./table"; type Props = ColumnProps & { dataKey: string; diff --git a/scm-ui/ui-components/src/table/index.ts b/scm-ui/ui-components/src/table/index.ts new file mode 100644 index 0000000000..517273661b --- /dev/null +++ b/scm-ui/ui-components/src/table/index.ts @@ -0,0 +1,4 @@ +export { default as Table} from "./Table"; +export { default as Column} from "./Column"; +export { default as TextColumn} from "./TextColumn"; +export { default as SortIcon} from "./SortIcon"; diff --git a/scm-ui/ui-components/src/table/types.ts b/scm-ui/ui-components/src/table/table.ts similarity index 100% rename from scm-ui/ui-components/src/table/types.ts rename to scm-ui/ui-components/src/table/table.ts From 5e3e57eb48924b247da0f98c59f7a44adea14375 Mon Sep 17 00:00:00 2001 From: Eduard Heimbuch Date: Tue, 3 Dec 2019 09:37:52 +0100 Subject: [PATCH 25/53] fix ts error --- scm-ui/ui-components/src/table/Table.tsx | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/scm-ui/ui-components/src/table/Table.tsx b/scm-ui/ui-components/src/table/Table.tsx index a41ad84d5a..c89c72e89e 100644 --- a/scm-ui/ui-components/src/table/Table.tsx +++ b/scm-ui/ui-components/src/table/Table.tsx @@ -65,8 +65,6 @@ const Table: FC = ({ data, sortable, children }) => { setlastSortBy(index); }; - // @ts-ignore - const { header } = child.props; return ( tableData.length > 0 && ( @@ -79,7 +77,10 @@ const Table: FC = ({ data, sortable, children }) => { onMouseEnter={() => setHoveredColumnIndex(index)} onMouseLeave={() => setHoveredColumnIndex(undefined)} > - {header} + { + // @ts-ignore + child.props.header + } {isSortable(child) && renderSortIcon(child, ascending, index === lastSortBy || index === hoveredColumnIndex)} From 5e907ea0b55d8bd70d0c8c32b8948a690b8959e2 Mon Sep 17 00:00:00 2001 From: Eduard Heimbuch Date: Tue, 3 Dec 2019 09:42:12 +0100 Subject: [PATCH 26/53] refactor --- scm-ui/ui-components/src/table/Table.tsx | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/scm-ui/ui-components/src/table/Table.tsx b/scm-ui/ui-components/src/table/Table.tsx index c89c72e89e..6d9ccca5c5 100644 --- a/scm-ui/ui-components/src/table/Table.tsx +++ b/scm-ui/ui-components/src/table/Table.tsx @@ -65,6 +65,10 @@ const Table: FC = ({ data, sortable, children }) => { setlastSortBy(index); }; + const shouldShowIcon = (index: number) => { + return index === lastSortBy || index === hoveredColumnIndex; + }; + return ( tableData.length > 0 && ( @@ -81,8 +85,7 @@ const Table: FC = ({ data, sortable, children }) => { // @ts-ignore child.props.header } - {isSortable(child) && - renderSortIcon(child, ascending, index === lastSortBy || index === hoveredColumnIndex)} + {isSortable(child) && renderSortIcon(child, ascending, shouldShowIcon(index))} ))} From e50d3840ce6e1ff19c041a98dd678a2b26479e42 Mon Sep 17 00:00:00 2001 From: Eduard Heimbuch Date: Tue, 3 Dec 2019 09:54:08 +0100 Subject: [PATCH 27/53] push updated stories --- .../src/__snapshots__/storyshots.test.ts.snap | 133 +++++++++++++++++- 1 file changed, 129 insertions(+), 4 deletions(-) diff --git a/scm-ui/ui-components/src/__snapshots__/storyshots.test.ts.snap b/scm-ui/ui-components/src/__snapshots__/storyshots.test.ts.snap index e8c0f9d3f4..844dadca2b 100644 --- a/scm-ui/ui-components/src/__snapshots__/storyshots.test.ts.snap +++ b/scm-ui/ui-components/src/__snapshots__/storyshots.test.ts.snap @@ -336,7 +336,7 @@ exports[`Storyshots DateFromNow Default 1`] = ` exports[`Storyshots Forms|Checkbox Default 1`] = `
`; + +exports[`Storyshots Table|Table Default 1`] = ` + + + + + + + + + + + + + + + + + +
+ FIRST + + SECOND + +
+

+ dddd +

+
+

+ xyz +

+
+

+ abc +

+
+

+ bbbb +

+
+`; + +exports[`Storyshots Table|Table TextColumn 1`] = ` + + + + + + + + + + + + + + + + + + + + + +
+ FIRST + + + SECOND + +
+ d + + y +
+ a + + b +
+ z + + a +
+`; From a08d47df821497fb54ed2092b893ef3dffbf37a3 Mon Sep 17 00:00:00 2001 From: Eduard Heimbuch Date: Tue, 3 Dec 2019 15:28:26 +0100 Subject: [PATCH 28/53] update test data --- .../src/__snapshots__/storyshots.test.ts.snap | 80 ++++++++++++++----- .../ui-components/src/table/Table.stories.tsx | 20 ++--- 2 files changed, 69 insertions(+), 31 deletions(-) diff --git a/scm-ui/ui-components/src/__snapshots__/storyshots.test.ts.snap b/scm-ui/ui-components/src/__snapshots__/storyshots.test.ts.snap index 844dadca2b..206a2075b4 100644 --- a/scm-ui/ui-components/src/__snapshots__/storyshots.test.ts.snap +++ b/scm-ui/ui-components/src/__snapshots__/storyshots.test.ts.snap @@ -2322,7 +2322,7 @@ exports[`Storyshots Table|Table Default 1`] = ` onMouseEnter={[Function]} onMouseLeave={[Function]} > - FIRST + First Name - SECOND + Last Name + + E-Mail + -

- dddd -

+

+ Tricia +

-

- xyz -

+ McMillan + + + + + tricia@hitchhiker.com + -

- abc -

+

+ Arthur +

-

- bbbb -

+ Dent + + + + + arthur@hitchhiker.com + @@ -2390,7 +2406,7 @@ exports[`Storyshots Table|Table TextColumn 1`] = ` onMouseEnter={[Function]} onMouseLeave={[Function]} > - FIRST + Id @@ -2401,7 +2417,18 @@ exports[`Storyshots Table|Table TextColumn 1`] = ` onMouseEnter={[Function]} onMouseLeave={[Function]} > - SECOND + Name + + + + Description @@ -2411,26 +2438,35 @@ exports[`Storyshots Table|Table TextColumn 1`] = ` - d + 21 - y + Pommes + + + Fried potato sticks - a + 42 - b + Quarter-Pounder + + + Big burger - z + -84 - a + Icecream + + + Cold dessert diff --git a/scm-ui/ui-components/src/table/Table.stories.tsx b/scm-ui/ui-components/src/table/Table.stories.tsx index b29db2c965..3d2241dcba 100644 --- a/scm-ui/ui-components/src/table/Table.stories.tsx +++ b/scm-ui/ui-components/src/table/Table.stories.tsx @@ -7,15 +7,15 @@ import { ColumnProps } from "./table"; storiesOf("Table|Table", module) .add("Default", () => ( - - {(row: any) =>

{row.first}

}
+
+ {(row: any) =>

{row.firstname}

}
{ return (a: any, b: any) => { - if (a.second > b.second) { + if (a.lastname > b.lastname) { return -1; - } else if (a.second < b.second) { + } else if (a.lastname < b.lastname) { return 1; } else { return 0; @@ -23,13 +23,15 @@ storiesOf("Table|Table", module) }; }} > - {(row: any) =>

{row.second}

} + {(row: any) => {row.lastname}}
+ {(row: any) => {row.email}}
)) .add("TextColumn", () => ( - - - +
+ + +
)); From 54ac5a1f148154da6d18b1e7ee9f88f7d01c4630 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Tue, 3 Dec 2019 16:04:11 +0100 Subject: [PATCH 29/53] remove ts-ignore, added empty message and small bug fixes --- .../ui-components/src/table/Table.stories.tsx | 30 ++++++-- scm-ui/ui-components/src/table/Table.tsx | 72 ++++++++++--------- scm-ui/ui-components/src/table/TextColumn.tsx | 2 +- scm-ui/ui-components/src/table/index.ts | 8 +-- 4 files changed, 67 insertions(+), 45 deletions(-) diff --git a/scm-ui/ui-components/src/table/Table.stories.tsx b/scm-ui/ui-components/src/table/Table.stories.tsx index 3d2241dcba..b4df2a1cb8 100644 --- a/scm-ui/ui-components/src/table/Table.stories.tsx +++ b/scm-ui/ui-components/src/table/Table.stories.tsx @@ -3,15 +3,19 @@ import { storiesOf } from "@storybook/react"; import Table from "./Table"; import Column from "./Column"; import TextColumn from "./TextColumn"; -import { ColumnProps } from "./table"; storiesOf("Table|Table", module) .add("Default", () => ( - +
{(row: any) =>

{row.firstname}

}
{ + createComparator={() => { return (a: any, b: any) => { if (a.lastname > b.lastname) { return -1; @@ -29,9 +33,21 @@ storiesOf("Table|Table", module)
)) .add("TextColumn", () => ( - - - - +
+ + + +
+ )) + .add("Empty", () => ( + + +
)); diff --git a/scm-ui/ui-components/src/table/Table.tsx b/scm-ui/ui-components/src/table/Table.tsx index 6d9ccca5c5..ffcd38238f 100644 --- a/scm-ui/ui-components/src/table/Table.tsx +++ b/scm-ui/ui-components/src/table/Table.tsx @@ -1,7 +1,8 @@ -import React, { FC, useState } from "react"; +import React, { FC, ReactElement, useState } from "react"; import styled from "styled-components"; import { Comparator } from "./table"; import SortIcon from "./SortIcon"; +import Notification from "../Notification"; const StyledTable = styled.table.attrs(() => ({ className: "table content is-hoverable" @@ -10,23 +11,23 @@ const StyledTable = styled.table.attrs(() => ({ type Props = { data: any[]; sortable?: boolean; + emptyMessage?: string; + children: Array; }; -// @ts-ignore -const Table: FC = ({ data, sortable, children }) => { +const Table: FC = ({ data, sortable, children, emptyMessage }) => { const [tableData, setTableData] = useState(data); const [ascending, setAscending] = useState(false); const [lastSortBy, setlastSortBy] = useState(); const [hoveredColumnIndex, setHoveredColumnIndex] = useState(); - const isSortable = (child: any) => { + const isSortable = (child: ReactElement) => { return sortable && child.props.createComparator; }; const sortFunctions: Comparator | undefined[] = []; React.Children.forEach(children, (child, index) => { - if (isSortable(child)) { - // @ts-ignore + if (child && isSortable(child)) { sortFunctions.push(child.props.createComparator(child.props, index)); } else { sortFunctions.push(undefined); @@ -37,7 +38,6 @@ const Table: FC = ({ data, sortable, children }) => { return ( {React.Children.map(children, (child, columnIndex) => { - // @ts-ignore return {React.cloneElement(child, { ...child.props, columnIndex, row })}; })} @@ -51,14 +51,17 @@ const Table: FC = ({ data, sortable, children }) => { }; const tableSort = (index: number) => { + const sortFn = sortFunctions[index]; + if (!sortFn) { + throw new Error(`column with index ${index} is not sortable`); + } const sortableData = [...tableData]; let sortOrder = ascending; if (lastSortBy !== index) { setAscending(true); sortOrder = true; } - // @ts-ignore - const sortFunction = sortOrder ? sortFunctions[index] : sortDescending(sortFunctions[index]); + const sortFunction = sortOrder ? sortFn : sortDescending(sortFn); sortableData.sort(sortFunction); setTableData(sortableData); setAscending(!sortOrder); @@ -69,30 +72,33 @@ const Table: FC = ({ data, sortable, children }) => { return index === lastSortBy || index === hoveredColumnIndex; }; + if (!tableData || tableData.length <= 0) { + if (emptyMessage) { + return {emptyMessage}; + } else { + return null; + } + } + return ( - tableData.length > 0 && ( - - - - {React.Children.map(children, (child, index) => ( - tableSort(index) : undefined} - onMouseEnter={() => setHoveredColumnIndex(index)} - onMouseLeave={() => setHoveredColumnIndex(undefined)} - > - { - // @ts-ignore - child.props.header - } - {isSortable(child) && renderSortIcon(child, ascending, shouldShowIcon(index))} - - ))} - - - {tableData.map(mapDataToColumns)} - - ) + + + + {React.Children.map(children, (child, index) => ( + tableSort(index) : undefined} + onMouseEnter={() => setHoveredColumnIndex(index)} + onMouseLeave={() => setHoveredColumnIndex(undefined)} + > + {child.props.header} + {isSortable(child) && renderSortIcon(child, ascending, shouldShowIcon(index))} + + ))} + + + {tableData.map(mapDataToColumns)} + ); }; @@ -100,7 +106,7 @@ Table.defaultProps = { sortable: true }; -const renderSortIcon = (child: any, ascending: boolean, showIcon: boolean) => { +const renderSortIcon = (child: ReactElement, ascending: boolean, showIcon: boolean) => { if (child.props.ascendingIcon && child.props.descendingIcon) { return ; } else { diff --git a/scm-ui/ui-components/src/table/TextColumn.tsx b/scm-ui/ui-components/src/table/TextColumn.tsx index 3b55a78edf..a170b02192 100644 --- a/scm-ui/ui-components/src/table/TextColumn.tsx +++ b/scm-ui/ui-components/src/table/TextColumn.tsx @@ -10,7 +10,7 @@ const TextColumn: FC = ({ row, dataKey }) => { }; TextColumn.defaultProps = { - createComparator: (props: Props, columnIndex) => { + createComparator: (props: Props) => { return (a: any, b: any) => { if (a[props.dataKey] < b[props.dataKey]) { return -1; diff --git a/scm-ui/ui-components/src/table/index.ts b/scm-ui/ui-components/src/table/index.ts index 517273661b..c99a4e64c1 100644 --- a/scm-ui/ui-components/src/table/index.ts +++ b/scm-ui/ui-components/src/table/index.ts @@ -1,4 +1,4 @@ -export { default as Table} from "./Table"; -export { default as Column} from "./Column"; -export { default as TextColumn} from "./TextColumn"; -export { default as SortIcon} from "./SortIcon"; +export { default as Table } from "./Table"; +export { default as Column } from "./Column"; +export { default as TextColumn } from "./TextColumn"; +export { default as SortIcon } from "./SortIcon"; From 74029e455373ccd8a39385baa1c92e84b56e7e8b Mon Sep 17 00:00:00 2001 From: Eduard Heimbuch Date: Tue, 3 Dec 2019 15:08:13 +0000 Subject: [PATCH 30/53] Close branch feature/sortable_table From 6e632bcce1848fa19949e2d165fbc7ea9d85bef0 Mon Sep 17 00:00:00 2001 From: Eduard Heimbuch Date: Tue, 3 Dec 2019 16:29:55 +0100 Subject: [PATCH 31/53] update story snapshot for empty table --- .../src/__snapshots__/storyshots.test.ts.snap | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/scm-ui/ui-components/src/__snapshots__/storyshots.test.ts.snap b/scm-ui/ui-components/src/__snapshots__/storyshots.test.ts.snap index 206a2075b4..ac759241d2 100644 --- a/scm-ui/ui-components/src/__snapshots__/storyshots.test.ts.snap +++ b/scm-ui/ui-components/src/__snapshots__/storyshots.test.ts.snap @@ -2394,6 +2394,15 @@ exports[`Storyshots Table|Table Default 1`] = ` `; +exports[`Storyshots Table|Table Empty 1`] = ` +
+ + No data found. +
+`; + exports[`Storyshots Table|Table TextColumn 1`] = ` Date: Wed, 4 Dec 2019 09:43:06 +0100 Subject: [PATCH 32/53] rename version script ot set-version in order to avoid wrong execution durring deploy --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 8034538536..8d9a9c931b 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "typecheck": "lerna run --scope '@scm-manager/ui-*' typecheck", "serve": "webpack-dev-server --mode=development --config=scm-ui/ui-scripts/src/webpack.config.js", "deploy": "ui-scripts publish", - "version": "ui-scripts version" + "set-version": "ui-scripts version" }, "devDependencies": { "babel-plugin-reflow": "^0.2.7", From d70948a650be617e583661362f78630e9ae6c114 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Wed, 4 Dec 2019 10:48:34 +0100 Subject: [PATCH 33/53] log error messages, if startup fails --- .../java/sonia/scm/lifecycle/BootstrapContextListener.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) 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 223e3cf65b..81fe7cced9 100644 --- a/scm-webapp/src/main/java/sonia/scm/lifecycle/BootstrapContextListener.java +++ b/scm-webapp/src/main/java/sonia/scm/lifecycle/BootstrapContextListener.java @@ -88,14 +88,16 @@ public class BootstrapContextListener extends GuiceServletContextListener { protected Injector getInjector() { Throwable startupError = SCMContext.getContext().getStartupError(); if (startupError != null) { + LOG.error("received unrecoverable error during startup", startupError); return createStageOneInjector(SingleView.error(startupError)); } else if (Versions.isTooOld()) { - LOG.error("Existing version is too old and cannot be migrated to new version. Please update to version {} first", Versions.MIN_VERSION); + LOG.error("existing version is too old and cannot be migrated to new version. Please update to version {} first", Versions.MIN_VERSION); return createStageOneInjector(SingleView.view("/templates/too-old.mustache", HttpServletResponse.SC_CONFLICT)); } else { try { return createStageTwoInjector(); } catch (Exception ex) { + LOG.error("failed to create stage two injector", ex); return createStageOneInjector(SingleView.error(ex)); } } From 14ad7e98f322193b410d583dea8046874e67f1a7 Mon Sep 17 00:00:00 2001 From: Eduard Heimbuch Date: Wed, 4 Dec 2019 13:16:08 +0100 Subject: [PATCH 34/53] rerender table data on change --- scm-ui/ui-components/src/table/Table.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/scm-ui/ui-components/src/table/Table.tsx b/scm-ui/ui-components/src/table/Table.tsx index ffcd38238f..d8344a95eb 100644 --- a/scm-ui/ui-components/src/table/Table.tsx +++ b/scm-ui/ui-components/src/table/Table.tsx @@ -1,4 +1,4 @@ -import React, { FC, ReactElement, useState } from "react"; +import React, { FC, ReactElement, useEffect, useState } from "react"; import styled from "styled-components"; import { Comparator } from "./table"; import SortIcon from "./SortIcon"; @@ -17,6 +17,9 @@ type Props = { const Table: FC = ({ data, sortable, children, emptyMessage }) => { const [tableData, setTableData] = useState(data); + useEffect(() => { + setTableData(data); + }, [data]); const [ascending, setAscending] = useState(false); const [lastSortBy, setlastSortBy] = useState(); const [hoveredColumnIndex, setHoveredColumnIndex] = useState(); From 3e3ab69b14f4c51cdb9caa8b32d12d65cebbbd12 Mon Sep 17 00:00:00 2001 From: Rene Pfeuffer Date: Wed, 4 Dec 2019 17:04:46 +0100 Subject: [PATCH 35/53] Create function for copy on write --- .../java/sonia/scm/store/CopyOnWrite.java | 69 ++++++++++++ .../java/sonia/scm/store/CopyOnWriteTest.java | 101 ++++++++++++++++++ 2 files changed, 170 insertions(+) create mode 100644 scm-dao-xml/src/main/java/sonia/scm/store/CopyOnWrite.java create mode 100644 scm-dao-xml/src/test/java/sonia/scm/store/CopyOnWriteTest.java diff --git a/scm-dao-xml/src/main/java/sonia/scm/store/CopyOnWrite.java b/scm-dao-xml/src/main/java/sonia/scm/store/CopyOnWrite.java new file mode 100644 index 0000000000..510ebb417a --- /dev/null +++ b/scm-dao-xml/src/main/java/sonia/scm/store/CopyOnWrite.java @@ -0,0 +1,69 @@ +package sonia.scm.store; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.UUID; + +public final class CopyOnWrite { + + private CopyOnWrite() {} + + public static void withTemporaryFile(FileWriter creator, Path targetFile) { + validateInput(targetFile); + + try { + Path temporaryFile = Files.createFile(targetFile.getParent().resolve(UUID.randomUUID().toString())); + + creator.write(temporaryFile); + + replaceOriginalFile(targetFile, temporaryFile); + } catch (Exception ex) { + throw new StoreException("could not write file", ex); + } + } + + private static void validateInput(Path targetFile) { + if (Files.isDirectory(targetFile)) { + throw new IllegalArgumentException("target file has to be a regular file, not a directory"); + } + if (targetFile.getParent() == null) { + throw new IllegalArgumentException("target file has to be specified with a parent directory"); + } + } + + private static void replaceOriginalFile(Path targetFile, Path temporaryFile) throws IOException { + Path backupFile = backupOriginalFile(targetFile); + try { + Files.move(temporaryFile, targetFile); + if (backupFile != null) { + Files.delete(backupFile); + } + } catch (RuntimeException | IOException e) { + restoreBackup(targetFile, backupFile); + throw e; + } + } + + private static Path backupOriginalFile(Path targetFile) throws IOException { + Path directory = targetFile.getParent(); + if (Files.exists(targetFile)) { + Path backupFile = directory.resolve(UUID.randomUUID().toString()); + Files.move(targetFile, backupFile); + return backupFile; + } else { + return null; + } + } + + private static void restoreBackup(Path targetFile, Path backupFile) throws IOException { + if (backupFile != null) { + Files.move(backupFile, targetFile); + } + } + + @FunctionalInterface + public interface FileWriter { + void write(Path t) throws Exception; + } +} diff --git a/scm-dao-xml/src/test/java/sonia/scm/store/CopyOnWriteTest.java b/scm-dao-xml/src/test/java/sonia/scm/store/CopyOnWriteTest.java new file mode 100644 index 0000000000..338a491c0d --- /dev/null +++ b/scm-dao-xml/src/test/java/sonia/scm/store/CopyOnWriteTest.java @@ -0,0 +1,101 @@ +package sonia.scm.store; + +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junitpioneer.jupiter.TempDirectory; + +import java.io.FileOutputStream; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + +import static org.junit.jupiter.api.Assertions.assertThrows; +import static sonia.scm.store.CopyOnWrite.withTemporaryFile; + +@ExtendWith(TempDirectory.class) +class CopyOnWriteTest { + + @Test + void shouldCreateNewFile(@TempDirectory.TempDir Path tempDir) { + Path expectedFile = tempDir.resolve("toBeCreated.txt"); + + withTemporaryFile( + file -> new FileOutputStream(file.toFile()).write("great success".getBytes()), + expectedFile); + + Assertions.assertThat(expectedFile).hasContent("great success"); + } + + @Test + void shouldOverwriteExistingFile(@TempDirectory.TempDir Path tempDir) throws IOException { + Path expectedFile = tempDir.resolve("toBeOverwritten.txt"); + Files.createFile(expectedFile); + + withTemporaryFile( + file -> new FileOutputStream(file.toFile()).write("great success".getBytes()), + expectedFile); + + Assertions.assertThat(expectedFile).hasContent("great success"); + } + + @Test + void shouldFailForDirectory(@TempDirectory.TempDir Path tempDir) { + assertThrows(IllegalArgumentException.class, + () -> withTemporaryFile( + file -> new FileOutputStream(file.toFile()).write("should not be written".getBytes()), + tempDir)); + } + + @Test + void shouldFailForMissingDirectory() { + assertThrows( + IllegalArgumentException.class, + () -> withTemporaryFile( + file -> new FileOutputStream(file.toFile()).write("should not be written".getBytes()), + Paths.get("someFile"))); + } + + @Test + void shouldKeepBackupIfTemporaryFileCouldNotBeWritten(@TempDirectory.TempDir Path tempDir) throws IOException { + Path unchangedOriginalFile = tempDir.resolve("notToBeDeleted.txt"); + new FileOutputStream(unchangedOriginalFile.toFile()).write("this should be kept".getBytes()); + + assertThrows( + IOException.class, + () -> withTemporaryFile( + file -> { + throw new IOException("test"); + }, + unchangedOriginalFile)); + + Assertions.assertThat(unchangedOriginalFile).hasContent("this should be kept"); + } + + @Test + void shouldKeepBackupIfTemporaryFileIsMissing(@TempDirectory.TempDir Path tempDir) throws IOException { + Path backedUpFile = tempDir.resolve("notToBeDeleted.txt"); + new FileOutputStream(backedUpFile.toFile()).write("this should be kept".getBytes()); + + assertThrows( + IOException.class, + () -> withTemporaryFile( + Files::delete, + backedUpFile)); + + Assertions.assertThat(backedUpFile).hasContent("this should be kept"); + } + + @Test + void shouldDeleteExistingFile(@TempDirectory.TempDir Path tempDir) throws IOException { + Path expectedFile = tempDir.resolve("toBeReplaced.txt"); + new FileOutputStream(expectedFile.toFile()).write("this should be removed".getBytes()); + + withTemporaryFile( + file -> new FileOutputStream(file.toFile()).write("overwritten".getBytes()), + expectedFile); + + Assertions.assertThat(Files.list(tempDir)).hasSize(1); + } +} From c4ed6f917d16b1ce35f8e813f8d0a0bf3e731f3d Mon Sep 17 00:00:00 2001 From: Rene Pfeuffer Date: Wed, 4 Dec 2019 17:16:17 +0100 Subject: [PATCH 36/53] Use copy on write in JAXB store implementations --- .../store/JAXBConfigurationEntryStore.java | 61 +++++++++---------- .../scm/store/JAXBConfigurationStore.java | 5 +- 2 files changed, 34 insertions(+), 32 deletions(-) diff --git a/scm-dao-xml/src/main/java/sonia/scm/store/JAXBConfigurationEntryStore.java b/scm-dao-xml/src/main/java/sonia/scm/store/JAXBConfigurationEntryStore.java index 40cf03c8a8..deddaa9500 100644 --- a/scm-dao-xml/src/main/java/sonia/scm/store/JAXBConfigurationEntryStore.java +++ b/scm-dao-xml/src/main/java/sonia/scm/store/JAXBConfigurationEntryStore.java @@ -317,48 +317,47 @@ public class JAXBConfigurationEntryStore implements ConfigurationEntryStore { + try (IndentXMLStreamWriter writer = XmlStreams.createWriter(temp)) { + writer.writeStartDocument(); - // configuration start - writer.writeStartElement(TAG_CONFIGURATION); + // configuration start + writer.writeStartElement(TAG_CONFIGURATION); - Marshaller m = context.createMarshaller(); + Marshaller m = context.createMarshaller(); - m.setProperty(Marshaller.JAXB_FRAGMENT, Boolean.TRUE); + m.setProperty(Marshaller.JAXB_FRAGMENT, Boolean.TRUE); - for (Entry e : entries.entrySet()) - { + for (Entry e : entries.entrySet()) { - // entry start - writer.writeStartElement(TAG_ENTRY); + // entry start + writer.writeStartElement(TAG_ENTRY); - // key start - writer.writeStartElement(TAG_KEY); - writer.writeCharacters(e.getKey()); + // key start + writer.writeStartElement(TAG_KEY); + writer.writeCharacters(e.getKey()); - // key end - writer.writeEndElement(); + // key end + writer.writeEndElement(); - // value - JAXBElement je = new JAXBElement(QName.valueOf(TAG_VALUE), type, - e.getValue()); + // value + JAXBElement je = new JAXBElement<>(QName.valueOf(TAG_VALUE), type, + e.getValue()); - m.marshal(je, writer); + m.marshal(je, writer); - // entry end - writer.writeEndElement(); - } + // entry end + writer.writeEndElement(); + } - // configuration end - writer.writeEndElement(); - writer.writeEndDocument(); - } - catch (Exception ex) - { - throw new StoreException("could not store configuration", ex); - } + // configuration end + writer.writeEndElement(); + writer.writeEndDocument(); + } + }, + file.toPath() + ); } //~--- fields --------------------------------------------------------------- diff --git a/scm-dao-xml/src/main/java/sonia/scm/store/JAXBConfigurationStore.java b/scm-dao-xml/src/main/java/sonia/scm/store/JAXBConfigurationStore.java index ac1477d7ea..92bd0fbcc4 100644 --- a/scm-dao-xml/src/main/java/sonia/scm/store/JAXBConfigurationStore.java +++ b/scm-dao-xml/src/main/java/sonia/scm/store/JAXBConfigurationStore.java @@ -113,7 +113,10 @@ public class JAXBConfigurationStore extends AbstractStore { Marshaller marshaller = context.createMarshaller(); marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE); - marshaller.marshal(object, configFile); + CopyOnWrite.withTemporaryFile( + temp -> marshaller.marshal(object, temp.toFile()), + configFile.toPath() + ); } catch (JAXBException ex) { throw new StoreException("failed to marshall object", ex); From 9ca2a576675d0b8212e7cb51fa4833340d36baf4 Mon Sep 17 00:00:00 2001 From: Rene Pfeuffer Date: Thu, 5 Dec 2019 07:43:26 +0100 Subject: [PATCH 37/53] Fix unit test --- .../src/test/java/sonia/scm/store/CopyOnWriteTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scm-dao-xml/src/test/java/sonia/scm/store/CopyOnWriteTest.java b/scm-dao-xml/src/test/java/sonia/scm/store/CopyOnWriteTest.java index 338a491c0d..b0f9412359 100644 --- a/scm-dao-xml/src/test/java/sonia/scm/store/CopyOnWriteTest.java +++ b/scm-dao-xml/src/test/java/sonia/scm/store/CopyOnWriteTest.java @@ -63,7 +63,7 @@ class CopyOnWriteTest { new FileOutputStream(unchangedOriginalFile.toFile()).write("this should be kept".getBytes()); assertThrows( - IOException.class, + StoreException.class, () -> withTemporaryFile( file -> { throw new IOException("test"); @@ -79,7 +79,7 @@ class CopyOnWriteTest { new FileOutputStream(backedUpFile.toFile()).write("this should be kept".getBytes()); assertThrows( - IOException.class, + StoreException.class, () -> withTemporaryFile( Files::delete, backedUpFile)); From 791437a905bf5cb6ce61aecdc222dd4baaf78939 Mon Sep 17 00:00:00 2001 From: Rene Pfeuffer Date: Thu, 5 Dec 2019 07:57:47 +0100 Subject: [PATCH 38/53] Use copy on write in repository db --- .../scm/repository/xml/MetadataStore.java | 6 +++- .../scm/repository/xml/PathDatabase.java | 36 +++++++++++-------- 2 files changed, 26 insertions(+), 16 deletions(-) diff --git a/scm-dao-xml/src/main/java/sonia/scm/repository/xml/MetadataStore.java b/scm-dao-xml/src/main/java/sonia/scm/repository/xml/MetadataStore.java index f22180ff9a..3a81f54fb2 100644 --- a/scm-dao-xml/src/main/java/sonia/scm/repository/xml/MetadataStore.java +++ b/scm-dao-xml/src/main/java/sonia/scm/repository/xml/MetadataStore.java @@ -5,6 +5,7 @@ import org.slf4j.LoggerFactory; import sonia.scm.ContextEntry; import sonia.scm.repository.InternalRepositoryException; import sonia.scm.repository.Repository; +import sonia.scm.store.CopyOnWrite; import sonia.scm.store.StoreConstants; import sonia.scm.update.UpdateStepRepositoryMetadataAccess; @@ -43,7 +44,10 @@ public class MetadataStore implements UpdateStepRepositoryMetadataAccess { try { Marshaller marshaller = jaxbContext.createMarshaller(); marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE); - marshaller.marshal(repository, resolveDataPath(path).toFile()); + CopyOnWrite.withTemporaryFile( + temp -> marshaller.marshal(repository, temp.toFile()), + resolveDataPath(path) + ); } catch (JAXBException ex) { throw new InternalRepositoryException(repository, "failed write repository metadata", ex); } diff --git a/scm-dao-xml/src/main/java/sonia/scm/repository/xml/PathDatabase.java b/scm-dao-xml/src/main/java/sonia/scm/repository/xml/PathDatabase.java index 70698aed59..c895b69840 100644 --- a/scm-dao-xml/src/main/java/sonia/scm/repository/xml/PathDatabase.java +++ b/scm-dao-xml/src/main/java/sonia/scm/repository/xml/PathDatabase.java @@ -4,6 +4,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import sonia.scm.ContextEntry; import sonia.scm.repository.InternalRepositoryException; +import sonia.scm.store.CopyOnWrite; import sonia.scm.xml.IndentXMLStreamWriter; import sonia.scm.xml.XmlStreams; @@ -40,23 +41,28 @@ class PathDatabase { ensureParentDirectoryExists(); LOG.trace("write repository path database to {}", storePath); - try (IndentXMLStreamWriter writer = XmlStreams.createWriter(storePath)) { - writer.writeStartDocument(ENCODING, VERSION); + CopyOnWrite.withTemporaryFile( + temp -> { + try (IndentXMLStreamWriter writer = XmlStreams.createWriter(temp)) { + writer.writeStartDocument(ENCODING, VERSION); - writeRepositoriesStart(writer, creationTime, lastModified); - for (Map.Entry e : pathDatabase.entrySet()) { - writeRepository(writer, e.getKey(), e.getValue()); - } - writer.writeEndElement(); + writeRepositoriesStart(writer, creationTime, lastModified); + for (Map.Entry e : pathDatabase.entrySet()) { + writeRepository(writer, e.getKey(), e.getValue()); + } + writer.writeEndElement(); - writer.writeEndDocument(); - } catch (XMLStreamException | IOException ex) { - throw new InternalRepositoryException( - ContextEntry.ContextBuilder.entity(Path.class, storePath.toString()).build(), - "failed to write repository path database", - ex - ); - } + writer.writeEndDocument(); + } catch (XMLStreamException | IOException ex) { + throw new InternalRepositoryException( + ContextEntry.ContextBuilder.entity(Path.class, storePath.toString()).build(), + "failed to write repository path database", + ex + ); + } + }, + storePath + ); } private void ensureParentDirectoryExists() { From 79abecfd36807a92f51c46c73be8dfc7217e3008 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Thu, 5 Dec 2019 08:56:40 +0100 Subject: [PATCH 39/53] fix typecheck on case insensitive file systems --- scm-ui/ui-components/src/table/Column.tsx | 2 +- scm-ui/ui-components/src/table/Table.tsx | 2 +- scm-ui/ui-components/src/table/TextColumn.tsx | 2 +- scm-ui/ui-components/src/table/{table.ts => types.ts} | 0 4 files changed, 3 insertions(+), 3 deletions(-) rename scm-ui/ui-components/src/table/{table.ts => types.ts} (100%) diff --git a/scm-ui/ui-components/src/table/Column.tsx b/scm-ui/ui-components/src/table/Column.tsx index 4cd0f5d30d..7f95535c36 100644 --- a/scm-ui/ui-components/src/table/Column.tsx +++ b/scm-ui/ui-components/src/table/Column.tsx @@ -1,5 +1,5 @@ import React, { FC, ReactNode } from "react"; -import { ColumnProps } from "./table"; +import { ColumnProps } from "./types"; type Props = ColumnProps & { children: (row: any, columnIndex: number) => ReactNode; diff --git a/scm-ui/ui-components/src/table/Table.tsx b/scm-ui/ui-components/src/table/Table.tsx index d8344a95eb..4d12e1c681 100644 --- a/scm-ui/ui-components/src/table/Table.tsx +++ b/scm-ui/ui-components/src/table/Table.tsx @@ -1,6 +1,6 @@ import React, { FC, ReactElement, useEffect, useState } from "react"; import styled from "styled-components"; -import { Comparator } from "./table"; +import { Comparator } from "./types"; import SortIcon from "./SortIcon"; import Notification from "../Notification"; diff --git a/scm-ui/ui-components/src/table/TextColumn.tsx b/scm-ui/ui-components/src/table/TextColumn.tsx index a170b02192..0ad5375503 100644 --- a/scm-ui/ui-components/src/table/TextColumn.tsx +++ b/scm-ui/ui-components/src/table/TextColumn.tsx @@ -1,5 +1,5 @@ import React, { FC } from "react"; -import { ColumnProps } from "./table"; +import { ColumnProps } from "./types"; type Props = ColumnProps & { dataKey: string; diff --git a/scm-ui/ui-components/src/table/table.ts b/scm-ui/ui-components/src/table/types.ts similarity index 100% rename from scm-ui/ui-components/src/table/table.ts rename to scm-ui/ui-components/src/table/types.ts From b2bbd1d9b5e2b82c224032e654bb7a553210bd76 Mon Sep 17 00:00:00 2001 From: Rene Pfeuffer Date: Thu, 5 Dec 2019 10:06:17 +0100 Subject: [PATCH 40/53] Enhance error handling --- .../java/sonia/scm/store/CopyOnWrite.java | 68 ++++++++++++++----- .../java/sonia/scm/store/CopyOnWriteTest.java | 13 ++++ 2 files changed, 63 insertions(+), 18 deletions(-) diff --git a/scm-dao-xml/src/main/java/sonia/scm/store/CopyOnWrite.java b/scm-dao-xml/src/main/java/sonia/scm/store/CopyOnWrite.java index 510ebb417a..18df2b6271 100644 --- a/scm-dao-xml/src/main/java/sonia/scm/store/CopyOnWrite.java +++ b/scm-dao-xml/src/main/java/sonia/scm/store/CopyOnWrite.java @@ -1,5 +1,8 @@ package sonia.scm.store; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; @@ -7,20 +10,15 @@ import java.util.UUID; public final class CopyOnWrite { + private static final Logger LOG = LoggerFactory.getLogger(CopyOnWrite.class); + private CopyOnWrite() {} - public static void withTemporaryFile(FileWriter creator, Path targetFile) { + public static void withTemporaryFile(FileWriter writer, Path targetFile) { validateInput(targetFile); - - try { - Path temporaryFile = Files.createFile(targetFile.getParent().resolve(UUID.randomUUID().toString())); - - creator.write(temporaryFile); - - replaceOriginalFile(targetFile, temporaryFile); - } catch (Exception ex) { - throw new StoreException("could not write file", ex); - } + Path temporaryFile = createTemporaryFile(targetFile); + executeCallback(writer, targetFile, temporaryFile); + replaceOriginalFile(targetFile, temporaryFile); } private static void validateInput(Path targetFile) { @@ -32,38 +30,72 @@ public final class CopyOnWrite { } } - private static void replaceOriginalFile(Path targetFile, Path temporaryFile) throws IOException { + private static Path createTemporaryFile(Path targetFile) { + Path temporaryFile = targetFile.getParent().resolve(UUID.randomUUID().toString()); + try { + Files.createFile(temporaryFile); + } catch (IOException ex) { + LOG.error("Error creating temporary file {} to replace file {}", temporaryFile, targetFile); + throw new StoreException("could create temporary file", ex); + } + return temporaryFile; + } + + private static void executeCallback(FileWriter writer, Path targetFile, Path temporaryFile) { + try { + writer.write(temporaryFile); + } catch (RuntimeException e) { + throw e; + } catch (Exception ex) { + LOG.error("Error writing to temporary file {}. Target file {} has not been modified", temporaryFile, targetFile); + throw new StoreException("could not write temporary file", ex); + } + } + + private static void replaceOriginalFile(Path targetFile, Path temporaryFile) { Path backupFile = backupOriginalFile(targetFile); try { Files.move(temporaryFile, targetFile); if (backupFile != null) { Files.delete(backupFile); } - } catch (RuntimeException | IOException e) { + } catch (IOException e) { + LOG.error("Error renaming temporary file {} to target file {}", temporaryFile, targetFile); restoreBackup(targetFile, backupFile); - throw e; + throw new StoreException("could rename temporary file to target file", e); } } - private static Path backupOriginalFile(Path targetFile) throws IOException { + private static Path backupOriginalFile(Path targetFile) { Path directory = targetFile.getParent(); if (Files.exists(targetFile)) { Path backupFile = directory.resolve(UUID.randomUUID().toString()); - Files.move(targetFile, backupFile); + try { + Files.move(targetFile, backupFile); + } catch (IOException e) { + LOG.error("Could not backup original file {}. Aborting here so that original file will not be overwritten.", targetFile); + throw new StoreException("could not create backup of file", e); + } return backupFile; } else { return null; } } - private static void restoreBackup(Path targetFile, Path backupFile) throws IOException { + private static void restoreBackup(Path targetFile, Path backupFile) { if (backupFile != null) { - Files.move(backupFile, targetFile); + try { + Files.move(backupFile, targetFile); + LOG.info("Recovered original file {} from backup", targetFile); + } catch (IOException e) { + LOG.error("Could not replace original file {} with backup file {} after failure", targetFile, backupFile); + } } } @FunctionalInterface public interface FileWriter { + @SuppressWarnings("squid:S00112") // We do not want to limit exceptions here void write(Path t) throws Exception; } } diff --git a/scm-dao-xml/src/test/java/sonia/scm/store/CopyOnWriteTest.java b/scm-dao-xml/src/test/java/sonia/scm/store/CopyOnWriteTest.java index b0f9412359..3767ac7c06 100644 --- a/scm-dao-xml/src/test/java/sonia/scm/store/CopyOnWriteTest.java +++ b/scm-dao-xml/src/test/java/sonia/scm/store/CopyOnWriteTest.java @@ -73,6 +73,19 @@ class CopyOnWriteTest { Assertions.assertThat(unchangedOriginalFile).hasContent("this should be kept"); } + @Test + void shouldNotWrapRuntimeExceptions(@TempDirectory.TempDir Path tempDir) throws IOException { + Path someFile = tempDir.resolve("something.txt"); + + assertThrows( + NullPointerException.class, + () -> withTemporaryFile( + file -> { + throw new NullPointerException("test"); + }, + someFile)); + } + @Test void shouldKeepBackupIfTemporaryFileIsMissing(@TempDirectory.TempDir Path tempDir) throws IOException { Path backedUpFile = tempDir.resolve("notToBeDeleted.txt"); From 503bd8fa16292a5dd413d1139cd21f6fbf4f6478 Mon Sep 17 00:00:00 2001 From: Rene Pfeuffer Date: Thu, 5 Dec 2019 10:10:26 +0100 Subject: [PATCH 41/53] Enhance error handling --- .../main/java/sonia/scm/store/CopyOnWrite.java | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/scm-dao-xml/src/main/java/sonia/scm/store/CopyOnWrite.java b/scm-dao-xml/src/main/java/sonia/scm/store/CopyOnWrite.java index 18df2b6271..034472a904 100644 --- a/scm-dao-xml/src/main/java/sonia/scm/store/CopyOnWrite.java +++ b/scm-dao-xml/src/main/java/sonia/scm/store/CopyOnWrite.java @@ -56,14 +56,12 @@ public final class CopyOnWrite { Path backupFile = backupOriginalFile(targetFile); try { Files.move(temporaryFile, targetFile); - if (backupFile != null) { - Files.delete(backupFile); - } } catch (IOException e) { LOG.error("Error renaming temporary file {} to target file {}", temporaryFile, targetFile); restoreBackup(targetFile, backupFile); throw new StoreException("could rename temporary file to target file", e); } + deleteBackupFile(backupFile); } private static Path backupOriginalFile(Path targetFile) { @@ -82,6 +80,17 @@ public final class CopyOnWrite { } } + private static void deleteBackupFile(Path backupFile) { + if (backupFile != null) { + try { + Files.delete(backupFile); + } catch (IOException e) { + LOG.warn("Could not delete backup file {}", backupFile); + throw new StoreException("could not delete backup file", e); + } + } + } + private static void restoreBackup(Path targetFile, Path backupFile) { if (backupFile != null) { try { From ff3a71f9d37a8ad63012fa110e22a0cb3bdbf326 Mon Sep 17 00:00:00 2001 From: Eduard Heimbuch Date: Thu, 5 Dec 2019 10:29:39 +0100 Subject: [PATCH 42/53] make label optional on Autocomplete component since its already used without label --- scm-ui/ui-components/src/Autocomplete.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scm-ui/ui-components/src/Autocomplete.tsx b/scm-ui/ui-components/src/Autocomplete.tsx index 814be20aa2..e7fff461e2 100644 --- a/scm-ui/ui-components/src/Autocomplete.tsx +++ b/scm-ui/ui-components/src/Autocomplete.tsx @@ -8,7 +8,7 @@ import { ActionMeta, ValueType } from "react-select/lib/types"; type Props = { loadSuggestions: (p: string) => Promise; valueSelected: (p: SelectValue) => void; - label: string; + label?: string; helpText?: string; value?: SelectValue; placeholder: string; From 2d88b545949afa730f987e18ef4ffa10ff4581da Mon Sep 17 00:00:00 2001 From: Eduard Heimbuch Date: Thu, 5 Dec 2019 09:57:48 +0000 Subject: [PATCH 43/53] Close branch feature/ui_development_mode From 149ea73e00b0cf229a8b5e2f3ce99da489e2440c Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Thu, 5 Dec 2019 13:16:54 +0100 Subject: [PATCH 44/53] move comparators from scm-review-plugin to ui-components --- scm-ui/ui-components/src/comparators.test.ts | 126 ++++++++++++++++++ scm-ui/ui-components/src/comparators.ts | 75 +++++++++++ scm-ui/ui-components/src/index.ts | 2 + scm-ui/ui-components/src/table/TextColumn.tsx | 11 +- 4 files changed, 205 insertions(+), 9 deletions(-) create mode 100644 scm-ui/ui-components/src/comparators.test.ts create mode 100644 scm-ui/ui-components/src/comparators.ts diff --git a/scm-ui/ui-components/src/comparators.test.ts b/scm-ui/ui-components/src/comparators.test.ts new file mode 100644 index 0000000000..345f3651e0 --- /dev/null +++ b/scm-ui/ui-components/src/comparators.test.ts @@ -0,0 +1,126 @@ +import { byKey, byValueLength, byNestedKeys } from "./comparators"; + +const createObject = (key: string, value?: string) => { + return { + [key]: value + }; +}; + +const createObjects = (key: string, values: Array) => { + return values.map(v => createObject(key, v)); +}; + +describe("key comparator tests", () => { + it("should sort array", () => { + const array = createObjects("key", ["z", "a", "y", "b"]); + array.sort(byKey("key")); + expect(array).toEqual(createObjects("key", ["a", "b", "y", "z"])); + }); + + it("should not fail if value is undefined", () => { + const array = createObjects("key", ["z", undefined, "a"]); + array.sort(byKey("key")); + expect(array).toEqual(createObjects("key", ["a", "z", undefined])); + }); + + it("should not fail if key is undefined", () => { + const array = createObjects("key", ["a"]); + array.push({}); + array.push(createObject("key", "z")); + array.sort(byKey("key")); + expect(array).toEqual([createObject("key", "a"), createObject("key", "z"), {}]); + }); + + it("should not fail if item is undefined", () => { + const array: any[] = createObjects("key", ["a"]); + array.push(undefined); + array.push(createObject("key", "z")); + array.sort(byKey("key")); + expect(array).toEqual([createObject("key", "a"), createObject("key", "z"), undefined]); + }); +}); + +describe("length comparator tests", () => { + it("should sort array", () => { + const array = createObjects("key", ["....", ".", "...", ".."]); + array.sort(byValueLength("key")); + expect(array).toEqual(createObjects("key", [".", "..", "...", "...."])); + }); + + it("should not fail if value is undefined", () => { + const array = createObjects("key", ["..", undefined, "."]); + array.sort(byValueLength("key")); + expect(array).toEqual(createObjects("key", [".", "..", undefined])); + }); + + it("should not fail if key is undefined", () => { + const array = createObjects("key", ["."]); + array.push({}); + array.push(createObject("key", "..")); + array.sort(byValueLength("key")); + expect(array).toEqual([createObject("key", "."), createObject("key", ".."), {}]); + }); + + it("should not fail if item is undefined", () => { + const array: any[] = createObjects("key", ["."]); + array.push(undefined); + array.push(createObject("key", "..")); + array.sort(byValueLength("key")); + expect(array).toEqual([createObject("key", "."), createObject("key", ".."), undefined]); + }); +}); + +describe("nested key comparator tests", () => { + const createObject = (key: string, nested?: string, value?: string) => { + if (!nested) { + return { + [key]: undefined + }; + } + return { + [key]: { + [nested]: value + } + }; + }; + + const createObjects = (key: string, nested: string, values: Array) => { + return values.map(v => createObject(key, nested, v)); + }; + + it("should sort array", () => { + const array = createObjects("key", "nested", ["z", "a", "y", "b"]); + array.sort(byNestedKeys("key", "nested")); + expect(array).toEqual(createObjects("key", "nested", ["a", "b", "y", "z"])); + }); + + it("should not fail if value is undefined", () => { + const array = createObjects("key", "nested", ["z", undefined, "a"]); + array.sort(byNestedKeys("key", "nested")); + expect(array).toEqual(createObjects("key", "nested", ["a", "z", undefined])); + }); + + it("should not fail if key is undefined", () => { + const array = createObjects("key", "nested", ["a"]); + array.push({}); + array.push(createObject("key", "nested", "z")); + array.sort(byNestedKeys("key", "nested")); + expect(array).toEqual([createObject("key", "nested", "a"), createObject("key", "nested", "z"), {}]); + }); + + it("should not fail if nested key is undefined", () => { + const array = createObjects("key", "nested", ["a"]); + array.push(createObject("key", undefined, "y")); + array.push(createObject("key", "nested", "z")); + array.sort(byNestedKeys("key", "nested")); + expect(array).toEqual([createObject("key", "nested", "a"), createObject("key", "nested", "z"), { key: undefined }]); + }); + + it("should not fail if item is undefined", () => { + const array: any[] = createObjects("key", "nested", ["a"]); + array.push(undefined); + array.push(createObject("key", "nested", "z")); + array.sort(byNestedKeys("key", "nested")); + expect(array).toEqual([createObject("key", "nested", "a"), createObject("key", "nested", "z"), undefined]); + }); +}); diff --git a/scm-ui/ui-components/src/comparators.ts b/scm-ui/ui-components/src/comparators.ts new file mode 100644 index 0000000000..e4d661c2fd --- /dev/null +++ b/scm-ui/ui-components/src/comparators.ts @@ -0,0 +1,75 @@ +const isUndefined = (o: any, key: string, nested?: string) => { + if (typeof o === "undefined" || typeof o[key] === "undefined") { + return true; + } + if (nested) { + return typeof o[key][nested] === "undefined"; + } + return false; +}; + +export const byKey = (key: string) => { + return (a: any, b: any) => { + if (isUndefined(a, key)) { + return 1; + } + + if (isUndefined(b, key)) { + return 0; + } + + if (a[key] < b[key]) { + return -1; + } else if (a[key] > b[key]) { + return 1; + } else { + return 0; + } + }; +}; + +export const byValueLength = (key: string) => { + return (a: any, b: any) => { + if (isUndefined(a, key)) { + return 1; + } + + if (isUndefined(b, key)) { + return 0; + } + + if (a[key].length < b[key].length) { + return -1; + } else if (a[key].length > b[key].length) { + return 1; + } else { + return 0; + } + }; +}; + +export const byNestedKeys = (key: string, nestedKey: string) => { + return (a: any, b: any) => { + if (isUndefined(a, key, nestedKey)) { + return 1; + } + + if (isUndefined(b, key, nestedKey)) { + return 0; + } + + if (a[key][nestedKey] < b[key][nestedKey]) { + return -1; + } else if (a[key][nestedKey] > b[key][nestedKey]) { + return 1; + } else { + return 0; + } + }; +}; + +export default { + byKey, + byValueLength, + byNestedKeys +}; diff --git a/scm-ui/ui-components/src/index.ts b/scm-ui/ui-components/src/index.ts index d37a04b2e5..e039411842 100644 --- a/scm-ui/ui-components/src/index.ts +++ b/scm-ui/ui-components/src/index.ts @@ -52,6 +52,8 @@ export { default as OverviewPageActions } from "./OverviewPageActions"; export { default as CardColumnGroup } from "./CardColumnGroup"; export { default as CardColumn } from "./CardColumn"; +export { default as comparators } from "./comparators"; + export { apiClient } from "./apiclient"; export * from "./errors"; diff --git a/scm-ui/ui-components/src/table/TextColumn.tsx b/scm-ui/ui-components/src/table/TextColumn.tsx index 0ad5375503..b2a19007bd 100644 --- a/scm-ui/ui-components/src/table/TextColumn.tsx +++ b/scm-ui/ui-components/src/table/TextColumn.tsx @@ -1,5 +1,6 @@ import React, { FC } from "react"; import { ColumnProps } from "./types"; +import comparators from "../comparators"; type Props = ColumnProps & { dataKey: string; @@ -11,15 +12,7 @@ const TextColumn: FC = ({ row, dataKey }) => { TextColumn.defaultProps = { createComparator: (props: Props) => { - return (a: any, b: any) => { - if (a[props.dataKey] < b[props.dataKey]) { - return -1; - } else if (a[props.dataKey] > b[props.dataKey]) { - return 1; - } else { - return 0; - } - }; + return comparators.byKey(props.dataKey); }, ascendingIcon: "sort-alpha-down-alt", descendingIcon: "sort-alpha-down" From 9525cba06b137df07213d6dd5cf543f9be25ad8a Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Thu, 5 Dec 2019 15:20:47 +0100 Subject: [PATCH 45/53] suppress sonarqube nio performance warning --- scm-dao-xml/src/main/java/sonia/scm/store/CopyOnWrite.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/scm-dao-xml/src/main/java/sonia/scm/store/CopyOnWrite.java b/scm-dao-xml/src/main/java/sonia/scm/store/CopyOnWrite.java index 034472a904..135221ea82 100644 --- a/scm-dao-xml/src/main/java/sonia/scm/store/CopyOnWrite.java +++ b/scm-dao-xml/src/main/java/sonia/scm/store/CopyOnWrite.java @@ -21,6 +21,7 @@ public final class CopyOnWrite { replaceOriginalFile(targetFile, temporaryFile); } + @SuppressWarnings("squid:S3725") // performance of Files#isDirectory private static void validateInput(Path targetFile) { if (Files.isDirectory(targetFile)) { throw new IllegalArgumentException("target file has to be a regular file, not a directory"); @@ -36,7 +37,7 @@ public final class CopyOnWrite { Files.createFile(temporaryFile); } catch (IOException ex) { LOG.error("Error creating temporary file {} to replace file {}", temporaryFile, targetFile); - throw new StoreException("could create temporary file", ex); + throw new StoreException("could not create temporary file", ex); } return temporaryFile; } @@ -64,6 +65,7 @@ public final class CopyOnWrite { deleteBackupFile(backupFile); } + @SuppressWarnings("squid:S3725") // performance of Files#exists private static Path backupOriginalFile(Path targetFile) { Path directory = targetFile.getParent(); if (Files.exists(targetFile)) { From 2f7f160db3ca12fb6180319fb5e56794ef277598 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Thu, 5 Dec 2019 14:21:35 +0000 Subject: [PATCH 46/53] Close branch feature/copy_on_write From d0f8e4ccf25ea2d8569291368dc0bf406b64b088 Mon Sep 17 00:00:00 2001 From: Rene Pfeuffer Date: Thu, 5 Dec 2019 17:43:21 +0100 Subject: [PATCH 47/53] Rename utility class --- .../sonia/scm/api/v2/resources/GitConfigResourceTest.java | 4 ++-- .../v2/resources/HgConfigAutoConfigurationResourceTest.java | 4 ++-- .../api/v2/resources/HgConfigInstallationsResourceTest.java | 4 ++-- .../scm/api/v2/resources/HgConfigPackageResourceTest.java | 4 ++-- .../sonia/scm/api/v2/resources/HgConfigResourceTest.java | 4 ++-- .../sonia/scm/api/v2/resources/SvnConfigResourceTest.java | 4 ++-- .../scm/web/{ScmTestDispatcher.java => RestDispatcher.java} | 6 +++--- .../scm/api/v2/resources/AuthenticationResourceTest.java | 4 ++-- .../scm/api/v2/resources/AutoCompleteResourceTest.java | 4 ++-- .../scm/api/v2/resources/AvailablePluginResourceTest.java | 4 ++-- .../sonia/scm/api/v2/resources/BranchRootResourceTest.java | 4 ++-- .../scm/api/v2/resources/ChangesetRootResourceTest.java | 4 ++-- .../java/sonia/scm/api/v2/resources/ConfigResourceTest.java | 4 ++-- .../java/sonia/scm/api/v2/resources/DiffResourceTest.java | 4 ++-- .../sonia/scm/api/v2/resources/FileHistoryResourceTest.java | 4 ++-- .../sonia/scm/api/v2/resources/GroupRootResourceTest.java | 4 ++-- .../scm/api/v2/resources/IncomingRootResourceTest.java | 4 ++-- .../scm/api/v2/resources/InstalledPluginResourceTest.java | 4 ++-- .../java/sonia/scm/api/v2/resources/MeResourceTest.java | 4 ++-- .../scm/api/v2/resources/ModificationsResourceTest.java | 4 ++-- .../scm/api/v2/resources/PendingPluginResourceTest.java | 4 ++-- .../v2/resources/RepositoryPermissionRootResourceTest.java | 4 ++-- .../api/v2/resources/RepositoryRoleRootResourceTest.java | 4 ++-- .../scm/api/v2/resources/RepositoryRootResourceTest.java | 4 ++-- .../api/v2/resources/RepositoryTypeRootResourceTest.java | 4 ++-- .../sonia/scm/api/v2/resources/SourceRootResourceTest.java | 4 ++-- .../sonia/scm/api/v2/resources/TagRootResourceTest.java | 4 ++-- .../java/sonia/scm/api/v2/resources/UIRootResourceTest.java | 4 ++-- .../sonia/scm/api/v2/resources/UserRootResourceTest.java | 4 ++-- 29 files changed, 59 insertions(+), 59 deletions(-) rename scm-test/src/main/java/sonia/scm/web/{ScmTestDispatcher.java => RestDispatcher.java} (95%) 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 3d3987aca3..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 @@ -24,7 +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.ScmTestDispatcher; +import sonia.scm.web.RestDispatcher; import javax.servlet.http.HttpServletResponse; import java.io.UnsupportedEncodingException; @@ -50,7 +50,7 @@ public class GitConfigResourceTest { @Rule public ShiroRule shiro = new ShiroRule(); - private ScmTestDispatcher dispatcher = new ScmTestDispatcher(); + private RestDispatcher dispatcher = new RestDispatcher(); private final URI baseUri = URI.create("/"); 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 fa2b559041..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 @@ -15,7 +15,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.ScmTestDispatcher; +import sonia.scm.web.RestDispatcher; import javax.inject.Provider; import javax.servlet.http.HttpServletResponse; @@ -37,7 +37,7 @@ public class HgConfigAutoConfigurationResourceTest { @Rule public ShiroRule shiro = new ShiroRule(); - private ScmTestDispatcher dispatcher = new ScmTestDispatcher(); + private RestDispatcher dispatcher = new RestDispatcher(); @InjectMocks private HgConfigDtoToHgConfigMapperImpl dtoToConfigMapper; 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 827c69d57f..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 @@ -12,7 +12,7 @@ import org.mockito.Answers; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; -import sonia.scm.web.ScmTestDispatcher; +import sonia.scm.web.RestDispatcher; import javax.inject.Provider; import javax.servlet.http.HttpServletResponse; @@ -33,7 +33,7 @@ public class HgConfigInstallationsResourceTest { @Rule public ShiroRule shiro = new ShiroRule(); - private ScmTestDispatcher dispatcher = new ScmTestDispatcher(); + private RestDispatcher dispatcher = new RestDispatcher(); private final URI baseUri = URI.create("/"); 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 6b77b7a0fc..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 @@ -20,7 +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.ScmTestDispatcher; +import sonia.scm.web.RestDispatcher; import javax.inject.Provider; import javax.servlet.http.HttpServletResponse; @@ -47,7 +47,7 @@ public class HgConfigPackageResourceTest { @Rule public ShiroRule shiro = new ShiroRule(); - private ScmTestDispatcher dispatcher = new ScmTestDispatcher(); + private RestDispatcher dispatcher = new RestDispatcher(); private final URI baseUri = java.net.URI.create("/"); 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 f03c894b13..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 @@ -17,7 +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.ScmTestDispatcher; +import sonia.scm.web.RestDispatcher; import javax.inject.Provider; import javax.servlet.http.HttpServletResponse; @@ -41,7 +41,7 @@ public class HgConfigResourceTest { @Rule public ShiroRule shiro = new ShiroRule(); - private ScmTestDispatcher dispatcher = new ScmTestDispatcher(); + private RestDispatcher dispatcher = new RestDispatcher(); private final URI baseUri = URI.create("/"); 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 225c838a6c..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 @@ -16,7 +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.ScmTestDispatcher; +import sonia.scm.web.RestDispatcher; import sonia.scm.web.SvnVndMediaType; import javax.servlet.http.HttpServletResponse; @@ -40,7 +40,7 @@ public class SvnConfigResourceTest { @Rule public ShiroRule shiro = new ShiroRule(); - private ScmTestDispatcher dispatcher = new ScmTestDispatcher(); + private RestDispatcher dispatcher = new RestDispatcher(); private final URI baseUri = URI.create("/"); diff --git a/scm-test/src/main/java/sonia/scm/web/ScmTestDispatcher.java b/scm-test/src/main/java/sonia/scm/web/RestDispatcher.java similarity index 95% rename from scm-test/src/main/java/sonia/scm/web/ScmTestDispatcher.java rename to scm-test/src/main/java/sonia/scm/web/RestDispatcher.java index f8b3040f73..bf0a64afc7 100644 --- a/scm-test/src/main/java/sonia/scm/web/ScmTestDispatcher.java +++ b/scm-test/src/main/java/sonia/scm/web/RestDispatcher.java @@ -23,14 +23,14 @@ import javax.ws.rs.ext.Provider; import java.util.HashMap; import java.util.Map; -public class ScmTestDispatcher { +public class RestDispatcher { - private static final Logger LOG = LoggerFactory.getLogger(ScmTestDispatcher.class); + private static final Logger LOG = LoggerFactory.getLogger(RestDispatcher.class); private final Dispatcher dispatcher; private final EnhanceableExceptionMapper exceptionMapper; - public ScmTestDispatcher() { + public RestDispatcher() { dispatcher = MockDispatcherFactory.createDispatcher(); exceptionMapper = new EnhanceableExceptionMapper(); dispatcher.getProviderFactory().register(exceptionMapper); 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 e96301ad2a..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 @@ -16,7 +16,7 @@ import sonia.scm.security.AccessTokenBuilder; import sonia.scm.security.AccessTokenBuilderFactory; import sonia.scm.security.AccessTokenCookieIssuer; import sonia.scm.security.DefaultAccessTokenCookieIssuer; -import sonia.scm.web.ScmTestDispatcher; +import sonia.scm.web.RestDispatcher; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @@ -43,7 +43,7 @@ public class AuthenticationResourceTest { @Rule public ShiroRule shiro = new ShiroRule(); - private ScmTestDispatcher dispatcher = new ScmTestDispatcher(); + private RestDispatcher dispatcher = new RestDispatcher(); @Mock private AccessTokenBuilderFactory accessTokenBuilderFactory; 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 7356feafe1..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 @@ -22,7 +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.ScmTestDispatcher; +import sonia.scm.web.RestDispatcher; import sonia.scm.web.VndMediaType; import sonia.scm.xml.XmlDatabase; @@ -50,7 +50,7 @@ public class AutoCompleteResourceTest { public static final String URL = "/" + AutoCompleteResource.PATH; private final Integer defaultLimit = DisplayManager.DEFAULT_LIMIT; - private ScmTestDispatcher dispatcher = new ScmTestDispatcher(); + private RestDispatcher dispatcher = new RestDispatcher(); private XmlUserDAO userDao; private XmlGroupDAO groupDao; 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 fd8b8b9fe9..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 @@ -21,7 +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.ScmTestDispatcher; +import sonia.scm.web.RestDispatcher; import sonia.scm.web.VndMediaType; import javax.inject.Provider; @@ -44,7 +44,7 @@ import static org.mockito.Mockito.when; @ExtendWith(MockitoExtension.class) class AvailablePluginResourceTest { - private ScmTestDispatcher dispatcher = new ScmTestDispatcher(); + private RestDispatcher dispatcher = new RestDispatcher(); @Mock Provider availablePluginResourceProvider; 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 43f3d9f74e..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 @@ -29,7 +29,7 @@ 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.ScmTestDispatcher; +import sonia.scm.web.RestDispatcher; import sonia.scm.web.VndMediaType; import javax.ws.rs.core.MediaType; @@ -56,7 +56,7 @@ public class BranchRootResourceTest extends RepositoryTestBase { public static final String BRANCH_URL = "/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + BRANCH_PATH; public static final String REVISION = "revision"; - private ScmTestDispatcher dispatcher = new ScmTestDispatcher(); + private RestDispatcher dispatcher = new RestDispatcher(); private final URI baseUri = URI.create("/"); private final ResourceLinks resourceLinks = ResourceLinksMock.createMock(baseUri); 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 5e890e36e6..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 @@ -25,7 +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.ScmTestDispatcher; +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 ScmTestDispatcher dispatcher = new ScmTestDispatcher(); + private RestDispatcher dispatcher = new RestDispatcher(); private final URI baseUri = URI.create("/"); private final ResourceLinks resourceLinks = ResourceLinksMock.createMock(baseUri); 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 68a743dab3..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 @@ -13,7 +13,7 @@ import org.mockito.InjectMocks; import org.mockito.Mock; import sonia.scm.config.ScmConfiguration; import sonia.scm.repository.NamespaceStrategyValidator; -import sonia.scm.web.ScmTestDispatcher; +import sonia.scm.web.RestDispatcher; import sonia.scm.web.VndMediaType; import javax.servlet.http.HttpServletResponse; @@ -39,7 +39,7 @@ public class ConfigResourceTest { @Rule public ShiroRule shiro = new ShiroRule(); - private ScmTestDispatcher dispatcher = new ScmTestDispatcher(); + private RestDispatcher dispatcher = new RestDispatcher(); private final URI baseUri = URI.create("/"); @SuppressWarnings("unused") // Is injected 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 fd41150aa8..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 @@ -23,7 +23,7 @@ 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.ScmTestDispatcher; +import sonia.scm.web.RestDispatcher; import sonia.scm.web.VndMediaType; import javax.ws.rs.core.Response; @@ -46,7 +46,7 @@ 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 ScmTestDispatcher dispatcher = new ScmTestDispatcher(); + private RestDispatcher dispatcher = new RestDispatcher(); @Mock private RepositoryServiceFactory serviceFactory; 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 907729d161..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 @@ -27,7 +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.ScmTestDispatcher; +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 ScmTestDispatcher dispatcher = new ScmTestDispatcher(); + private RestDispatcher dispatcher = new RestDispatcher(); private final Subject subject = mock(Subject.class); private final ThreadState subjectThreadState = new SubjectThreadState(subject); 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 97088f9d6f..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 @@ -18,7 +18,7 @@ import sonia.scm.group.Group; import sonia.scm.group.GroupManager; import sonia.scm.security.PermissionAssigner; import sonia.scm.security.PermissionDescriptor; -import sonia.scm.web.ScmTestDispatcher; +import sonia.scm.web.RestDispatcher; import sonia.scm.web.VndMediaType; import javax.servlet.http.HttpServletResponse; @@ -52,7 +52,7 @@ public class GroupRootResourceTest { @Rule public ShiroRule shiro = new ShiroRule(); - private ScmTestDispatcher dispatcher = new ScmTestDispatcher(); + private RestDispatcher dispatcher = new RestDispatcher(); private final ResourceLinks resourceLinks = ResourceLinksMock.createMock(URI.create("/")); 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 481960e8c7..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 @@ -28,7 +28,7 @@ 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.ScmTestDispatcher; +import sonia.scm.web.RestDispatcher; import sonia.scm.web.VndMediaType; import javax.ws.rs.core.Response; @@ -55,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 ScmTestDispatcher dispatcher = new ScmTestDispatcher(); + private RestDispatcher dispatcher = new RestDispatcher(); private final URI baseUri = URI.create("/"); private final ResourceLinks resourceLinks = ResourceLinksMock.createMock(baseUri); 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 5319725c9d..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 @@ -17,7 +17,7 @@ import org.mockito.junit.jupiter.MockitoExtension; import sonia.scm.plugin.InstalledPlugin; import sonia.scm.plugin.PluginInformation; import sonia.scm.plugin.PluginManager; -import sonia.scm.web.ScmTestDispatcher; +import sonia.scm.web.RestDispatcher; import sonia.scm.web.VndMediaType; import javax.inject.Provider; @@ -39,7 +39,7 @@ import static sonia.scm.plugin.PluginTestHelper.createInstalled; @ExtendWith(MockitoExtension.class) class InstalledPluginResourceTest { - private ScmTestDispatcher dispatcher = new ScmTestDispatcher(); + private RestDispatcher dispatcher = new RestDispatcher(); @Mock Provider installedPluginResourceProvider; 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 1c876755a6..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 @@ -20,7 +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.ScmTestDispatcher; +import sonia.scm.web.RestDispatcher; import sonia.scm.web.VndMediaType; import javax.servlet.http.HttpServletResponse; @@ -48,7 +48,7 @@ public class MeResourceTest { @Rule public ShiroRule shiro = new ShiroRule(); - private ScmTestDispatcher dispatcher = new ScmTestDispatcher(); + private RestDispatcher dispatcher = new RestDispatcher(); private final ResourceLinks resourceLinks = ResourceLinksMock.createMock(URI.create("/")); 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 6806dd248e..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 @@ -23,7 +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.ScmTestDispatcher; +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 ScmTestDispatcher dispatcher = new ScmTestDispatcher(); + private RestDispatcher dispatcher = new RestDispatcher(); private final URI baseUri = URI.create("/"); private final ResourceLinks resourceLinks = ResourceLinksMock.createMock(baseUri); 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 be5374472f..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 @@ -20,7 +20,7 @@ import sonia.scm.plugin.InstalledPlugin; import sonia.scm.plugin.InstalledPluginDescriptor; import sonia.scm.plugin.PluginInformation; import sonia.scm.plugin.PluginManager; -import sonia.scm.web.ScmTestDispatcher; +import sonia.scm.web.RestDispatcher; import javax.servlet.http.HttpServletResponse; import javax.ws.rs.core.Response; @@ -40,7 +40,7 @@ import static org.mockito.Mockito.when; @ExtendWith(MockitoExtension.class) class PendingPluginResourceTest { - private ScmTestDispatcher dispatcher = new ScmTestDispatcher(); + private RestDispatcher dispatcher = new RestDispatcher(); ResourceLinks resourceLinks = ResourceLinksMock.createMock(create("/")); 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 8ad4522b4d..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 @@ -29,7 +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.ScmTestDispatcher; +import sonia.scm.web.RestDispatcher; import sonia.scm.web.VndMediaType; import javax.ws.rs.HttpMethod; @@ -104,7 +104,7 @@ public class RepositoryPermissionRootResourceTest extends RepositoryTestBase { .content(PERMISSION_TEST_PAYLOAD) .path(PATH_OF_ONE_PERMISSION); - private ScmTestDispatcher dispatcher = new ScmTestDispatcher(); + private RestDispatcher dispatcher = new RestDispatcher(); @Mock private RepositoryManager repositoryManager; 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 e458af6a11..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 @@ -17,7 +17,7 @@ import org.mockito.junit.MockitoJUnitRunner; import sonia.scm.PageResult; import sonia.scm.repository.RepositoryRole; import sonia.scm.repository.RepositoryRoleManager; -import sonia.scm.web.ScmTestDispatcher; +import sonia.scm.web.RestDispatcher; import sonia.scm.web.VndMediaType; import javax.servlet.http.HttpServletResponse; @@ -63,7 +63,7 @@ public class RepositoryRoleRootResourceTest { private RepositoryRoleCollectionToDtoMapper collectionToDtoMapper; - private ScmTestDispatcher dispatcher = new ScmTestDispatcher(); + private RestDispatcher dispatcher = new RestDispatcher(); @Captor private ArgumentCaptor modifyCaptor; 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 206f7713a3..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 @@ -22,7 +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.ScmTestDispatcher; +import sonia.scm.web.RestDispatcher; import sonia.scm.web.VndMediaType; import javax.servlet.http.HttpServletResponse; @@ -61,7 +61,7 @@ public class RepositoryRootResourceTest extends RepositoryTestBase { private static final String REALM = "AdminRealm"; - private ScmTestDispatcher dispatcher = new ScmTestDispatcher(); + private RestDispatcher dispatcher = new RestDispatcher(); @Rule public ShiroRule shiro = new ShiroRule(); 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 3dd30e21c0..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 @@ -13,7 +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.ScmTestDispatcher; +import sonia.scm.web.RestDispatcher; import sonia.scm.web.VndMediaType; import java.net.URI; @@ -31,7 +31,7 @@ import static org.mockito.Mockito.when; @RunWith(MockitoJUnitRunner.Silent.class) public class RepositoryTypeRootResourceTest { - private ScmTestDispatcher dispatcher = new ScmTestDispatcher(); + private RestDispatcher dispatcher = new RestDispatcher(); @Mock private RepositoryManager repositoryManager; 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 4dd694c092..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 @@ -17,7 +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.ScmTestDispatcher; +import sonia.scm.web.RestDispatcher; import java.io.IOException; import java.net.URI; @@ -30,7 +30,7 @@ import static org.mockito.Mockito.when; @RunWith(MockitoJUnitRunner.Silent.class) public class SourceRootResourceTest extends RepositoryTestBase { - private ScmTestDispatcher dispatcher = new ScmTestDispatcher(); + private RestDispatcher dispatcher = new RestDispatcher(); private final URI baseUri = URI.create("/"); private final ResourceLinks resourceLinks = ResourceLinksMock.createMock(baseUri); 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 29616db703..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 @@ -23,7 +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.ScmTestDispatcher; +import sonia.scm.web.RestDispatcher; import sonia.scm.web.VndMediaType; import java.net.URI; @@ -42,7 +42,7 @@ 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 ScmTestDispatcher dispatcher = new ScmTestDispatcher(); + private RestDispatcher dispatcher = new RestDispatcher(); private final URI baseUri = URI.create("/"); private final ResourceLinks resourceLinks = ResourceLinksMock.createMock(baseUri); 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 da0c862168..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 @@ -15,7 +15,7 @@ import sonia.scm.plugin.InstalledPluginDescriptor; import sonia.scm.plugin.PluginInformation; import sonia.scm.plugin.PluginLoader; import sonia.scm.plugin.PluginResources; -import sonia.scm.web.ScmTestDispatcher; +import sonia.scm.web.RestDispatcher; import sonia.scm.web.VndMediaType; import javax.servlet.http.HttpServletRequest; @@ -37,7 +37,7 @@ import static org.mockito.Mockito.when; @RunWith(MockitoJUnitRunner.Silent.class) public class UIRootResourceTest { - private ScmTestDispatcher dispatcher = new ScmTestDispatcher(); + private RestDispatcher dispatcher = new RestDispatcher(); @Mock private PluginLoader pluginLoader; 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 6426007556..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 @@ -22,7 +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.ScmTestDispatcher; +import sonia.scm.web.RestDispatcher; import sonia.scm.web.VndMediaType; import javax.servlet.http.HttpServletResponse; @@ -56,7 +56,7 @@ public class UserRootResourceTest { @Rule public ShiroRule shiro = new ShiroRule(); - private ScmTestDispatcher dispatcher = new ScmTestDispatcher(); + private RestDispatcher dispatcher = new RestDispatcher(); private final ResourceLinks resourceLinks = ResourceLinksMock.createMock(URI.create("/")); From 6e40f7797348958c6dba9f7b28c50e14e84749c8 Mon Sep 17 00:00:00 2001 From: Rene Pfeuffer Date: Fri, 6 Dec 2019 13:33:37 +0100 Subject: [PATCH 48/53] Add exception to default exception mapper set --- scm-test/src/main/java/sonia/scm/web/RestDispatcher.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/scm-test/src/main/java/sonia/scm/web/RestDispatcher.java b/scm-test/src/main/java/sonia/scm/web/RestDispatcher.java index bf0a64afc7..29d57f204f 100644 --- a/scm-test/src/main/java/sonia/scm/web/RestDispatcher.java +++ b/scm-test/src/main/java/sonia/scm/web/RestDispatcher.java @@ -13,6 +13,7 @@ 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; @@ -64,6 +65,7 @@ public class RestDispatcher { 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) { From fb8d512ec678fd9b93303ba9ca64e8d61d0c561a Mon Sep 17 00:00:00 2001 From: Rene Pfeuffer Date: Fri, 6 Dec 2019 13:45:10 +0100 Subject: [PATCH 49/53] Fix integration tests Without this flag, the server will not be shutdown fast enough between the integration tests of scm-webapp and scm-it. Therefore the new server cannot start correctly whicht results in an error (ShutdownMonitor already started) --- Jenkinsfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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') { From 4a782db1fa104212bf50876b8a4cbec755a3190d Mon Sep 17 00:00:00 2001 From: Rene Pfeuffer Date: Fri, 6 Dec 2019 14:44:50 +0100 Subject: [PATCH 50/53] Implement test --- .../AuthorizationExceptionMapperTest.java | 46 ++++++++++++++++++- 1 file changed, 45 insertions(+), 1 deletion(-) 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 index f546a7cf34..cd56d966ef 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/rest/AuthorizationExceptionMapperTest.java +++ b/scm-webapp/src/test/java/sonia/scm/api/rest/AuthorizationExceptionMapperTest.java @@ -1,5 +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 { -// TODO verify differentiation between normal user and anonymous + + 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); + } } From 126649864469b290136b801c2fc81dfdd35e8cbb Mon Sep 17 00:00:00 2001 From: Rene Pfeuffer Date: Fri, 6 Dec 2019 13:45:47 +0000 Subject: [PATCH 51/53] Close branch bugfix/classloader_leak From f85ae4f7f16ba41f4bac2a57b579205ef167e40f Mon Sep 17 00:00:00 2001 From: Rene Pfeuffer Date: Tue, 10 Dec 2019 15:03:05 +0100 Subject: [PATCH 52/53] Guard against empty commit --- .../main/java/sonia/scm/repository/spi/GitBrowseCommand.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitBrowseCommand.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitBrowseCommand.java index 2048d13dea..aa362b8ec6 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitBrowseCommand.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitBrowseCommand.java @@ -70,6 +70,7 @@ import java.util.List; import java.util.Map; import java.util.Optional; +import static java.util.Optional.empty; import static sonia.scm.ContextEntry.ContextBuilder.entity; import static sonia.scm.NotFoundException.notFound; @@ -209,7 +210,7 @@ public class GitBrowseCommand extends AbstractGitCommand logger.trace("fetch last commit for {} at {}", path, revId.getName()); RevCommit commit = getLatestCommit(repo, revId, path); - Optional lfsPointer = GitUtil.getLfsPointer(repo, path, commit, treeWalk); + Optional lfsPointer = commit == null? empty(): GitUtil.getLfsPointer(repo, path, commit, treeWalk); if (lfsPointer.isPresent()) { BlobStore lfsBlobStore = lfsBlobStoreFactory.getLfsBlobStore(repository); From 11acf7ad211483268fb009fa28990ae18fda941d Mon Sep 17 00:00:00 2001 From: Rene Pfeuffer Date: Tue, 10 Dec 2019 15:50:33 +0100 Subject: [PATCH 53/53] Enable cache for browse results --- .../main/java/sonia/scm/api/v2/resources/SourceRootResource.java | 1 - 1 file changed, 1 deletion(-) diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/SourceRootResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/SourceRootResource.java index b431997462..758afd7660 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/SourceRootResource.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/SourceRootResource.java @@ -60,7 +60,6 @@ public class SourceRootResource { if (revision != null && !revision.isEmpty()) { browseCommand.setRevision(URLDecoder.decode(revision, "UTF-8")); } - browseCommand.setDisableCache(true); BrowserResult browserResult = browseCommand.getBrowserResult(); if (browserResult != null) {