From bba4dc34b3802e394f6efe4b8d815eb5baf87752 Mon Sep 17 00:00:00 2001 From: Philipp Czora Date: Fri, 16 Nov 2018 14:13:37 +0100 Subject: [PATCH 01/14] Fixed bug causing branch selector to be mounted before branches are fetched --- scm-ui/src/repos/sources/containers/Sources.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scm-ui/src/repos/sources/containers/Sources.js b/scm-ui/src/repos/sources/containers/Sources.js index 1a9f1d62e7..5c54235659 100644 --- a/scm-ui/src/repos/sources/containers/Sources.js +++ b/scm-ui/src/repos/sources/containers/Sources.js @@ -2,7 +2,7 @@ import React from "react"; import { connect } from "react-redux"; import { withRouter } from "react-router-dom"; -import type { Repository, Branch } from "@scm-manager/ui-types"; +import type { Branch, Repository } from "@scm-manager/ui-types"; import FileTree from "../components/FileTree"; import { ErrorNotification, Loading } from "@scm-manager/ui-components"; import BranchSelector from "../../containers/BranchSelector"; @@ -109,9 +109,9 @@ class Sources extends React.Component { } renderBranchSelector = () => { - const { repository, branches, revision } = this.props; + const { branches, revision } = this.props; - if (repository._links.branches) { + if (this.props.branches) { return ( Date: Fri, 16 Nov 2018 14:21:55 +0100 Subject: [PATCH 02/14] Cleaner code --- scm-ui/src/repos/sources/containers/Sources.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scm-ui/src/repos/sources/containers/Sources.js b/scm-ui/src/repos/sources/containers/Sources.js index 5c54235659..890ab595d0 100644 --- a/scm-ui/src/repos/sources/containers/Sources.js +++ b/scm-ui/src/repos/sources/containers/Sources.js @@ -111,7 +111,7 @@ class Sources extends React.Component { renderBranchSelector = () => { const { branches, revision } = this.props; - if (this.props.branches) { + if (branches) { return ( Date: Mon, 26 Nov 2018 16:55:12 +0100 Subject: [PATCH 03/14] fix xml comment syntax in logging.xml --- deployments/helm/templates/configmap.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deployments/helm/templates/configmap.yaml b/deployments/helm/templates/configmap.yaml index dd52b6fa8c..1ccb773355 100644 --- a/deployments/helm/templates/configmap.yaml +++ b/deployments/helm/templates/configmap.yaml @@ -108,7 +108,7 @@ data: - <-- + From 7c754d5ee49a3854f405f7c60e72650759337c4b Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Mon, 26 Nov 2018 16:55:29 +0100 Subject: [PATCH 04/14] added init container to install plugins --- deployments/helm/templates/deployment.yaml | 16 ++++++++++++++++ deployments/helm/templates/scripts.yaml | 21 +++++++++++++++++++++ deployments/helm/values.yaml | 4 ++++ 3 files changed, 41 insertions(+) create mode 100644 deployments/helm/templates/scripts.yaml diff --git a/deployments/helm/templates/deployment.yaml b/deployments/helm/templates/deployment.yaml index 928daa5f06..7e19f61e57 100644 --- a/deployments/helm/templates/deployment.yaml +++ b/deployments/helm/templates/deployment.yaml @@ -29,6 +29,17 @@ spec: volumeMounts: - name: data mountPath: /data + {{- if .Values.plugins }} + - name: install-plugins + image: alpine:3.8 + imagePullPolicy: IfNotPresent + command: ['sh', '/scripts/install-plugins.sh'] + volumeMounts: + - name: data + mountPath: /data + - name: scripts + mountPath: /scripts + {{- end }} containers: - name: {{ .Chart.Name }} image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" @@ -63,6 +74,11 @@ spec: - name: config configMap: name: {{ include "scm-manager.fullname" . }} + {{- if .Values.plugins }} + - name: scripts + configMap: + name: {{ include "scm-manager.fullname" . }}-scripts + {{- end }} {{- with .Values.nodeSelector }} nodeSelector: {{ toYaml . | indent 8 }} diff --git a/deployments/helm/templates/scripts.yaml b/deployments/helm/templates/scripts.yaml new file mode 100644 index 0000000000..43a442a8e2 --- /dev/null +++ b/deployments/helm/templates/scripts.yaml @@ -0,0 +1,21 @@ +{{- if .Values.plugins }} +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ include "scm-manager.fullname" . }}-scripts + labels: + app: {{ include "scm-manager.name" . }} + chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" + release: "{{ .Release.Name }}" + heritage: "{{ .Release.Service }}" +data: + install-plugins.sh: | + #!/bin/sh + mkdir -p /data/plugins + chown 1000:1000 /data/plugins + {{ range $i, $plugin := .Values.plugins }} + # install plugin {{ $plugin.name }} + wget -O /data/plugins/{{ $plugin.name }}.smp {{ $plugin.url }} + chown 1000:1000 /data/plugins/{{ $plugin.name }}.smp + {{ end }} +{{- end }} diff --git a/deployments/helm/values.yaml b/deployments/helm/values.yaml index d54088aa8b..0b107a8168 100644 --- a/deployments/helm/values.yaml +++ b/deployments/helm/values.yaml @@ -10,6 +10,10 @@ image: tag: latest pullPolicy: Always +# plugins: +# - name: scm-review-plugin +# url: https://oss.cloudogu.com/jenkins/job/scm-manager/job/scm-manager-bitbucket/job/scm-review-plugin/job/develop/lastSuccessfulBuild/artifact/target/scm-review-plugin-2.0.0-SNAPSHOT.smp + nameOverride: "" fullnameOverride: "" From 0f97fee71234aa7783c401af849586d3518f98e0 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Tue, 27 Nov 2018 11:33:27 +0100 Subject: [PATCH 05/14] add polyfill for Object.assign and fetch, to fix ie11 --- .../packages/ui-components/yarn.lock | 4 ---- scm-ui/package.json | 10 ++++++--- scm-ui/public/index.mustache | 1 + scm-ui/yarn.lock | 21 +++++++++++++++++-- 4 files changed, 27 insertions(+), 9 deletions(-) diff --git a/scm-ui-components/packages/ui-components/yarn.lock b/scm-ui-components/packages/ui-components/yarn.lock index d7afe51035..94816787ec 100644 --- a/scm-ui-components/packages/ui-components/yarn.lock +++ b/scm-ui-components/packages/ui-components/yarn.lock @@ -688,10 +688,6 @@ react "^16.4.2" react-dom "^16.4.2" -"@scm-manager/ui-types@2.0.0-SNAPSHOT": - version "2.0.0-20181010-130547" - resolved "https://registry.yarnpkg.com/@scm-manager/ui-types/-/ui-types-2.0.0-20181010-130547.tgz#9987b519e43d5c4b895327d012d3fd72429a7953" - "@types/node@*": version "10.12.0" resolved "https://registry.yarnpkg.com/@types/node/-/node-10.12.0.tgz#ea6dcbddbc5b584c83f06c60e82736d8fbb0c235" diff --git a/scm-ui/package.json b/scm-ui/package.json index c4b7cb3983..d80ee6571e 100644 --- a/scm-ui/package.json +++ b/scm-ui/package.json @@ -5,6 +5,7 @@ "private": true, "main": "src/index.js", "dependencies": { + "@babel/polyfill": "^7.0.0", "@fortawesome/fontawesome-free": "^5.3.1", "@scm-manager/ui-extensions": "^0.1.1", "bulma": "^0.7.1", @@ -31,17 +32,19 @@ "redux": "^4.0.0", "redux-devtools-extension": "^2.13.5", "redux-logger": "^3.0.6", - "redux-thunk": "^2.3.0" + "redux-thunk": "^2.3.0", + "whatwg-fetch": "^3.0.0" }, "scripts": { + "polyfills": "concat node_modules/@babel/polyfill/dist/polyfill.min.js node_modules/whatwg-fetch/dist/fetch.umd.js -o target/scm-ui/polyfills.bundle.js", "webfonts": "copyfiles -f node_modules/@fortawesome/fontawesome-free/webfonts/* target/scm-ui/styles/webfonts", "build-css": "node-sass-chokidar --include-path ./styles --include-path ./node_modules styles/ -o target/scm-ui/styles", "watch-css": "npm run build-css && node-sass-chokidar --include-path ./styles --include-path ./node_modules styles/ -o target/scm-ui/styles --watch --recursive", "start-js": "ui-bundler serve --target target/scm-ui --vendor vendor.bundle.js", - "start": "npm-run-all -p webfonts watch-css start-js", + "start": "npm-run-all -p webfonts watch-css polyfills start-js", "build-js": "ui-bundler bundle --mode=production target/scm-ui/scm-ui.bundle.js", "build-vendor": "ui-bundler vendor --mode=production target/scm-ui/vendor.bundle.js", - "build": "npm-run-all -s webfonts build-css build-vendor build-js", + "build": "npm-run-all -s webfonts build-css polyfills build-vendor build-js", "test": "ui-bundler test", "test-ci": "ui-bundler test --ci", "flow": "flow", @@ -49,6 +52,7 @@ }, "devDependencies": { "@scm-manager/ui-bundler": "^0.0.21", + "concat": "^1.0.3", "copyfiles": "^2.0.0", "enzyme": "^3.3.0", "enzyme-adapter-react-16": "^1.1.1", diff --git a/scm-ui/public/index.mustache b/scm-ui/public/index.mustache index 62a40d8e93..590b5e3cdb 100644 --- a/scm-ui/public/index.mustache +++ b/scm-ui/public/index.mustache @@ -34,6 +34,7 @@ + diff --git a/scm-ui/yarn.lock b/scm-ui/yarn.lock index ec5a53aecc..5e91190b95 100644 --- a/scm-ui/yarn.lock +++ b/scm-ui/yarn.lock @@ -513,6 +513,13 @@ "@babel/helper-regex" "^7.0.0" regexpu-core "^4.1.3" +"@babel/polyfill@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/@babel/polyfill/-/polyfill-7.0.0.tgz#c8ff65c9ec3be6a1ba10113ebd40e8750fb90bff" + dependencies: + core-js "^2.5.7" + regenerator-runtime "^0.11.1" + "@babel/preset-env@^7.0.0": version "7.1.0" resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.1.0.tgz#e67ea5b0441cfeab1d6f41e9b5c79798800e8d11" @@ -2005,6 +2012,12 @@ concat-stream@^1.6.0, concat-stream@^1.6.1, concat-stream@~1.6.0: readable-stream "^2.2.2" typedarray "^0.0.6" +concat@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/concat/-/concat-1.0.3.tgz#40f3353089d65467695cb1886b45edd637d8cca8" + dependencies: + commander "^2.9.0" + connect-history-api-fallback@^1: version "1.5.0" resolved "https://registry.yarnpkg.com/connect-history-api-fallback/-/connect-history-api-fallback-1.5.0.tgz#b06873934bc5e344fef611a196a6faae0aee015a" @@ -2065,7 +2078,7 @@ copyfiles@^2.0.0: through2 "^2.0.1" yargs "^11.0.0" -core-js@^2.4.0, core-js@^2.5.0: +core-js@^2.4.0, core-js@^2.5.0, core-js@^2.5.7: version "2.5.7" resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.5.7.tgz#f972608ff0cead68b841a16a932d0b183791814e" @@ -7056,7 +7069,7 @@ regenerator-runtime@^0.10.5: version "0.10.5" resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.10.5.tgz#336c3efc1220adcedda2c9fab67b5a7955a33658" -regenerator-runtime@^0.11.0: +regenerator-runtime@^0.11.0, regenerator-runtime@^0.11.1: version "0.11.1" resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz#be05ad7f9bf7d22e056f9726cee5017fbf19e2e9" @@ -8530,6 +8543,10 @@ whatwg-fetch@^2.0.4: version "2.0.4" resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-2.0.4.tgz#dde6a5df315f9d39991aa17621853d720b85566f" +whatwg-fetch@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-3.0.0.tgz#fc804e458cc460009b1a2b966bc8817d2578aefb" + whatwg-mimetype@^2.1.0: version "2.2.0" resolved "https://registry.yarnpkg.com/whatwg-mimetype/-/whatwg-mimetype-2.2.0.tgz#a3d58ef10b76009b042d03e25591ece89b88d171" From ed3917469b6c6323133c25029972d734d8453427 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Tue, 27 Nov 2018 11:33:51 +0100 Subject: [PATCH 06/14] added cache-control header to api response, to fix stale data on ie11 --- .../api/v2/CacheControlResponseFilter.java | 39 ++++++++++++ .../v2/CacheControlResponseFilterTest.java | 61 +++++++++++++++++++ 2 files changed, 100 insertions(+) create mode 100644 scm-webapp/src/main/java/sonia/scm/api/v2/CacheControlResponseFilter.java create mode 100644 scm-webapp/src/test/java/sonia/scm/api/v2/CacheControlResponseFilterTest.java diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/CacheControlResponseFilter.java b/scm-webapp/src/main/java/sonia/scm/api/v2/CacheControlResponseFilter.java new file mode 100644 index 0000000000..059b48df5a --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/CacheControlResponseFilter.java @@ -0,0 +1,39 @@ +package sonia.scm.api.v2; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.ws.rs.container.ContainerRequestContext; +import javax.ws.rs.container.ContainerResponseContext; +import javax.ws.rs.container.ContainerResponseFilter; +import javax.ws.rs.ext.Provider; + +/** + * Adds the Cache-Control: no-cache header to every api call. But only if non caching headers are set to the response. + * The Cache-Control header should fix stale resources on ie. + */ +@Provider +public class CacheControlResponseFilter implements ContainerResponseFilter { + + private static final Logger LOG = LoggerFactory.getLogger(CacheControlResponseFilter.class); + + @Override + public void filter(ContainerRequestContext requestContext, ContainerResponseContext responseContext) { + if (!isCacheable(responseContext)) { + LOG.trace("add no-cache header to response"); + responseContext.getHeaders().add("Cache-Control", "no-cache"); + } + } + + private boolean isCacheable(ContainerResponseContext responseContext) { + return hasLastModifiedDate(responseContext) || hasEntityTag(responseContext); + } + + private boolean hasEntityTag(ContainerResponseContext responseContext) { + return responseContext.getEntityTag() != null; + } + + private boolean hasLastModifiedDate(ContainerResponseContext responseContext) { + return responseContext.getLastModified() != null; + } +} diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/CacheControlResponseFilterTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/CacheControlResponseFilterTest.java new file mode 100644 index 0000000000..b0e8c4fdf5 --- /dev/null +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/CacheControlResponseFilterTest.java @@ -0,0 +1,61 @@ +package sonia.scm.api.v2; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; + +import javax.ws.rs.container.ContainerRequestContext; +import javax.ws.rs.container.ContainerResponseContext; +import javax.ws.rs.core.EntityTag; +import javax.ws.rs.core.MultivaluedMap; +import java.util.Date; + +import static org.mockito.Mockito.*; + +@RunWith(MockitoJUnitRunner.class) +public class CacheControlResponseFilterTest { + + @Mock + private ContainerRequestContext requestContext; + + @Mock + private ContainerResponseContext responseContext; + + @Mock + private MultivaluedMap headers; + + private CacheControlResponseFilter filter = new CacheControlResponseFilter(); + + @Before + public void setUpMocks() { + when(responseContext.getHeaders()).thenReturn(headers); + } + + @Test + public void filterShouldAddCacheControlHeader() { + filter.filter(requestContext, responseContext); + + verify(headers).add("Cache-Control", "no-cache"); + } + + @Test + public void filterShouldNotSetHeaderIfLastModifiedIsNotNull() { + when(responseContext.getLastModified()).thenReturn(new Date()); + + filter.filter(requestContext, responseContext); + + verify(headers, never()).add("Cache-Control", "no-cache"); + } + + @Test + public void filterShouldNotSetHeaderIfEtagIsNotNull() { + when(responseContext.getEntityTag()).thenReturn(new EntityTag("42")); + + filter.filter(requestContext, responseContext); + + verify(headers, never()).add("Cache-Control", "no-cache"); + } + +} From 46b111df7d97c056d4d0620a5f81eb41c9c3fa28 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Tue, 27 Nov 2018 11:58:40 +0100 Subject: [PATCH 07/14] remove vulnerable flatmap-stream package --- scm-ui/yarn.lock | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/scm-ui/yarn.lock b/scm-ui/yarn.lock index 5e91190b95..667ba08368 100644 --- a/scm-ui/yarn.lock +++ b/scm-ui/yarn.lock @@ -2941,12 +2941,11 @@ event-emitter@^0.3.5: d "1" es5-ext "~0.10.14" -event-stream@~3.3.0: - version "3.3.6" - resolved "https://registry.yarnpkg.com/event-stream/-/event-stream-3.3.6.tgz#cac1230890e07e73ec9cacd038f60a5b66173eef" +event-stream@3.3.5, event-stream@~3.3.0: + version "3.3.5" + resolved "https://registry.yarnpkg.com/event-stream/-/event-stream-3.3.5.tgz#e5dd8989543630d94c6cf4d657120341fa31636b" dependencies: duplexer "^0.1.1" - flatmap-stream "^0.1.0" from "^0.1.7" map-stream "0.0.7" pause-stream "^0.0.11" @@ -3264,10 +3263,6 @@ flat-cache@^1.2.1: graceful-fs "^4.1.2" write "^0.2.1" -flatmap-stream@^0.1.0: - version "0.1.1" - resolved "https://registry.yarnpkg.com/flatmap-stream/-/flatmap-stream-0.1.1.tgz#d34f39ef3b9aa5a2fc225016bd3adf28ac5ae6ea" - flow-bin@^0.79.1: version "0.79.1" resolved "https://registry.yarnpkg.com/flow-bin/-/flow-bin-0.79.1.tgz#01c9f427baa6556753fa878c192d42e1ecb764b6" From 0f8fb10a03cc3f86dafbbebdc4e8f816c8aeee94 Mon Sep 17 00:00:00 2001 From: Philipp Czora Date: Tue, 27 Nov 2018 12:43:19 +0000 Subject: [PATCH 08/14] Close branch feature/helm-install-plugins From 75b856773df8cde2417a7e269abce28a41d2a18d Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Tue, 27 Nov 2018 12:48:34 +0000 Subject: [PATCH 09/14] Close branch feature/bugfix_branchchooser_in_sources From 2855911fef99d956411b570acd8c1c22a1120278 Mon Sep 17 00:00:00 2001 From: Philipp Czora Date: Tue, 27 Nov 2018 12:49:53 +0000 Subject: [PATCH 10/14] Close branch bugfix/ie11 From bc629ec648e358e492709db664acef6d94b959fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Tue, 27 Nov 2018 14:06:11 +0100 Subject: [PATCH 11/14] Send repository id with hg hook request --- .../RepositoryLocationResolver.java | 6 ++- .../scm/repository/HgRepositoryHandler.java | 7 ++++ .../main/java/sonia/scm/web/HgCGIServlet.java | 4 ++ .../sonia/scm/web/HgHookCallbackServlet.java | 35 ++++++----------- .../resources/sonia/scm/python/scmhooks.py | 3 +- .../scm/web/HgHookCallbackServletTest.java | 39 ------------------- 6 files changed, 30 insertions(+), 64 deletions(-) delete mode 100644 scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/web/HgHookCallbackServletTest.java diff --git a/scm-core/src/main/java/sonia/scm/repository/RepositoryLocationResolver.java b/scm-core/src/main/java/sonia/scm/repository/RepositoryLocationResolver.java index e9355e4b89..9ac93405ce 100644 --- a/scm-core/src/main/java/sonia/scm/repository/RepositoryLocationResolver.java +++ b/scm-core/src/main/java/sonia/scm/repository/RepositoryLocationResolver.java @@ -29,11 +29,15 @@ public class RepositoryLocationResolver { this.initialRepositoryLocationResolver = initialRepositoryLocationResolver; } - File getRepositoryDirectory(Repository repository){ + public File getRepositoryDirectory(Repository repository){ if (repositoryDAO instanceof PathBasedRepositoryDAO) { PathBasedRepositoryDAO pathBasedRepositoryDAO = (PathBasedRepositoryDAO) repositoryDAO; return pathBasedRepositoryDAO.getPath(repository).toFile(); } return initialRepositoryLocationResolver.getRelativeRepositoryPath(repository).getAbsolutePath(); } + + public File getRepositoryDirectory(String repositoryId) { + return getRepositoryDirectory(repositoryDAO.get(repositoryId)); + } } diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgRepositoryHandler.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgRepositoryHandler.java index 533adbac82..f337237dba 100644 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgRepositoryHandler.java +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgRepositoryHandler.java @@ -114,6 +114,7 @@ public class HgRepositoryHandler RepositoryLocationResolver repositoryLocationResolver) { super(storeFactory, repositoryLocationResolver); + this.repositoryLocationResolver = repositoryLocationResolver; this.hgContextProvider = hgContextProvider; try @@ -427,6 +428,10 @@ public class HgRepositoryHandler } } + public File getDirectory(String repositoryId) { + return repositoryLocationResolver.getRepositoryDirectory(repositoryId); + } + //~--- fields --------------------------------------------------------------- /** Field description */ @@ -434,4 +439,6 @@ public class HgRepositoryHandler /** Field description */ private JAXBContext jaxbContext; + + private final RepositoryLocationResolver repositoryLocationResolver; } diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/web/HgCGIServlet.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/web/HgCGIServlet.java index 1821f92fa4..1d4154ca9e 100644 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/web/HgCGIServlet.java +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/web/HgCGIServlet.java @@ -80,6 +80,9 @@ public class HgCGIServlet extends HttpServlet implements ScmProviderHttpServlet /** Field description */ public static final String ENV_REPOSITORY_PATH = "SCM_REPOSITORY_PATH"; + /** Field description */ + public static final String ENV_REPOSITORY_ID = "SCM_REPOSITORY_ID"; + /** Field description */ public static final String ENV_SESSION_PREFIX = "SCM_"; @@ -261,6 +264,7 @@ public class HgCGIServlet extends HttpServlet implements ScmProviderHttpServlet executor.setStatusCodeHandler(exceptionHandler); executor.setContentLengthWorkaround(true); executor.getEnvironment().set(ENV_REPOSITORY_NAME, repository.getNamespace() + "/" + repository.getName()); + executor.getEnvironment().set(ENV_REPOSITORY_ID, repository.getId()); executor.getEnvironment().set(ENV_REPOSITORY_PATH, directory.getAbsolutePath()); diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/web/HgHookCallbackServlet.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/web/HgHookCallbackServlet.java index 4483f74828..1b31eb11ca 100644 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/web/HgHookCallbackServlet.java +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/web/HgHookCallbackServlet.java @@ -35,6 +35,7 @@ package sonia.scm.web; //~--- non-JDK imports -------------------------------------------------------- +import com.google.common.base.Preconditions; import com.google.common.base.Strings; import com.google.common.io.Closeables; import com.google.inject.Inject; @@ -44,12 +45,10 @@ import org.apache.shiro.SecurityUtils; import org.apache.shiro.subject.Subject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import sonia.scm.ContextEntry; import sonia.scm.NotFoundException; import sonia.scm.repository.HgContext; import sonia.scm.repository.HgHookManager; import sonia.scm.repository.HgRepositoryHandler; -import sonia.scm.repository.InternalRepositoryException; import sonia.scm.repository.RepositoryHookType; import sonia.scm.repository.api.HgHookMessage; import sonia.scm.repository.api.HgHookMessage.Severity; @@ -88,7 +87,7 @@ public class HgHookCallbackServlet extends HttpServlet public static final String HGHOOK_PRE_RECEIVE = "pretxnchangegroup"; /** Field description */ - public static final String PARAM_REPOSITORYPATH = "repositoryPath"; + public static final String PARAM_REPOSITORYID = "repositoryId"; /** Field description */ private static final String PARAM_CHALLENGE = "challenge"; @@ -170,7 +169,7 @@ public class HgHookCallbackServlet extends HttpServlet if (m.matches()) { - File repositoryPath = getRepositoryPath(request); + String repositoryId = getRepositoryId(request); String type = m.group(1); String challenge = request.getParameter(PARAM_CHALLENGE); @@ -187,7 +186,7 @@ public class HgHookCallbackServlet extends HttpServlet authenticate(request, credentials); } - hookCallback(response, repositoryPath, type, challenge, node); + hookCallback(response, type, repositoryId, challenge, node); } else if (logger.isDebugEnabled()) { @@ -246,7 +245,7 @@ public class HgHookCallbackServlet extends HttpServlet } } - private void fireHook(HttpServletResponse response, File repositoryDirectory, String node, RepositoryHookType type) + private void fireHook(HttpServletResponse response, String repositoryId, String node, RepositoryHookType type) throws IOException { HgHookContextProvider context = null; @@ -258,10 +257,10 @@ public class HgHookCallbackServlet extends HttpServlet contextProvider.get().setPending(true); } + File repositoryDirectory = handler.getDirectory(repositoryId); context = new HgHookContextProvider(handler, repositoryDirectory, hookManager, node, type); - String repositoryId = getRepositoryId(repositoryDirectory); hookEventFacade.handle(repositoryId).fireHookEvent(type, context); printMessages(response, context); @@ -280,7 +279,7 @@ public class HgHookCallbackServlet extends HttpServlet } } - private void hookCallback(HttpServletResponse response, File repositoryDirectory, String typeName, String challenge, String node) throws IOException { + private void hookCallback(HttpServletResponse response, String typeName, String repositoryId, String challenge, String node) throws IOException { if (hookManager.isAcceptAble(challenge)) { RepositoryHookType type = null; @@ -296,7 +295,7 @@ public class HgHookCallbackServlet extends HttpServlet if (type != null) { - fireHook(response, repositoryDirectory, node, type); + fireHook(response, repositoryId, node, type); } else { @@ -441,21 +440,11 @@ public class HgHookCallbackServlet extends HttpServlet //~--- get methods ---------------------------------------------------------- - @SuppressWarnings("squid:S2083") // we do nothing with the path given, so this should be no issue - private String getRepositoryId(File repositoryPath) + private String getRepositoryId(HttpServletRequest request) { - return handler.getRepositoryId(repositoryPath); - } - - private File getRepositoryPath(HttpServletRequest request) { - String path = request.getParameter(PARAM_REPOSITORYPATH); - if (Util.isNotEmpty(path)) { - return new File(path); - } - else - { - throw new InternalRepositoryException(ContextEntry.ContextBuilder.entity("directory", path), "could not find hgrc in directory"); - } + String id = request.getParameter(PARAM_REPOSITORYID); + Preconditions.checkArgument(!Strings.isNullOrEmpty(id), "repository id not found in request"); + return id; } //~--- fields --------------------------------------------------------------- diff --git a/scm-plugins/scm-hg-plugin/src/main/resources/sonia/scm/python/scmhooks.py b/scm-plugins/scm-hg-plugin/src/main/resources/sonia/scm/python/scmhooks.py index e9a58d589f..493016955d 100644 --- a/scm-plugins/scm-hg-plugin/src/main/resources/sonia/scm/python/scmhooks.py +++ b/scm-plugins/scm-hg-plugin/src/main/resources/sonia/scm/python/scmhooks.py @@ -41,6 +41,7 @@ import os, urllib, urllib2 baseUrl = os.environ['SCM_URL'] challenge = os.environ['SCM_CHALLENGE'] credentials = os.environ['SCM_CREDENTIALS'] +repositoryId = os.environ['SCM_REPOSITORY_ID'] def printMessages(ui, msgs): for line in msgs: @@ -53,7 +54,7 @@ def callHookUrl(ui, repo, hooktype, node): try: url = baseUrl + hooktype ui.debug( "send scm-hook to " + url + " and " + node + "\n" ) - data = urllib.urlencode({'node': node, 'challenge': challenge, 'credentials': credentials, 'repositoryPath': repo.root}) + data = urllib.urlencode({'node': node, 'challenge': challenge, 'credentials': credentials, 'repositoryPath': repo.root, 'repositoryId': repositoryId}) # open url but ignore proxy settings proxy_handler = urllib2.ProxyHandler({}) opener = urllib2.build_opener(proxy_handler) diff --git a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/web/HgHookCallbackServletTest.java b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/web/HgHookCallbackServletTest.java deleted file mode 100644 index eb5caea876..0000000000 --- a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/web/HgHookCallbackServletTest.java +++ /dev/null @@ -1,39 +0,0 @@ -package sonia.scm.web; - -import org.junit.Test; -import sonia.scm.repository.HgRepositoryHandler; -import sonia.scm.repository.Repository; -import sonia.scm.repository.RepositoryDAO; - -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import java.io.File; -import java.io.IOException; - -import static org.mockito.Matchers.anyInt; -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 sonia.scm.web.HgHookCallbackServlet.PARAM_REPOSITORYPATH; - -public class HgHookCallbackServletTest { - - @Test - public void shouldExtractCorrectRepositoryId() throws ServletException, IOException { - HgRepositoryHandler handler = mock(HgRepositoryHandler.class); - HgHookCallbackServlet servlet = new HgHookCallbackServlet(null, handler, null, null); - HttpServletRequest request = mock(HttpServletRequest.class); - HttpServletResponse response = mock(HttpServletResponse.class); - - when(request.getContextPath()).thenReturn("http://example.com/scm"); - when(request.getRequestURI()).thenReturn("http://example.com/scm/hook/hg/pretxnchangegroup"); - String path = "/tmp/hg/12345"; - when(request.getParameter(PARAM_REPOSITORYPATH)).thenReturn(path); - - servlet.doPost(request, response); - - verify(response, never()).sendError(anyInt()); - } -} From d4db39755f54e9324036571122137e8ecffb4e83 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Tue, 27 Nov 2018 15:31:57 +0100 Subject: [PATCH 12/14] Harmonize repository resolution --- .../repository/AbstractRepositoryHandler.java | 2 +- .../AbstractSimpleRepositoryHandler.java | 10 ++++----- .../scm/repository/DirectoryHealthCheck.java | 2 +- .../InitialRepositoryLocationResolver.java | 4 ++-- .../repository/PathBasedRepositoryDAO.java | 5 +++-- .../RepositoryDirectoryHandler.java | 5 ++--- .../RepositoryLocationResolver.java | 10 +++------ ...InitialRepositoryLocationResolverTest.java | 4 +--- .../scm/repository/xml/XmlRepositoryDAO.java | 21 ++++++++----------- .../repository/xml/XmlRepositoryDAOTest.java | 4 ++-- .../java/sonia/scm/repository/GitGcTask.java | 2 +- .../AbstractGitIncomingOutgoingCommand.java | 2 +- .../spi/AbstractGitPushOrPullCommand.java | 2 +- .../scm/repository/spi/GitPullCommand.java | 4 ++-- .../spi/GitRepositoryServiceProvider.java | 2 +- .../sonia/scm/web/GitRepositoryResolver.java | 2 +- .../repository/GitRepositoryHandlerTest.java | 2 +- .../spi/AbstractRemoteCommandTestBase.java | 4 ++-- .../scm/repository/AbstractHgHandler.java | 2 +- .../scm/repository/HgRepositoryHandler.java | 7 ------- .../spi/AbstractHgPushOrPullCommand.java | 2 +- .../scm/repository/spi/HgIncomingCommand.java | 2 +- .../scm/repository/spi/HgOutgoingCommand.java | 2 +- .../spi/HgRepositoryServiceProvider.java | 2 +- .../main/java/sonia/scm/web/HgCGIServlet.java | 3 +-- .../repository/HgRepositoryHandlerTest.java | 2 +- .../spi/IncomingOutgoingTestBase.java | 4 ++-- .../spi/SvnRepositoryServiceProvider.java | 2 +- .../main/java/sonia/scm/web/SvnDAVConfig.java | 2 +- .../repository/SvnRepositoryHandlerTest.java | 2 +- .../SimpleRepositoryHandlerTestBase.java | 2 +- 31 files changed, 52 insertions(+), 69 deletions(-) diff --git a/scm-core/src/main/java/sonia/scm/repository/AbstractRepositoryHandler.java b/scm-core/src/main/java/sonia/scm/repository/AbstractRepositoryHandler.java index 42c8f22a0f..b13cc0e26b 100644 --- a/scm-core/src/main/java/sonia/scm/repository/AbstractRepositoryHandler.java +++ b/scm-core/src/main/java/sonia/scm/repository/AbstractRepositoryHandler.java @@ -168,7 +168,7 @@ public abstract class AbstractRepositoryHandler * @throws NotSupportedFeatureException */ @Override - public ImportHandler getImportHandler() throws NotSupportedFeatureException + public ImportHandler getImportHandler() { throw new NotSupportedFeatureException("import"); } diff --git a/scm-core/src/main/java/sonia/scm/repository/AbstractSimpleRepositoryHandler.java b/scm-core/src/main/java/sonia/scm/repository/AbstractSimpleRepositoryHandler.java index b14c7dd3de..17e5d3e24f 100644 --- a/scm-core/src/main/java/sonia/scm/repository/AbstractSimpleRepositoryHandler.java +++ b/scm-core/src/main/java/sonia/scm/repository/AbstractSimpleRepositoryHandler.java @@ -76,7 +76,7 @@ public abstract class AbstractSimpleRepositoryHandler new InternalRepositoryException(repository, "path object for repository not found")); + RepositoryPath repositoryPath = findExistingRepositoryPath(repository.getId()).orElseThrow(() -> new InternalRepositoryException(repository, "path object for repository not found")); repositoryPath.setRepository(repository); repositoryPath.setToBeSynchronized(true); storeDB(); @@ -112,7 +109,7 @@ public class XmlRepositoryDAO @Override public void add(Repository repository) { - InitialRepositoryLocation initialLocation = initialRepositoryLocationResolver.getRelativeRepositoryPath(repository); + InitialRepositoryLocation initialLocation = initialRepositoryLocationResolver.getRelativeRepositoryPath(repository.getId()); try { fileSystem.create(initialLocation.getAbsolutePath()); } catch (IOException e) { @@ -153,7 +150,7 @@ public class XmlRepositoryDAO @Override public void delete(Repository repository) { - Path directory = getPath(repository); + Path directory = getPath(repository.getId()); super.delete(repository); try { fileSystem.destroy(directory.toFile()); @@ -173,19 +170,19 @@ public class XmlRepositoryDAO } @Override - public Path getPath(Repository repository) { + public Path getPath(String repositoryId) { return context .getBaseDirectory() .toPath() .resolve( - findExistingRepositoryPath(repository) + findExistingRepositoryPath(repositoryId) .map(RepositoryPath::getPath) - .orElseThrow(() -> new InternalRepositoryException(repository, "could not find base directory for repository"))); + .orElseThrow(() -> new InternalRepositoryException(ContextEntry.ContextBuilder.entity("repository", repositoryId), "could not find base directory for repository"))); } - private Optional findExistingRepositoryPath(Repository repository) { + private Optional findExistingRepositoryPath(String repositoryId) { return db.values().stream() - .filter(repoPath -> repoPath.getId().equals(repository.getId())) + .filter(repoPath -> repoPath.getId().equals(repositoryId)) .findAny(); } } diff --git a/scm-dao-xml/src/test/java/sonia/scm/repository/xml/XmlRepositoryDAOTest.java b/scm-dao-xml/src/test/java/sonia/scm/repository/xml/XmlRepositoryDAOTest.java index 25a4566ed1..145e7dfa8d 100644 --- a/scm-dao-xml/src/test/java/sonia/scm/repository/xml/XmlRepositoryDAOTest.java +++ b/scm-dao-xml/src/test/java/sonia/scm/repository/xml/XmlRepositoryDAOTest.java @@ -89,7 +89,7 @@ public class XmlRepositoryDAOTest { XmlRepositoryDAO dao = new XmlRepositoryDAO(storeFactory, new InitialRepositoryLocationResolver(context), fileSystem, context); - Path path = dao.getPath(existingRepository); + Path path = dao.getPath(existingRepository.getId()); assertThat(path.toString()).isEqualTo(context.getBaseDirectory().getPath() + "/path"); } @@ -102,7 +102,7 @@ public class XmlRepositoryDAOTest { XmlRepositoryDAO dao = new XmlRepositoryDAO(storeFactory, new InitialRepositoryLocationResolver(context), fileSystem, context); - Path path = dao.getPath(existingRepository); + Path path = dao.getPath(existingRepository.getId()); assertThat(path.toString()).isEqualTo("/tmp/path"); } diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/GitGcTask.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/GitGcTask.java index 248eae92d1..9a30cc9f3e 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/GitGcTask.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/GitGcTask.java @@ -121,7 +121,7 @@ public class GitGcTask implements Runnable { } private void gc(Repository repository){ - File file = repositoryHandler.getDirectory(repository); + File file = repositoryHandler.getDirectory(repository.getId()); Git git = null; try { git = open(file); diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/AbstractGitIncomingOutgoingCommand.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/AbstractGitIncomingOutgoingCommand.java index 3cf72166ea..348203af92 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/AbstractGitIncomingOutgoingCommand.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/AbstractGitIncomingOutgoingCommand.java @@ -119,7 +119,7 @@ public abstract class AbstractGitIncomingOutgoingCommand Git git = Git.wrap(open()); - GitUtil.fetch(git, handler.getDirectory(remoteRepository), remoteRepository); + GitUtil.fetch(git, handler.getDirectory(remoteRepository.getId()), remoteRepository); ObjectId localId = getDefaultBranch(git.getRepository()); ObjectId remoteId = null; diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/AbstractGitPushOrPullCommand.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/AbstractGitPushOrPullCommand.java index e4e37d6fed..a9b9e25aca 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/AbstractGitPushOrPullCommand.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/AbstractGitPushOrPullCommand.java @@ -196,7 +196,7 @@ public abstract class AbstractGitPushOrPullCommand extends AbstractGitCommand */ protected String getRemoteUrl(sonia.scm.repository.Repository repository) { - return getRemoteUrl(handler.getDirectory(repository)); + return getRemoteUrl(handler.getDirectory(repository.getId())); } //~--- methods -------------------------------------------------------------- diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitPullCommand.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitPullCommand.java index 7a829355bf..8810c15c58 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitPullCommand.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitPullCommand.java @@ -196,12 +196,12 @@ public class GitPullCommand extends AbstractGitPushOrPullCommand private PullResponse pullFromScmRepository(Repository sourceRepository) throws IOException { - File sourceDirectory = handler.getDirectory(sourceRepository); + File sourceDirectory = handler.getDirectory(sourceRepository.getId()); Preconditions.checkArgument(sourceDirectory.exists(), "source repository directory does not exists"); - File targetDirectory = handler.getDirectory(repository); + File targetDirectory = handler.getDirectory(repository.getId()); Preconditions.checkArgument(sourceDirectory.exists(), "target repository directory does not exists"); diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitRepositoryServiceProvider.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitRepositoryServiceProvider.java index bda0d87b21..ae1af333bc 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitRepositoryServiceProvider.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitRepositoryServiceProvider.java @@ -73,7 +73,7 @@ public class GitRepositoryServiceProvider extends RepositoryServiceProvider public GitRepositoryServiceProvider(GitRepositoryHandler handler, Repository repository) { this.handler = handler; this.repository = repository; - this.context = new GitContext(handler.getDirectory(repository), repository); + this.context = new GitContext(handler.getDirectory(repository.getId()), repository); } //~--- methods -------------------------------------------------------------- diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/GitRepositoryResolver.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/GitRepositoryResolver.java index eb57ac8ff8..a2114a1b6a 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/GitRepositoryResolver.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/GitRepositoryResolver.java @@ -106,7 +106,7 @@ public class GitRepositoryResolver implements RepositoryResolver Date: Wed, 28 Nov 2018 19:49:55 +0100 Subject: [PATCH 13/14] re implement XmlRepositoryDAO --- pom.xml | 24 ++ .../java/sonia/scm/BasicContextProvider.java | 20 + .../java/sonia/scm/SCMContextProvider.java | 12 + .../AbstractSimpleRepositoryHandler.java | 2 +- .../InitialRepositoryLocationResolver.java | 44 +- .../RepositoryLocationResolver.java | 29 +- .../sonia/scm/xml/IndentXMLStreamWriter.java | 4 +- .../sonia/scm/BasicContextProviderTest.java | 44 ++ ...InitialRepositoryLocationResolverTest.java | 39 +- .../RepositoryLocationResolverTest.java | 65 +++ .../scm/xml/IndentXMLStreamWriterTest.java | 2 +- .../scm/repository/xml/MetadataStore.java | 50 +++ .../scm/repository/xml/PathDatabase.java | 145 +++++++ .../scm/repository/xml/XmlRepositoryDAO.java | 253 +++++++---- .../store/JAXBConfigurationEntryStore.java | 120 +----- .../main/java/sonia/scm/xml/XmlStreams.java | 71 ++++ .../repository/xml/XmlRepositoryDAOTest.java | 394 ++++++++++++++---- .../repository/GitRepositoryHandlerTest.java | 8 +- .../repository/HgRepositoryHandlerTest.java | 14 +- .../java/sonia/scm/repository/HgTestUtil.java | 2 +- .../repository/TempSCMContextProvider.java | 6 + .../repository/SvnRepositoryHandlerTest.java | 11 +- .../scm/repository/RepositoryTestData.java | 4 + .../SimpleRepositoryHandlerTestBase.java | 14 +- .../main/java/sonia/scm/util/MockUtil.java | 5 + .../DefaultRepositoryManagerTest.java | 6 +- 26 files changed, 1019 insertions(+), 369 deletions(-) create mode 100644 scm-core/src/test/java/sonia/scm/BasicContextProviderTest.java create mode 100644 scm-core/src/test/java/sonia/scm/repository/RepositoryLocationResolverTest.java create mode 100644 scm-dao-xml/src/main/java/sonia/scm/repository/xml/MetadataStore.java create mode 100644 scm-dao-xml/src/main/java/sonia/scm/repository/xml/PathDatabase.java create mode 100644 scm-dao-xml/src/main/java/sonia/scm/xml/XmlStreams.java diff --git a/pom.xml b/pom.xml index e480d1527b..f8bb7c5727 100644 --- a/pom.xml +++ b/pom.xml @@ -142,6 +142,11 @@ junit-vintage-engine + + org.junit-pioneer + junit-pioneer + + org.hamcrest hamcrest-core @@ -159,6 +164,11 @@ mockito-core + + org.mockito + mockito-junit-jupiter + + org.assertj assertj-core @@ -325,6 +335,13 @@ test + + org.junit-pioneer + junit-pioneer + 0.3.0 + test + + org.hamcrest hamcrest-core @@ -346,6 +363,13 @@ test + + org.mockito + mockito-junit-jupiter + ${mockito.version} + test + + org.assertj assertj-core diff --git a/scm-core/src/main/java/sonia/scm/BasicContextProvider.java b/scm-core/src/main/java/sonia/scm/BasicContextProvider.java index f6507fc453..6954c03832 100644 --- a/scm-core/src/main/java/sonia/scm/BasicContextProvider.java +++ b/scm-core/src/main/java/sonia/scm/BasicContextProvider.java @@ -35,6 +35,7 @@ package sonia.scm; //~--- non-JDK imports -------------------------------------------------------- +import com.google.common.annotations.VisibleForTesting; import sonia.scm.util.Util; //~--- JDK imports ------------------------------------------------------------ @@ -43,6 +44,7 @@ import java.io.File; import java.io.IOException; import java.io.InputStream; +import java.nio.file.Path; import java.util.Locale; import java.util.Properties; @@ -105,8 +107,26 @@ public class BasicContextProvider implements SCMContextProvider } } + @VisibleForTesting + BasicContextProvider(File baseDirectory, String version, Stage stage) { + this.baseDirectory = baseDirectory; + this.version = version; + this.stage = stage; + } + //~--- methods -------------------------------------------------------------- + + @Override + public Path resolve(Path path) { + if (path.isAbsolute()) { + return path; + } + + return baseDirectory.toPath().resolve(path); + } + + /** * {@inheritDoc} */ diff --git a/scm-core/src/main/java/sonia/scm/SCMContextProvider.java b/scm-core/src/main/java/sonia/scm/SCMContextProvider.java index 18328403fe..93918770c8 100644 --- a/scm-core/src/main/java/sonia/scm/SCMContextProvider.java +++ b/scm-core/src/main/java/sonia/scm/SCMContextProvider.java @@ -37,6 +37,7 @@ package sonia.scm; import java.io.Closeable; import java.io.File; +import java.nio.file.Path; /** * The main class for retrieving the home and the version of the SCM-Manager. @@ -65,6 +66,17 @@ public interface SCMContextProvider extends Closeable */ public File getBaseDirectory(); + /** + * Resolves the given path against the base directory. + * + * @param path path to resolve + * + * @return absolute resolved path + * + * @since 2.0.0 + */ + Path resolve(Path path); + /** * Returns the current stage of SCM-Manager. * diff --git a/scm-core/src/main/java/sonia/scm/repository/AbstractSimpleRepositoryHandler.java b/scm-core/src/main/java/sonia/scm/repository/AbstractSimpleRepositoryHandler.java index 17e5d3e24f..9941a4253b 100644 --- a/scm-core/src/main/java/sonia/scm/repository/AbstractSimpleRepositoryHandler.java +++ b/scm-core/src/main/java/sonia/scm/repository/AbstractSimpleRepositoryHandler.java @@ -168,6 +168,6 @@ public abstract class AbstractSimpleRepositoryHandler) invocationOnMock -> invocationOnMock.getArgument(0)); + } + + private RepositoryLocationResolver createResolver(RepositoryDAO pathBasedRepositoryDAO) { + return new RepositoryLocationResolver(contextProvider, pathBasedRepositoryDAO, initialRepositoryLocationResolver); + } + + @Test + void shouldReturnPathFromDao() { + Path repositoryPath = Paths.get("repos", "42"); + when(pathBasedRepositoryDAO.getPath("42")).thenReturn(repositoryPath); + + RepositoryLocationResolver resolver = createResolver(pathBasedRepositoryDAO); + Path path = resolver.getPath("42"); + + assertThat(path).isSameAs(repositoryPath); + } + + @Test + void shouldReturnInitialPathIfDaoIsNotPathBased() { + Path repositoryPath = Paths.get("r", "42"); + when(initialRepositoryLocationResolver.getPath("42")).thenReturn(repositoryPath); + + RepositoryLocationResolver resolver = createResolver(repositoryDAO); + Path path = resolver.getPath("42"); + + assertThat(path).isSameAs(repositoryPath); + } + +} diff --git a/scm-core/src/test/java/sonia/scm/xml/IndentXMLStreamWriterTest.java b/scm-core/src/test/java/sonia/scm/xml/IndentXMLStreamWriterTest.java index ecfdd06a0f..16c4278793 100644 --- a/scm-core/src/test/java/sonia/scm/xml/IndentXMLStreamWriterTest.java +++ b/scm-core/src/test/java/sonia/scm/xml/IndentXMLStreamWriterTest.java @@ -89,7 +89,7 @@ public class IndentXMLStreamWriterTest StringBuilder buffer = new StringBuilder(""); buffer.append(IndentXMLStreamWriter.LINE_SEPARATOR); buffer.append("").append(IndentXMLStreamWriter.LINE_SEPARATOR); - buffer.append(" Hello"); + buffer.append(" Hello"); buffer.append(IndentXMLStreamWriter.LINE_SEPARATOR); buffer.append("").append(IndentXMLStreamWriter.LINE_SEPARATOR); 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 new file mode 100644 index 0000000000..1f5f0e81b6 --- /dev/null +++ b/scm-dao-xml/src/main/java/sonia/scm/repository/xml/MetadataStore.java @@ -0,0 +1,50 @@ +package sonia.scm.repository.xml; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import sonia.scm.ContextEntry; +import sonia.scm.repository.InternalRepositoryException; +import sonia.scm.repository.Repository; + +import javax.xml.bind.JAXBContext; +import javax.xml.bind.JAXBException; +import javax.xml.bind.Marshaller; +import java.nio.file.Path; + +class MetadataStore { + + private static final Logger LOG = LoggerFactory.getLogger(MetadataStore.class); + + private final JAXBContext jaxbContext; + + MetadataStore() { + try { + jaxbContext = JAXBContext.newInstance(Repository.class); + } catch (JAXBException ex) { + throw new IllegalStateException("failed to create jaxb context for repository", ex); + } + } + + Repository read(Path path) { + LOG.trace("read repository metadata from {}", path); + try { + return (Repository) jaxbContext.createUnmarshaller().unmarshal(path.toFile()); + } catch (JAXBException ex) { + throw new InternalRepositoryException( + ContextEntry.ContextBuilder.entity(Path.class, path.toString()).build(), "failed read repository metadata", ex + ); + } + } + + void write(Path path, Repository repository) { + LOG.trace("write repository metadata of {} to {}", repository.getNamespaceAndName(), path); + try { + Marshaller marshaller = jaxbContext.createMarshaller(); + marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE); + marshaller.marshal(repository, path.toFile()); + } 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 new file mode 100644 index 0000000000..bddcdec570 --- /dev/null +++ b/scm-dao-xml/src/main/java/sonia/scm/repository/xml/PathDatabase.java @@ -0,0 +1,145 @@ +package sonia.scm.repository.xml; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import sonia.scm.ContextEntry; +import sonia.scm.repository.InternalRepositoryException; +import sonia.scm.xml.IndentXMLStreamWriter; +import sonia.scm.xml.XmlStreams; + +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.XMLStreamReader; +import javax.xml.stream.XMLStreamWriter; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Map; + +class PathDatabase { + + private static final Logger LOG = LoggerFactory.getLogger(PathDatabase.class); + + private static final String ENCODING = "UTF-8"; + private static final String VERSION = "1.0"; + + private static final String ELEMENT_REPOSITORIES = "repositories"; + private static final String ATTRIBUTE_CREATION_TIME = "creation-time"; + private static final String ATTRIBUTE_LAST_MODIFIED = "last-modified"; + + private static final String ELEMENT_REPOSITORY = "repository"; + private static final String ATTRIBUTE_ID = "id"; + + private final Path storePath; + + PathDatabase(Path storePath){ + this.storePath = storePath; + } + + void write(Long creationTime, Long lastModified, Map pathDatabase) { + ensureParentDirectoryExists(); + LOG.trace("write repository path database to {}", storePath); + + try (IndentXMLStreamWriter writer = XmlStreams.createWriter(storePath)) { + writer.writeStartDocument(ENCODING, VERSION); + + 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 + ); + } + } + + private void ensureParentDirectoryExists() { + Path parent = storePath.getParent(); + if (!Files.exists(parent)) { + try { + Files.createDirectories(parent); + } catch (IOException ex) { + throw new InternalRepositoryException( + ContextEntry.ContextBuilder.entity(Path.class, parent.toString()).build(), + "failed to create parent directory", + ex + ); + } + } + } + + private void writeRepositoriesStart(XMLStreamWriter writer, Long creationTime, Long lastModified) throws XMLStreamException { + writer.writeStartElement(ELEMENT_REPOSITORIES); + writer.writeAttribute(ATTRIBUTE_CREATION_TIME, String.valueOf(creationTime)); + writer.writeAttribute(ATTRIBUTE_LAST_MODIFIED, String.valueOf(lastModified)); + } + + private void writeRepository(XMLStreamWriter writer, String id, Path value) throws XMLStreamException { + writer.writeStartElement(ELEMENT_REPOSITORY); + writer.writeAttribute(ATTRIBUTE_ID, id); + writer.writeCharacters(value.toString()); + writer.writeEndElement(); + } + + void read(OnRepositories onRepositories, OnRepository onRepository) { + LOG.trace("read repository path database from {}", storePath); + XMLStreamReader reader = null; + try { + reader = XmlStreams.createReader(storePath); + + while (reader.hasNext()) { + int eventType = reader.next(); + + if (eventType == XMLStreamReader.START_ELEMENT) { + String element = reader.getLocalName(); + if (ELEMENT_REPOSITORIES.equals(element)) { + readRepositories(reader, onRepositories); + } else if (ELEMENT_REPOSITORY.equals(element)) { + readRepository(reader, onRepository); + } + } + } + } catch (XMLStreamException | IOException ex) { + throw new InternalRepositoryException( + ContextEntry.ContextBuilder.entity(Path.class, storePath.toString()).build(), + "failed to read repository path database", + ex + ); + } finally { + XmlStreams.close(reader); + } + } + + private void readRepository(XMLStreamReader reader, OnRepository onRepository) throws XMLStreamException { + String id = reader.getAttributeValue(null, ATTRIBUTE_ID); + Path path = Paths.get(reader.getElementText()); + onRepository.handle(id, path); + } + + private void readRepositories(XMLStreamReader reader, OnRepositories onRepositories) { + String creationTime = reader.getAttributeValue(null, ATTRIBUTE_CREATION_TIME); + String lastModified = reader.getAttributeValue(null, ATTRIBUTE_LAST_MODIFIED); + onRepositories.handle(Long.parseLong(creationTime), Long.parseLong(lastModified)); + } + + @FunctionalInterface + interface OnRepositories { + + void handle(Long creationTime, Long lastModified); + + } + + @FunctionalInterface + interface OnRepository { + + void handle(String id, Path path); + + } + +} diff --git a/scm-dao-xml/src/main/java/sonia/scm/repository/xml/XmlRepositoryDAO.java b/scm-dao-xml/src/main/java/sonia/scm/repository/xml/XmlRepositoryDAO.java index c83de49525..de51ebdef7 100644 --- a/scm-dao-xml/src/main/java/sonia/scm/repository/xml/XmlRepositoryDAO.java +++ b/scm-dao-xml/src/main/java/sonia/scm/repository/xml/XmlRepositoryDAO.java @@ -33,156 +33,229 @@ package sonia.scm.repository.xml; //~--- non-JDK imports -------------------------------------------------------- -import com.google.inject.Inject; +import com.google.common.annotations.VisibleForTesting; +import com.google.common.collect.ImmutableList; import com.google.inject.Singleton; -import sonia.scm.ContextEntry; import sonia.scm.SCMContextProvider; import sonia.scm.io.FileSystem; import sonia.scm.repository.InitialRepositoryLocationResolver; -import sonia.scm.repository.InitialRepositoryLocationResolver.InitialRepositoryLocation; import sonia.scm.repository.InternalRepositoryException; import sonia.scm.repository.NamespaceAndName; import sonia.scm.repository.PathBasedRepositoryDAO; import sonia.scm.repository.Repository; -import sonia.scm.store.ConfigurationStoreFactory; -import sonia.scm.xml.AbstractXmlDAO; +import sonia.scm.store.StoreConstants; +import javax.inject.Inject; import java.io.IOException; +import java.nio.file.Files; import java.nio.file.Path; +import java.time.Clock; import java.util.Collection; -import java.util.Optional; +import java.util.LinkedHashMap; +import java.util.Map; /** * @author Sebastian Sdorra */ @Singleton -public class XmlRepositoryDAO - extends AbstractXmlDAO - implements PathBasedRepositoryDAO { +public class XmlRepositoryDAO implements PathBasedRepositoryDAO { - public static final String STORE_NAME = "repositories"; + private static final String STORE_NAME = "repositories"; + + private final PathDatabase pathDatabase; + private final MetadataStore metadataStore = new MetadataStore(); - private InitialRepositoryLocationResolver initialRepositoryLocationResolver; - private final FileSystem fileSystem; private final SCMContextProvider context; + private final InitialRepositoryLocationResolver locationResolver; + private final FileSystem fileSystem; - //~--- constructors --------------------------------------------------------- + @VisibleForTesting + Clock clock = Clock.systemUTC(); + + private Long creationTime; + private Long lastModified; + + private Map pathById; + private Map byId; + private Map byNamespaceAndName; - /** - * Constructs ... - * @param storeFactory - * @param fileSystem - * @param context - */ @Inject - public XmlRepositoryDAO(ConfigurationStoreFactory storeFactory, InitialRepositoryLocationResolver initialRepositoryLocationResolver, FileSystem fileSystem, SCMContextProvider context) { - super(storeFactory.getStore(XmlRepositoryDatabase.class, STORE_NAME)); - this.initialRepositoryLocationResolver = initialRepositoryLocationResolver; - this.fileSystem = fileSystem; + public XmlRepositoryDAO(SCMContextProvider context, InitialRepositoryLocationResolver locationResolver, FileSystem fileSystem) { this.context = context; + this.locationResolver = locationResolver; + this.fileSystem = fileSystem; + + this.creationTime = clock.millis(); + + this.pathById = new LinkedHashMap<>(); + this.byId = new LinkedHashMap<>(); + this.byNamespaceAndName = new LinkedHashMap<>(); + + pathDatabase = new PathDatabase(createStorePath()); + read(); } - //~--- methods -------------------------------------------------------------- + private void read() { + Path storePath = createStorePath(); - @Override - public boolean contains(NamespaceAndName namespaceAndName) { - return db.contains(namespaceAndName); + if (!Files.exists(storePath)) { + return; + } + + pathDatabase.read(this::loadDates, this::loadRepository); } - //~--- get methods ---------------------------------------------------------- - - @Override - public Repository get(NamespaceAndName namespaceAndName) { - return db.get(namespaceAndName); + private void loadDates(Long creationTime, Long lastModified) { + this.creationTime = creationTime; + this.lastModified = lastModified; } - //~--- methods -------------------------------------------------------------- + private void loadRepository(String id, Path repositoryPath) { + Path metadataPath = createMetadataPath(context.resolve(repositoryPath)); + Repository repository = metadataStore.read(metadataPath); + + byId.put(id, repository); + byNamespaceAndName.put(repository.getNamespaceAndName(), repository); + pathById.put(id, repositoryPath); + } + + @VisibleForTesting + Path createStorePath() { + return context.getBaseDirectory() + .toPath() + .resolve(StoreConstants.CONFIG_DIRECTORY_NAME) + .resolve(STORE_NAME.concat(StoreConstants.FILE_EXTENSION)); + } + + + @VisibleForTesting + Path createMetadataPath(Path repositoryPath) { + return repositoryPath.resolve(StoreConstants.REPOSITORY_METADATA.concat(StoreConstants.FILE_EXTENSION)); + } @Override - public void modify(Repository repository) { - RepositoryPath repositoryPath = findExistingRepositoryPath(repository.getId()).orElseThrow(() -> new InternalRepositoryException(repository, "path object for repository not found")); - repositoryPath.setRepository(repository); - repositoryPath.setToBeSynchronized(true); - storeDB(); + public String getType() { + return "xml"; + } + + @Override + public Long getCreationTime() { + return creationTime; + } + + @Override + public Long getLastModified() { + return lastModified; } @Override public void add(Repository repository) { - InitialRepositoryLocation initialLocation = initialRepositoryLocationResolver.getRelativeRepositoryPath(repository.getId()); + Repository clone = repository.clone(); + + Path repositoryPath = locationResolver.getPath(repository.getId()); + Path resolvedPath = context.resolve(repositoryPath); + try { - fileSystem.create(initialLocation.getAbsolutePath()); + fileSystem.create(resolvedPath.toFile()); + + Path metadataPath = createMetadataPath(resolvedPath); + metadataStore.write(metadataPath, repository); + + synchronized (this) { + pathById.put(repository.getId(), repositoryPath); + + byId.put(repository.getId(), clone); + byNamespaceAndName.put(repository.getNamespaceAndName(), clone); + + writePathDatabase(); + } + } catch (IOException e) { - throw new InternalRepositoryException(repository, "could not create directory for repository data: " + initialLocation.getAbsolutePath(), e); - } - RepositoryPath repositoryPath = new RepositoryPath(initialLocation.getRelativePath(), repository.getId(), repository.clone()); - repositoryPath.setToBeSynchronized(true); - synchronized (store) { - db.add(repositoryPath); - storeDB(); + throw new InternalRepositoryException(repository, "failed to create filesystem", e); } } + private void writePathDatabase() { + lastModified = clock.millis(); + pathDatabase.write(creationTime, lastModified, pathById); + } + + @Override + public boolean contains(Repository repository) { + return byId.containsKey(repository.getId()); + } + + @Override + public boolean contains(NamespaceAndName namespaceAndName) { + return byNamespaceAndName.containsKey(namespaceAndName); + } + + @Override + public boolean contains(String id) { + return byId.containsKey(id); + } + + @Override + public Repository get(NamespaceAndName namespaceAndName) { + return byNamespaceAndName.get(namespaceAndName); + } + @Override public Repository get(String id) { - RepositoryPath repositoryPath = db.get(id); - if (repositoryPath != null) { - return repositoryPath.getRepository(); - } - return null; + return byId.get(id); } @Override public Collection getAll() { - return db.getRepositories(); + return ImmutableList.copyOf(byNamespaceAndName.values()); } - /** - * Method description - * - * @param repository - * @return - */ @Override - protected Repository clone(Repository repository) { - return repository.clone(); + public void modify(Repository repository) { + Repository clone = repository.clone(); + + synchronized (this) { + // remove old namespaceAndName from map, in case of rename + Repository prev = byId.put(clone.getId(), clone); + if (prev != null) { + byNamespaceAndName.remove(prev.getNamespaceAndName()); + } + byNamespaceAndName.put(clone.getNamespaceAndName(), clone); + + writePathDatabase(); + } + + Path repositoryPath = context.resolve(getPath(repository.getId())); + Path metadataPath = createMetadataPath(repositoryPath); + metadataStore.write(metadataPath, clone); } @Override public void delete(Repository repository) { - Path directory = getPath(repository.getId()); - super.delete(repository); - try { - fileSystem.destroy(directory.toFile()); - } catch (IOException e) { - throw new InternalRepositoryException(repository, "could not delete repository directory", e); - } - } + Path path; + synchronized (this) { + Repository prev = byId.remove(repository.getId()); + if (prev != null) { + byNamespaceAndName.remove(prev.getNamespaceAndName()); + } - /** - * Method description - * - * @return - */ - @Override - protected XmlRepositoryDatabase createNewDatabase() { - return new XmlRepositoryDatabase(); + path = pathById.remove(repository.getId()); + + writePathDatabase(); + } + + path = context.resolve(path); + + try { + fileSystem.destroy(path.toFile()); + } catch (IOException e) { + throw new InternalRepositoryException(repository, "failed to destroy filesystem", e); + } } @Override public Path getPath(String repositoryId) { - return context - .getBaseDirectory() - .toPath() - .resolve( - findExistingRepositoryPath(repositoryId) - .map(RepositoryPath::getPath) - .orElseThrow(() -> new InternalRepositoryException(ContextEntry.ContextBuilder.entity("repository", repositoryId), "could not find base directory for repository"))); - } - - private Optional findExistingRepositoryPath(String repositoryId) { - return db.values().stream() - .filter(repoPath -> repoPath.getId().equals(repositoryId)) - .findAny(); + return pathById.get(repositoryId); } } 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 6a9098b545..40cf03c8a8 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 @@ -35,32 +35,14 @@ package sonia.scm.store; //~--- non-JDK imports -------------------------------------------------------- -import com.google.common.base.Charsets; import com.google.common.base.Predicate; import com.google.common.collect.Collections2; import com.google.common.collect.Maps; - import org.slf4j.Logger; import org.slf4j.LoggerFactory; - import sonia.scm.security.KeyGenerator; import sonia.scm.xml.IndentXMLStreamWriter; - -//~--- JDK imports ------------------------------------------------------------ - -import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; -import java.io.InputStreamReader; -import java.io.OutputStreamWriter; -import java.io.Reader; -import java.io.Writer; - -import java.util.Collection; -import java.util.Collections; -import java.util.Map; -import java.util.Map.Entry; +import sonia.scm.xml.XmlStreams; import javax.xml.bind.JAXBContext; import javax.xml.bind.JAXBElement; @@ -68,11 +50,14 @@ import javax.xml.bind.JAXBException; import javax.xml.bind.Marshaller; import javax.xml.bind.Unmarshaller; import javax.xml.namespace.QName; -import javax.xml.stream.XMLInputFactory; -import javax.xml.stream.XMLOutputFactory; -import javax.xml.stream.XMLStreamException; import javax.xml.stream.XMLStreamReader; -import javax.xml.stream.XMLStreamWriter; +import java.io.File; +import java.util.Collection; +import java.util.Collections; +import java.util.Map; +import java.util.Map.Entry; + +//~--- JDK imports ------------------------------------------------------------ /** * @@ -255,74 +240,6 @@ public class JAXBConfigurationEntryStore implements ConfigurationEntryStore implements ConfigurationEntryStore implements ConfigurationEntryStore implements ConfigurationEntryStore implements ConfigurationEntryStore store; - @Mock - private XmlRepositoryDatabase db; @Mock private SCMContextProvider context; - @Rule - public TemporaryFolder temporaryFolder = new TemporaryFolder(); + @Mock + private InitialRepositoryLocationResolver locationResolver; - private final FileSystem fileSystem = new DefaultFileSystem(); + private FileSystem fileSystem = new DefaultFileSystem(); - @Before - public void init() throws IOException { - when(storeFactory.getStore(XmlRepositoryDatabase.class, STORE_NAME)).thenReturn(store); - when(store.get()).thenReturn(db); - when(context.getBaseDirectory()).thenReturn(temporaryFolder.newFolder()); + private XmlRepositoryDAO dao; + + private Path baseDirectory; + + private AtomicLong atomicClock; + + @BeforeEach + void createDAO(@TempDirectory.TempDir Path baseDirectory) { + this.baseDirectory = baseDirectory; + this.atomicClock = new AtomicLong(); + + when(locationResolver.getPath("42")).thenReturn(Paths.get("repos", "42")); + when(locationResolver.getPath("42+1")).thenReturn(Paths.get("repos", "puzzle")); + + when(context.getBaseDirectory()).thenReturn(baseDirectory.toFile()); + when(context.resolve(any(Path.class))).then(ic -> { + Path path = ic.getArgument(0); + return baseDirectory.resolve(path); + }); + + dao = createDAO(); + } + + private XmlRepositoryDAO createDAO() { + XmlRepositoryDAO dao = new XmlRepositoryDAO(context, locationResolver, fileSystem); + + Clock clock = mock(Clock.class); + when(clock.millis()).then(ic -> atomicClock.incrementAndGet()); + dao.clock = clock; + + return dao; } @Test - public void addShouldCreateNewRepositoryPathWithRelativePath() { - InitialRepositoryLocationResolver initialRepositoryLocationResolver = new InitialRepositoryLocationResolver(context); - XmlRepositoryDAO dao = new XmlRepositoryDAO(storeFactory, initialRepositoryLocationResolver, fileSystem, context); - - dao.add(new Repository("id", null, null, null)); - - verify(db).add(argThat(repositoryPath -> { - assertThat(repositoryPath.getId()).isEqualTo("id"); - assertThat(repositoryPath.getPath()).isEqualTo(InitialRepositoryLocationResolver.DEFAULT_REPOSITORY_PATH + "/id"); - return true; - })); - verify(store).set(db); + void shouldReturnXmlType() { + assertThat(dao.getType()).isEqualTo("xml"); } @Test - public void modifyShouldStoreChangedRepository() { - Repository oldRepository = new Repository("id", "old", null, null); - RepositoryPath repositoryPath = new RepositoryPath("/path", "id", oldRepository); - when(db.values()).thenReturn(asList(repositoryPath)); - - XmlRepositoryDAO dao = new XmlRepositoryDAO(storeFactory, new InitialRepositoryLocationResolver(context), fileSystem, context); - - Repository newRepository = new Repository("id", "new", null, null); - dao.modify(newRepository); - - assertThat(repositoryPath.getRepository()).isSameAs(newRepository); - verify(store).set(db); + void shouldReturnCreationTimeAfterCreation() { + long now = System.currentTimeMillis(); + assertThat(dao.getCreationTime()).isBetween(now - 200, now + 200); } @Test - public void shouldGetPathInBaseDirectoryForRelativePath() { - Repository existingRepository = new Repository("id", "old", null, null); - RepositoryPath repositoryPath = new RepositoryPath("path", "id", existingRepository); - when(db.values()).thenReturn(asList(repositoryPath)); - - XmlRepositoryDAO dao = new XmlRepositoryDAO(storeFactory, new InitialRepositoryLocationResolver(context), fileSystem, context); - - Path path = dao.getPath(existingRepository.getId()); - - assertThat(path.toString()).isEqualTo(context.getBaseDirectory().getPath() + "/path"); + void shouldNotReturnLastModifiedAfterCreation() { + assertThat(dao.getLastModified()).isNull(); } @Test - public void shouldGetPathInBaseDirectoryForAbsolutePath() { - Repository existingRepository = new Repository("id", "old", null, null); - RepositoryPath repositoryPath = new RepositoryPath("/tmp/path", "id", existingRepository); - when(db.values()).thenReturn(asList(repositoryPath)); + void shouldReturnTrueForEachContainsMethod() { + Repository heartOfGold = createHeartOfGold(); + dao.add(heartOfGold); - XmlRepositoryDAO dao = new XmlRepositoryDAO(storeFactory, new InitialRepositoryLocationResolver(context), fileSystem, context); + assertThat(dao.contains(heartOfGold)).isTrue(); + assertThat(dao.contains(heartOfGold.getId())).isTrue(); + assertThat(dao.contains(heartOfGold.getNamespaceAndName())).isTrue(); + } - Path path = dao.getPath(existingRepository.getId()); + private Repository createHeartOfGold() { + Repository heartOfGold = RepositoryTestData.createHeartOfGold(); + heartOfGold.setId("42"); + return heartOfGold; + } - assertThat(path.toString()).isEqualTo("/tmp/path"); + @Test + void shouldReturnFalseForEachContainsMethod() { + Repository heartOfGold = createHeartOfGold(); + + assertThat(dao.contains(heartOfGold)).isFalse(); + assertThat(dao.contains(heartOfGold.getId())).isFalse(); + assertThat(dao.contains(heartOfGold.getNamespaceAndName())).isFalse(); + } + + @Test + void shouldReturnNullForEachGetMethod() { + assertThat(dao.get("42")).isNull(); + assertThat(dao.get(new NamespaceAndName("hitchhiker","HeartOfGold"))).isNull(); + } + + @Test + void shouldReturnRepository() { + Repository heartOfGold = createHeartOfGold(); + dao.add(heartOfGold); + + assertThat(dao.get("42")).isEqualTo(heartOfGold); + assertThat(dao.get(new NamespaceAndName("hitchhiker","HeartOfGold"))).isEqualTo(heartOfGold); + } + + @Test + void shouldNotReturnTheSameInstance() { + Repository heartOfGold = createHeartOfGold(); + dao.add(heartOfGold); + + Repository repository = dao.get("42"); + assertThat(repository).isNotSameAs(heartOfGold); + } + + @Test + void shouldReturnAllRepositories() { + Repository heartOfGold = createHeartOfGold(); + dao.add(heartOfGold); + + Repository puzzle = createPuzzle(); + dao.add(puzzle); + + Collection repositories = dao.getAll(); + assertThat(repositories).containsExactlyInAnyOrder(heartOfGold, puzzle); + } + + private Repository createPuzzle() { + Repository puzzle = RepositoryTestData.create42Puzzle(); + puzzle.setId("42+1"); + return puzzle; + } + + @Test + void shouldModifyRepository() { + Repository heartOfGold = createHeartOfGold(); + heartOfGold.setDescription("HeartOfGold"); + dao.add(heartOfGold); + assertThat(dao.get("42").getDescription()).isEqualTo("HeartOfGold"); + + heartOfGold = createHeartOfGold(); + heartOfGold.setDescription("Heart of Gold"); + dao.modify(heartOfGold); + + assertThat(dao.get("42").getDescription()).isEqualTo("Heart of Gold"); + } + + @Test + void shouldRemoveRepository() { + Repository heartOfGold = createHeartOfGold(); + dao.add(heartOfGold); + assertThat(dao.contains("42")).isTrue(); + + dao.delete(heartOfGold); + assertThat(dao.contains("42")).isFalse(); + assertThat(dao.contains(new NamespaceAndName("hitchhiker", "HeartOfGold"))).isFalse(); + } + + @Test + void shouldUpdateLastModifiedAfterEachWriteOperation() { + Repository heartOfGold = createHeartOfGold(); + dao.add(heartOfGold); + + Long firstLastModified = dao.getLastModified(); + assertThat(firstLastModified).isNotNull(); + + Repository puzzle = createPuzzle(); + dao.add(puzzle); + + Long lastModifiedAdded = dao.getLastModified(); + assertThat(lastModifiedAdded).isGreaterThan(firstLastModified); + + heartOfGold.setDescription("Heart of Gold"); + dao.modify(heartOfGold); + + Long lastModifiedModified = dao.getLastModified(); + assertThat(lastModifiedModified).isGreaterThan(lastModifiedAdded); + + dao.delete(puzzle); + + Long lastModifiedRemoved = dao.getLastModified(); + assertThat(lastModifiedRemoved).isGreaterThan(lastModifiedModified); + } + + @Test + void shouldRenameTheRepository() { + Repository heartOfGold = createHeartOfGold(); + dao.add(heartOfGold); + + heartOfGold.setNamespace("hg2tg"); + heartOfGold.setName("hog"); + + dao.modify(heartOfGold); + + Repository repository = dao.get("42"); + assertThat(repository.getNamespace()).isEqualTo("hg2tg"); + assertThat(repository.getName()).isEqualTo("hog"); + + assertThat(dao.contains(new NamespaceAndName("hg2tg", "hog"))).isTrue(); + assertThat(dao.contains(new NamespaceAndName("hitchhiker", "HeartOfGold"))).isFalse(); + } + + @Test + void shouldDeleteRepositoryEvenWithChangedNamespace() { + Repository heartOfGold = createHeartOfGold(); + dao.add(heartOfGold); + + heartOfGold.setNamespace("hg2tg"); + heartOfGold.setName("hog"); + + dao.delete(heartOfGold); + + assertThat(dao.contains(new NamespaceAndName("hitchhiker", "HeartOfGold"))).isFalse(); + } + + @Test + void shouldReturnThePathForTheRepository() { + Path repositoryPath = Paths.get("r", "42"); + when(locationResolver.getPath("42")).thenReturn(repositoryPath); + + Repository heartOfGold = createHeartOfGold(); + dao.add(heartOfGold); + + Path path = dao.getPath("42"); + assertThat(path).isEqualTo(repositoryPath); + } + + @Test + void shouldCreateTheDirectoryForTheRepository() { + Path repositoryPath = Paths.get("r", "42"); + when(locationResolver.getPath("42")).thenReturn(repositoryPath); + + Repository heartOfGold = createHeartOfGold(); + dao.add(heartOfGold); + + Path path = getAbsolutePathFromDao("42"); + assertThat(path).isDirectory(); + } + + @Test + void shouldRemoveRepositoryDirectoryAfterDeletion() { + Repository heartOfGold = createHeartOfGold(); + dao.add(heartOfGold); + + Path path = getAbsolutePathFromDao(heartOfGold.getId()); + assertThat(path).isDirectory(); + + dao.delete(heartOfGold); + assertThat(path).doesNotExist(); + } + + private Path getAbsolutePathFromDao(String id) { + return context.resolve(dao.getPath(id)); + } + + @Test + void shouldCreateRepositoryPathDatabase() throws IOException { + Repository heartOfGold = createHeartOfGold(); + dao.add(heartOfGold); + + Path storePath = dao.createStorePath(); + assertThat(storePath).isRegularFile(); + + String content = content(storePath); + + assertThat(content).contains(heartOfGold.getId()); + assertThat(content).contains(dao.getPath(heartOfGold.getId()).toString()); + } + + private String content(Path storePath) throws IOException { + return new String(Files.readAllBytes(storePath), Charsets.UTF_8); + } + + @Test + void shouldStoreRepositoryMetadataAfterAdd() throws IOException { + Repository heartOfGold = createHeartOfGold(); + dao.add(heartOfGold); + + Path repositoryDirectory = getAbsolutePathFromDao(heartOfGold.getId()); + Path metadataPath = dao.createMetadataPath(repositoryDirectory); + + assertThat(metadataPath).isRegularFile(); + + String content = content(metadataPath); + assertThat(content).contains(heartOfGold.getName()); + assertThat(content).contains(heartOfGold.getNamespace()); + assertThat(content).contains(heartOfGold.getDescription()); + } + + @Test + void shouldUpdateRepositoryMetadataAfterModify() throws IOException { + Repository heartOfGold = createHeartOfGold(); + dao.add(heartOfGold); + + heartOfGold.setDescription("Awesome Spaceship"); + dao.modify(heartOfGold); + + Path repositoryDirectory = getAbsolutePathFromDao(heartOfGold.getId()); + Path metadataPath = dao.createMetadataPath(repositoryDirectory); + + String content = content(metadataPath); + assertThat(content).contains("Awesome Spaceship"); + } + + @Test + void shouldReadPathDatabaseAndMetadataOfRepositories() { + Repository heartOfGold = createHeartOfGold(); + dao.add(heartOfGold); + + // reload data + dao = createDAO(); + + heartOfGold = dao.get("42"); + assertThat(heartOfGold.getName()).isEqualTo("HeartOfGold"); + + Path path = getAbsolutePathFromDao(heartOfGold.getId()); + assertThat(path).isDirectory(); + } + + @Test + void shouldReadCreationTimeAndLastModifedDateFromDatabase() { + Repository heartOfGold = createHeartOfGold(); + dao.add(heartOfGold); + + Long creationTime = dao.getCreationTime(); + Long lastModified = dao.getLastModified(); + + // reload data + dao = createDAO(); + + assertThat(dao.getCreationTime()).isEqualTo(creationTime); + assertThat(dao.getLastModified()).isEqualTo(lastModified); } } diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/GitRepositoryHandlerTest.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/GitRepositoryHandlerTest.java index bfff7d3bf1..d3eca1d6e0 100644 --- a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/GitRepositoryHandlerTest.java +++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/GitRepositoryHandlerTest.java @@ -62,8 +62,6 @@ public class GitRepositoryHandlerTest extends SimpleRepositoryHandlerTestBase { @Mock private GitWorkdirFactory gitWorkdirFactory; - RepositoryLocationResolver repositoryLocationResolver; - @Override protected void checkDirectory(File directory) { @@ -86,10 +84,10 @@ public class GitRepositoryHandlerTest extends SimpleRepositoryHandlerTestBase { @Override protected RepositoryHandler createRepositoryHandler(ConfigurationStoreFactory factory, + RepositoryLocationResolver locationResolver, File directory) { - repositoryLocationResolver = new RepositoryLocationResolver(repoDao, new InitialRepositoryLocationResolver(contextProvider)); GitRepositoryHandler repositoryHandler = new GitRepositoryHandler(factory, - scheduler, repositoryLocationResolver, gitWorkdirFactory); + scheduler, locationResolver, gitWorkdirFactory); repositoryHandler.init(contextProvider); GitConfig config = new GitConfig(); @@ -103,7 +101,7 @@ public class GitRepositoryHandlerTest extends SimpleRepositoryHandlerTestBase { @Test public void getDirectory() { GitRepositoryHandler repositoryHandler = new GitRepositoryHandler(factory, - scheduler, repositoryLocationResolver, gitWorkdirFactory); + scheduler, locationResolver, gitWorkdirFactory); GitConfig config = new GitConfig(); config.setDisabled(false); config.setGcExpression("gc exp"); diff --git a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/HgRepositoryHandlerTest.java b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/HgRepositoryHandlerTest.java index b8b9646a90..7a13c06eb2 100644 --- a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/HgRepositoryHandlerTest.java +++ b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/HgRepositoryHandlerTest.java @@ -50,7 +50,7 @@ import static org.junit.Assert.assertTrue; /** * @author Sebastian Sdorra */ -@RunWith(MockitoJUnitRunner.class) +@RunWith(MockitoJUnitRunner.Silent.class) public class HgRepositoryHandlerTest extends SimpleRepositoryHandlerTestBase { @Mock @@ -59,8 +59,6 @@ public class HgRepositoryHandlerTest extends SimpleRepositoryHandlerTestBase { @Mock private com.google.inject.Provider provider; - private RepositoryLocationResolver repositoryLocationResolver; - @Override protected void checkDirectory(File directory) { File hgDirectory = new File(directory, ".hg"); @@ -70,11 +68,8 @@ public class HgRepositoryHandlerTest extends SimpleRepositoryHandlerTestBase { } @Override - protected RepositoryHandler createRepositoryHandler(ConfigurationStoreFactory factory, - File directory) { - repositoryLocationResolver = new RepositoryLocationResolver(repoDao, new InitialRepositoryLocationResolver(contextProvider)); - HgRepositoryHandler handler = new HgRepositoryHandler(factory, - new HgContextProvider(), repositoryLocationResolver); + protected RepositoryHandler createRepositoryHandler(ConfigurationStoreFactory factory, RepositoryLocationResolver locationResolver, File directory) { + HgRepositoryHandler handler = new HgRepositoryHandler(factory, new HgContextProvider(), locationResolver); handler.init(contextProvider); HgTestUtil.checkForSkip(handler); @@ -84,8 +79,7 @@ public class HgRepositoryHandlerTest extends SimpleRepositoryHandlerTestBase { @Test public void getDirectory() { - HgRepositoryHandler repositoryHandler = new HgRepositoryHandler(factory, - provider, repositoryLocationResolver); + HgRepositoryHandler repositoryHandler = new HgRepositoryHandler(factory, provider, locationResolver); HgConfig hgConfig = new HgConfig(); hgConfig.setHgBinary("hg"); diff --git a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/HgTestUtil.java b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/HgTestUtil.java index d2344816ef..68f7e18a76 100644 --- a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/HgTestUtil.java +++ b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/HgTestUtil.java @@ -103,7 +103,7 @@ public final class HgTestUtil PathBasedRepositoryDAO repoDao = mock(PathBasedRepositoryDAO.class); - RepositoryLocationResolver repositoryLocationResolver = new RepositoryLocationResolver(repoDao, new InitialRepositoryLocationResolver(context)); + RepositoryLocationResolver repositoryLocationResolver = new RepositoryLocationResolver(context, repoDao, new InitialRepositoryLocationResolver()); HgRepositoryHandler handler = new HgRepositoryHandler(new InMemoryConfigurationStoreFactory(), new HgContextProvider(), repositoryLocationResolver); Path repoDir = directory.toPath(); diff --git a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/TempSCMContextProvider.java b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/TempSCMContextProvider.java index bc6794e5a5..0a0064ad44 100644 --- a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/TempSCMContextProvider.java +++ b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/TempSCMContextProvider.java @@ -41,6 +41,7 @@ import sonia.scm.Stage; import java.io.File; import java.io.IOException; +import java.nio.file.Path; /** * @@ -136,6 +137,11 @@ public class TempSCMContextProvider implements SCMContextProvider this.baseDirectory = baseDirectory; } + @Override + public Path resolve(Path path) { + return baseDirectory.toPath().resolve(path); + } + //~--- fields --------------------------------------------------------------- /** Field description */ diff --git a/scm-plugins/scm-svn-plugin/src/test/java/sonia/scm/repository/SvnRepositoryHandlerTest.java b/scm-plugins/scm-svn-plugin/src/test/java/sonia/scm/repository/SvnRepositoryHandlerTest.java index bfc8bbc428..7b11d1bb7f 100644 --- a/scm-plugins/scm-svn-plugin/src/test/java/sonia/scm/repository/SvnRepositoryHandlerTest.java +++ b/scm-plugins/scm-svn-plugin/src/test/java/sonia/scm/repository/SvnRepositoryHandlerTest.java @@ -67,15 +67,10 @@ public class SvnRepositoryHandlerTest extends SimpleRepositoryHandlerTestBase { @Mock private com.google.inject.Provider repositoryManagerProvider; - @Mock - private RepositoryDAO repositoryDAO; - private HookContextFactory hookContextFactory = new HookContextFactory(mock(PreProcessorUtil.class)); private HookEventFacade facade = new HookEventFacade(repositoryManagerProvider, hookContextFactory); - private RepositoryLocationResolver repositoryLocationResolver; - @Override protected void checkDirectory(File directory) { File format = new File(directory, "format"); @@ -91,9 +86,9 @@ public class SvnRepositoryHandlerTest extends SimpleRepositoryHandlerTestBase { @Override protected RepositoryHandler createRepositoryHandler(ConfigurationStoreFactory factory, + RepositoryLocationResolver locationResolver, File directory) { - repositoryLocationResolver = new RepositoryLocationResolver(repoDao, new InitialRepositoryLocationResolver(contextProvider)); - SvnRepositoryHandler handler = new SvnRepositoryHandler(factory, null, repositoryLocationResolver); + SvnRepositoryHandler handler = new SvnRepositoryHandler(factory, null, locationResolver); handler.init(contextProvider); @@ -109,7 +104,7 @@ public class SvnRepositoryHandlerTest extends SimpleRepositoryHandlerTestBase { public void getDirectory() { when(factory.getStore(any(), any())).thenReturn(store); SvnRepositoryHandler repositoryHandler = new SvnRepositoryHandler(factory, - facade, repositoryLocationResolver); + facade, locationResolver); SvnConfig svnConfig = new SvnConfig(); repositoryHandler.setConfig(svnConfig); diff --git a/scm-test/src/main/java/sonia/scm/repository/RepositoryTestData.java b/scm-test/src/main/java/sonia/scm/repository/RepositoryTestData.java index b81c39ca00..5dbd672b98 100644 --- a/scm-test/src/main/java/sonia/scm/repository/RepositoryTestData.java +++ b/scm-test/src/main/java/sonia/scm/repository/RepositoryTestData.java @@ -45,6 +45,7 @@ public final class RepositoryTestData { .type(type) .contact("douglas.adams@hitchhiker.com") .name("42Puzzle") + .namespace("hitchhiker") .description("The 42 Puzzle") .build(); } @@ -59,6 +60,7 @@ public final class RepositoryTestData { .type(type) .contact("zaphod.beeblebrox@hitchhiker.com") .name("happyVerticalPeopleTransporter") + .namespace("hitchhiker") .description("Happy Vertical People Transporter") .build(); } @@ -72,6 +74,7 @@ public final class RepositoryTestData { .type(type) .contact("zaphod.beeblebrox@hitchhiker.com") .name("HeartOfGold") + .namespace("hitchhiker") .description( "Heart of Gold is the first prototype ship to successfully utilise the revolutionary Infinite Improbability Drive") .build(); @@ -87,6 +90,7 @@ public final class RepositoryTestData { .type(type) .contact("douglas.adams@hitchhiker.com") .name("RestaurantAtTheEndOfTheUniverse") + .namespace("hitchhiker") .description("The Restaurant at the End of the Universe") .build(); } diff --git a/scm-test/src/main/java/sonia/scm/repository/SimpleRepositoryHandlerTestBase.java b/scm-test/src/main/java/sonia/scm/repository/SimpleRepositoryHandlerTestBase.java index 706117b2c7..37f7266984 100644 --- a/scm-test/src/main/java/sonia/scm/repository/SimpleRepositoryHandlerTestBase.java +++ b/scm-test/src/main/java/sonia/scm/repository/SimpleRepositoryHandlerTestBase.java @@ -45,6 +45,7 @@ import java.nio.file.Path; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -63,7 +64,7 @@ public abstract class SimpleRepositoryHandlerTestBase extends AbstractTestBase { protected abstract void checkDirectory(File directory); protected abstract RepositoryHandler createRepositoryHandler( - ConfigurationStoreFactory factory, File directory) throws IOException, RepositoryPathNotFoundException; + ConfigurationStoreFactory factory, RepositoryLocationResolver locationResolver, File directory) throws IOException, RepositoryPathNotFoundException; @Test public void testCreate() { @@ -75,7 +76,15 @@ public abstract class SimpleRepositoryHandlerTestBase extends AbstractTestBase { InMemoryConfigurationStoreFactory storeFactory = new InMemoryConfigurationStoreFactory(); baseDirectory = new File(contextProvider.getBaseDirectory(), "repositories"); IOUtil.mkdirs(baseDirectory); - handler = createRepositoryHandler(storeFactory, baseDirectory); + + locationResolver = mock(RepositoryLocationResolver.class); + + when(locationResolver.getPath(anyString())).then(ic -> { + String id = ic.getArgument(0); + return baseDirectory.toPath().resolve(id); + }); + + handler = createRepositoryHandler(storeFactory, locationResolver, baseDirectory); } @Override @@ -105,6 +114,7 @@ public abstract class SimpleRepositoryHandlerTestBase extends AbstractTestBase { } protected File baseDirectory; + protected RepositoryLocationResolver locationResolver; private RepositoryHandler handler; } diff --git a/scm-test/src/main/java/sonia/scm/util/MockUtil.java b/scm-test/src/main/java/sonia/scm/util/MockUtil.java index 756b2632be..76bf4ae24d 100644 --- a/scm-test/src/main/java/sonia/scm/util/MockUtil.java +++ b/scm-test/src/main/java/sonia/scm/util/MockUtil.java @@ -55,6 +55,7 @@ import static org.mockito.Mockito.*; import java.io.File; +import java.nio.file.Path; import java.util.Arrays; import java.util.List; @@ -213,6 +214,10 @@ public final class MockUtil SCMContextProvider provider = mock(SCMContextProvider.class); when(provider.getBaseDirectory()).thenReturn(directory); + when(provider.resolve(any(Path.class))).then(ic -> { + Path p = ic.getArgument(0); + return directory.toPath().resolve(p); + }); return provider; } diff --git a/scm-webapp/src/test/java/sonia/scm/repository/DefaultRepositoryManagerTest.java b/scm-webapp/src/test/java/sonia/scm/repository/DefaultRepositoryManagerTest.java index bb7c861d33..141a7f8527 100644 --- a/scm-webapp/src/test/java/sonia/scm/repository/DefaultRepositoryManagerTest.java +++ b/scm-webapp/src/test/java/sonia/scm/repository/DefaultRepositoryManagerTest.java @@ -434,9 +434,9 @@ public class DefaultRepositoryManagerTest extends ManagerTestBase { DefaultFileSystem fileSystem = new DefaultFileSystem(); Set handlerSet = new HashSet<>(); ConfigurationStoreFactory factory = new JAXBConfigurationStoreFactory(contextProvider); - InitialRepositoryLocationResolver initialRepositoryLocationResolver = new InitialRepositoryLocationResolver(contextProvider); - XmlRepositoryDAO repositoryDAO = new XmlRepositoryDAO(factory, initialRepositoryLocationResolver, fileSystem, contextProvider); - RepositoryLocationResolver repositoryLocationResolver = new RepositoryLocationResolver(repositoryDAO, initialRepositoryLocationResolver); + InitialRepositoryLocationResolver initialRepositoryLocationResolver = new InitialRepositoryLocationResolver(); + XmlRepositoryDAO repositoryDAO = new XmlRepositoryDAO(contextProvider, initialRepositoryLocationResolver, fileSystem); + RepositoryLocationResolver repositoryLocationResolver = new RepositoryLocationResolver(contextProvider, repositoryDAO, initialRepositoryLocationResolver); handlerSet.add(new DummyRepositoryHandler(factory, repositoryLocationResolver)); handlerSet.add(new DummyRepositoryHandler(factory, repositoryLocationResolver) { @Override From 09a55b9f4b18f3f4bddee80b43f8e8c7cba8336b Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Wed, 28 Nov 2018 19:58:03 +0100 Subject: [PATCH 14/14] remove unused files --- .../scm/repository/xml/RepositoryPath.java | 101 ---------- .../repository/xml/XmlRepositoryDatabase.java | 188 ------------------ .../scm/repository/xml/XmlRepositoryList.java | 123 ------------ .../xml/XmlRepositoryMapAdapter.java | 112 ----------- 4 files changed, 524 deletions(-) delete mode 100644 scm-dao-xml/src/main/java/sonia/scm/repository/xml/RepositoryPath.java delete mode 100644 scm-dao-xml/src/main/java/sonia/scm/repository/xml/XmlRepositoryDatabase.java delete mode 100644 scm-dao-xml/src/main/java/sonia/scm/repository/xml/XmlRepositoryList.java delete mode 100644 scm-dao-xml/src/main/java/sonia/scm/repository/xml/XmlRepositoryMapAdapter.java diff --git a/scm-dao-xml/src/main/java/sonia/scm/repository/xml/RepositoryPath.java b/scm-dao-xml/src/main/java/sonia/scm/repository/xml/RepositoryPath.java deleted file mode 100644 index db57b228f9..0000000000 --- a/scm-dao-xml/src/main/java/sonia/scm/repository/xml/RepositoryPath.java +++ /dev/null @@ -1,101 +0,0 @@ -package sonia.scm.repository.xml; - -import org.apache.commons.lang.StringUtils; -import sonia.scm.ModelObject; -import sonia.scm.repository.Repository; - -import javax.xml.bind.annotation.XmlAccessType; -import javax.xml.bind.annotation.XmlAccessorType; -import javax.xml.bind.annotation.XmlRootElement; -import javax.xml.bind.annotation.XmlTransient; - -@XmlRootElement(name = "repository-link") -@XmlAccessorType(XmlAccessType.FIELD) -public class RepositoryPath implements ModelObject { - - private String path; - private String id; - private Long lastModified; - private Long creationDate; - - @XmlTransient - private Repository repository; - - @XmlTransient - private boolean toBeSynchronized; - - /** - * Needed from JAXB - */ - public RepositoryPath() { - } - - public RepositoryPath(String path, String id, Repository repository) { - this.path = path; - this.id = id; - this.repository = repository; - } - - public Repository getRepository() { - return repository; - } - - public void setRepository(Repository repository) { - this.repository = repository; - } - - public String getPath() { - return path; - } - - public void setPath(String path) { - this.path = path; - } - - @Override - public String getId() { - return id; - } - - @Override - public void setLastModified(Long lastModified) { - this.lastModified = lastModified; - } - - @Override - public Long getCreationDate() { - return creationDate; - } - - @Override - public void setCreationDate(Long creationDate) { - this.creationDate = creationDate; - } - - public void setId(String id) { - this.id = id; - } - - @Override - public Long getLastModified() { - return lastModified; - } - - @Override - public String getType() { - return getRepository()!= null? getRepository().getType():""; - } - - @Override - public boolean isValid() { - return StringUtils.isNotEmpty(path); - } - - public boolean toBeSynchronized() { - return toBeSynchronized; - } - - public void setToBeSynchronized(boolean toBeSynchronized) { - this.toBeSynchronized = toBeSynchronized; - } -} diff --git a/scm-dao-xml/src/main/java/sonia/scm/repository/xml/XmlRepositoryDatabase.java b/scm-dao-xml/src/main/java/sonia/scm/repository/xml/XmlRepositoryDatabase.java deleted file mode 100644 index c7b2af656f..0000000000 --- a/scm-dao-xml/src/main/java/sonia/scm/repository/xml/XmlRepositoryDatabase.java +++ /dev/null @@ -1,188 +0,0 @@ -/** - * Copyright (c) 2010, Sebastian Sdorra - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * 3. Neither the name of SCM-Manager; nor the names of its - * contributors may be used to endorse or promote products derived from this - * software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY - * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON - * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - * http://bitbucket.org/sdorra/scm-manager - * - */ - - - -package sonia.scm.repository.xml; - -//~--- non-JDK imports -------------------------------------------------------- - -import sonia.scm.repository.NamespaceAndName; -import sonia.scm.repository.Repository; -import sonia.scm.xml.XmlDatabase; - -import javax.xml.bind.annotation.XmlAccessType; -import javax.xml.bind.annotation.XmlAccessorType; -import javax.xml.bind.annotation.XmlElement; -import javax.xml.bind.annotation.XmlRootElement; -import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; -import java.util.ArrayList; -import java.util.Collection; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; - -//~--- JDK imports ------------------------------------------------------------ - -@XmlRootElement(name = "repository-db") -@XmlAccessorType(XmlAccessType.FIELD) -public class XmlRepositoryDatabase implements XmlDatabase { - - private Long creationTime; - - private Long lastModified; - - @XmlJavaTypeAdapter(XmlRepositoryMapAdapter.class) - @XmlElement(name = "repositories") - private Map repositoryPathMap = new LinkedHashMap<>(); - - - public XmlRepositoryDatabase() { - long c = System.currentTimeMillis(); - creationTime = c; - lastModified = c; - } - - static String createKey(NamespaceAndName namespaceAndName) - { - return namespaceAndName.getNamespace() + ":" + namespaceAndName.getName(); - } - - static String createKey(Repository repository) - { - return createKey(repository.getNamespaceAndName()); - } - - @Override - public void add(RepositoryPath repositoryPath) - { - repositoryPathMap.put(createKey(repositoryPath.getRepository()), repositoryPath); - } - - public boolean contains(NamespaceAndName namespaceAndName) - { - return repositoryPathMap.containsKey(createKey(namespaceAndName)); - } - - @Override - public boolean contains(String id) - { - return get(id) != null; - } - - @Override - public RepositoryPath remove(String id) - { - return repositoryPathMap.remove(createKey(get(id).getRepository())); - } - - public Collection getRepositories() { - List repositories = new ArrayList<>(); - for (RepositoryPath repositoryPath : repositoryPathMap.values()) { - Repository repository = repositoryPath.getRepository(); - repositories.add(repository); - } - return repositories; - } - - @Override - public Collection values() - { - return repositoryPathMap.values(); - } - - public Repository get(NamespaceAndName namespaceAndName) { - RepositoryPath repositoryPath = repositoryPathMap.get(createKey(namespaceAndName)); - if (repositoryPath != null) { - return repositoryPath.getRepository(); - } - return null; - } - - - @Override - public RepositoryPath get(String id) { - return values().stream() - .filter(repoPath -> repoPath.getId().equals(id)) - .findFirst() - .orElse(null); - } - - /** - * Method description - * - * - * @return - */ - @Override - public long getCreationTime() - { - return creationTime; - } - - /** - * Method description - * - * - * @return - */ - @Override - public long getLastModified() - { - return lastModified; - } - - //~--- set methods ---------------------------------------------------------- - - /** - * Method description - * - * - * @param creationTime - */ - @Override - public void setCreationTime(long creationTime) - { - this.creationTime = creationTime; - } - - /** - * Method description - * - * - * @param lastModified - */ - @Override - public void setLastModified(long lastModified) - { - this.lastModified = lastModified; - } -} diff --git a/scm-dao-xml/src/main/java/sonia/scm/repository/xml/XmlRepositoryList.java b/scm-dao-xml/src/main/java/sonia/scm/repository/xml/XmlRepositoryList.java deleted file mode 100644 index b31f870a8e..0000000000 --- a/scm-dao-xml/src/main/java/sonia/scm/repository/xml/XmlRepositoryList.java +++ /dev/null @@ -1,123 +0,0 @@ -/** - * Copyright (c) 2010, Sebastian Sdorra - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * 3. Neither the name of SCM-Manager; nor the names of its - * contributors may be used to endorse or promote products derived from this - * software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY - * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON - * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - * http://bitbucket.org/sdorra/scm-manager - * - */ - - - -package sonia.scm.repository.xml; - -//~--- non-JDK imports -------------------------------------------------------- - -import sonia.scm.repository.Repository; - -//~--- JDK imports ------------------------------------------------------------ - -import java.util.Iterator; -import java.util.LinkedList; -import java.util.Map; - -import javax.xml.bind.annotation.XmlAccessType; -import javax.xml.bind.annotation.XmlAccessorType; -import javax.xml.bind.annotation.XmlElement; -import javax.xml.bind.annotation.XmlRootElement; - -/** - * - * @author Sebastian Sdorra - */ -@XmlRootElement(name = "repositories") -@XmlAccessorType(XmlAccessType.FIELD) -public class XmlRepositoryList implements Iterable -{ - - /** - * Constructs ... - * - */ - public XmlRepositoryList() {} - - /** - * Constructs ... - * - * - * - * @param repositoryMap - */ - public XmlRepositoryList(Map repositoryMap) - { - this.repositories = new LinkedList<>(repositoryMap.values()); - } - - //~--- methods -------------------------------------------------------------- - - /** - * Method description - * - * - * @return - */ - @Override - public Iterator iterator() - { - return repositories.iterator(); - } - - //~--- get methods ---------------------------------------------------------- - - /** - * Method description - * - * - * @return - */ - public LinkedList getRepositoryPaths() - { - return repositories; - } - - //~--- set methods ---------------------------------------------------------- - - /** - * Method description - * - * - * @param repositories - */ - public void setRepositories(LinkedList repositories) - { - this.repositories = repositories; - } - - //~--- fields --------------------------------------------------------------- - - /** Field description */ - @XmlElement(name = "repository-path") - private LinkedList repositories; -} diff --git a/scm-dao-xml/src/main/java/sonia/scm/repository/xml/XmlRepositoryMapAdapter.java b/scm-dao-xml/src/main/java/sonia/scm/repository/xml/XmlRepositoryMapAdapter.java deleted file mode 100644 index 633c9a27b3..0000000000 --- a/scm-dao-xml/src/main/java/sonia/scm/repository/xml/XmlRepositoryMapAdapter.java +++ /dev/null @@ -1,112 +0,0 @@ -/** - * Copyright (c) 2010, Sebastian Sdorra - * All rights reserved. - *

- * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - *

- * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * 3. Neither the name of SCM-Manager; nor the names of its - * contributors may be used to endorse or promote products derived from this - * software without specific prior written permission. - *

- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY - * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON - * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - *

- * http://bitbucket.org/sdorra/scm-manager - */ - - -package sonia.scm.repository.xml; - -import sonia.scm.SCMContext; -import sonia.scm.SCMContextProvider; -import sonia.scm.repository.InternalRepositoryException; -import sonia.scm.repository.Repository; -import sonia.scm.store.StoreConstants; -import sonia.scm.store.StoreException; - -import javax.xml.bind.JAXBContext; -import javax.xml.bind.JAXBException; -import javax.xml.bind.Marshaller; -import javax.xml.bind.Unmarshaller; -import javax.xml.bind.annotation.adapters.XmlAdapter; -import java.io.File; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.LinkedHashMap; -import java.util.Map; - - -/** - * @author Sebastian Sdorra - */ -public class XmlRepositoryMapAdapter extends XmlAdapter> { - - @Override - public XmlRepositoryList marshal(Map repositoryMap) { - XmlRepositoryList repositoryPaths = new XmlRepositoryList(repositoryMap); - try { - JAXBContext context = JAXBContext.newInstance(Repository.class); - Marshaller marshaller = context.createMarshaller(); - - marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE); - - // marshall the repo_path/metadata.xml files - for (RepositoryPath repositoryPath : repositoryPaths.getRepositoryPaths()) { - if (repositoryPath.toBeSynchronized()) { - - File baseDirectory = SCMContext.getContext().getBaseDirectory(); - Path dir = baseDirectory.toPath().resolve(repositoryPath.getPath()); - - if (!Files.isDirectory(dir)) { - throw new InternalRepositoryException(repositoryPath.getRepository(), "repository path not found"); - } - marshaller.marshal(repositoryPath.getRepository(), getRepositoryMetadataFile(dir.toFile())); - repositoryPath.setToBeSynchronized(false); - } - } - } catch (JAXBException ex) { - throw new StoreException("failed to marshal repository database", ex); - } - - return repositoryPaths; - - } - - private File getRepositoryMetadataFile(File dir) { - return new File(dir, StoreConstants.REPOSITORY_METADATA.concat(StoreConstants.FILE_EXTENSION)); - } - - @Override - public Map unmarshal(XmlRepositoryList repositoryPaths) { - Map repositoryPathMap = new LinkedHashMap<>(); - try { - JAXBContext context = JAXBContext.newInstance(Repository.class); - Unmarshaller unmarshaller = context.createUnmarshaller(); - for (RepositoryPath repositoryPath : repositoryPaths) { - SCMContextProvider contextProvider = SCMContext.getContext(); - File baseDirectory = contextProvider.getBaseDirectory(); - Repository repository = (Repository) unmarshaller.unmarshal(getRepositoryMetadataFile(baseDirectory.toPath().resolve(repositoryPath.getPath()).toFile())); - - repositoryPath.setRepository(repository); - repositoryPathMap.put(XmlRepositoryDatabase.createKey(repository), repositoryPath); - } - } catch (JAXBException ex) { - throw new StoreException("failed to unmarshal object", ex); - } - return repositoryPathMap; - } -}