From fae3de7b6c641aec5550af14a7a0f57abeaae7c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Wed, 19 Jun 2019 09:31:56 +0200 Subject: [PATCH 01/15] Extract update step test util --- .../sonia/scm/update/UpdateStepTestUtil.java | 20 +------------------ .../group/XmlGroupV1UpdateStepTest.java | 5 ++++- .../update/user/XmlUserV1UpdateStepTest.java | 15 ++++++++++++-- 3 files changed, 18 insertions(+), 22 deletions(-) rename {scm-webapp/src/test => scm-test/src/main}/java/sonia/scm/update/UpdateStepTestUtil.java (71%) diff --git a/scm-webapp/src/test/java/sonia/scm/update/UpdateStepTestUtil.java b/scm-test/src/main/java/sonia/scm/update/UpdateStepTestUtil.java similarity index 71% rename from scm-webapp/src/test/java/sonia/scm/update/UpdateStepTestUtil.java rename to scm-test/src/main/java/sonia/scm/update/UpdateStepTestUtil.java index 8326e6d255..7b4487f1a5 100644 --- a/scm-webapp/src/test/java/sonia/scm/update/UpdateStepTestUtil.java +++ b/scm-test/src/main/java/sonia/scm/update/UpdateStepTestUtil.java @@ -3,11 +3,6 @@ package sonia.scm.update; import com.google.common.io.Resources; import org.mockito.Mockito; import sonia.scm.SCMContextProvider; -import sonia.scm.security.AssignedPermission; -import sonia.scm.security.DefaultKeyGenerator; -import sonia.scm.store.ConfigurationEntryStore; -import sonia.scm.store.ConfigurationEntryStoreFactory; -import sonia.scm.store.JAXBConfigurationEntryStoreFactory; import java.io.IOException; import java.net.URL; @@ -20,15 +15,13 @@ import static org.mockito.Mockito.lenient; public class UpdateStepTestUtil { -private final SCMContextProvider contextProvider; + private final SCMContextProvider contextProvider; private final Path tempDir; - private final ConfigurationEntryStoreFactory storeFactory; public UpdateStepTestUtil(Path tempDir) { this.tempDir = tempDir; contextProvider = Mockito.mock(SCMContextProvider.class); - storeFactory = new JAXBConfigurationEntryStoreFactory(contextProvider, null, new DefaultKeyGenerator()); lenient().when(contextProvider.getBaseDirectory()).thenReturn(tempDir.toFile()); lenient().when(contextProvider.resolve(any())).thenAnswer(invocation -> tempDir.resolve(invocation.getArgument(0).toString())); } @@ -37,10 +30,6 @@ private final SCMContextProvider contextProvider; return contextProvider; } - public ConfigurationEntryStoreFactory getStoreFactory() { - return storeFactory; - } - public void copyConfigFile(String fileName) throws IOException { Path configDir = tempDir.resolve("config"); Files.createDirectories(configDir); @@ -53,13 +42,6 @@ private final SCMContextProvider contextProvider; copyTestDatabaseFile(configDir, fileName, targetFileName); } - public ConfigurationEntryStore getStoreForConfigFile(String name) { - return storeFactory - .withType(AssignedPermission.class) - .withName(name) - .build(); - } - public Path getFile(String name) { return tempDir.resolve("config").resolve(name); } diff --git a/scm-webapp/src/test/java/sonia/scm/update/group/XmlGroupV1UpdateStepTest.java b/scm-webapp/src/test/java/sonia/scm/update/group/XmlGroupV1UpdateStepTest.java index 6dee5fd5c1..caa7f68fea 100644 --- a/scm-webapp/src/test/java/sonia/scm/update/group/XmlGroupV1UpdateStepTest.java +++ b/scm-webapp/src/test/java/sonia/scm/update/group/XmlGroupV1UpdateStepTest.java @@ -11,6 +11,8 @@ import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import sonia.scm.group.Group; import sonia.scm.group.xml.XmlGroupDAO; +import sonia.scm.security.DefaultKeyGenerator; +import sonia.scm.store.JAXBConfigurationEntryStoreFactory; import sonia.scm.update.UpdateStepTestUtil; import javax.xml.bind.JAXBException; @@ -44,7 +46,8 @@ class XmlGroupV1UpdateStepTest { @BeforeEach void mockScmHome(@TempDirectory.TempDir Path tempDir) { testUtil = new UpdateStepTestUtil(tempDir); - updateStep = new XmlGroupV1UpdateStep(testUtil.getContextProvider(), groupDAO, testUtil.getStoreFactory()); + JAXBConfigurationEntryStoreFactory storeFactory = new JAXBConfigurationEntryStoreFactory(testUtil.getContextProvider(), null, new DefaultKeyGenerator()); + updateStep = new XmlGroupV1UpdateStep(testUtil.getContextProvider(), groupDAO, storeFactory); } @Nested diff --git a/scm-webapp/src/test/java/sonia/scm/update/user/XmlUserV1UpdateStepTest.java b/scm-webapp/src/test/java/sonia/scm/update/user/XmlUserV1UpdateStepTest.java index 19d37d542d..de3ab3cead 100644 --- a/scm-webapp/src/test/java/sonia/scm/update/user/XmlUserV1UpdateStepTest.java +++ b/scm-webapp/src/test/java/sonia/scm/update/user/XmlUserV1UpdateStepTest.java @@ -10,6 +10,9 @@ import org.mockito.Captor; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import sonia.scm.security.AssignedPermission; +import sonia.scm.security.DefaultKeyGenerator; +import sonia.scm.store.ConfigurationEntryStore; +import sonia.scm.store.JAXBConfigurationEntryStoreFactory; import sonia.scm.update.UpdateStepTestUtil; import sonia.scm.user.User; import sonia.scm.user.xml.XmlUserDAO; @@ -43,7 +46,8 @@ class XmlUserV1UpdateStepTest { @BeforeEach void mockScmHome(@TempDirectory.TempDir Path tempDir) { testUtil = new UpdateStepTestUtil(tempDir); - updateStep = new XmlUserV1UpdateStep(testUtil.getContextProvider(), userDAO, testUtil.getStoreFactory()); + JAXBConfigurationEntryStoreFactory storeFactory = new JAXBConfigurationEntryStoreFactory(testUtil.getContextProvider(), null, new DefaultKeyGenerator()); + updateStep = new XmlUserV1UpdateStep(testUtil.getContextProvider(), userDAO, storeFactory); } @Nested @@ -63,7 +67,7 @@ class XmlUserV1UpdateStepTest { void shouldCreateNewPermissionsForV1AdminUser() throws JAXBException { updateStep.doUpdate(); Optional assignedPermission = - testUtil.getStoreForConfigFile("security") + getStoreForConfigFile("security") .getAll() .values() .stream() @@ -116,6 +120,13 @@ class XmlUserV1UpdateStepTest { "", ""); } + + private ConfigurationEntryStore getStoreForConfigFile(String name) { + return new JAXBConfigurationEntryStoreFactory(testUtil.getContextProvider(), null, new DefaultKeyGenerator()) + .withType(AssignedPermission.class) + .withName(name) + .build(); + } } @Test From d43ad44da9282511288ca30a6d1ff1b47d09e770 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Wed, 19 Jun 2019 10:14:29 +0200 Subject: [PATCH 02/15] Enhance in-memory store factories for tests --- ...nMemoryConfigurationEntryStoreFactory.java | 29 ++++++++----------- .../InMemoryConfigurationStoreFactory.java | 26 ++++++++--------- .../sonia/scm/update/UpdateEngineTest.java | 2 +- .../XmlRepositoryV1UpdateStepTest.java | 4 +-- .../security/XmlSecurityV1UpdateStepTest.java | 5 ++-- .../update/user/XmlUserV1UpdateStepTest.java | 3 +- 6 files changed, 32 insertions(+), 37 deletions(-) diff --git a/scm-test/src/main/java/sonia/scm/store/InMemoryConfigurationEntryStoreFactory.java b/scm-test/src/main/java/sonia/scm/store/InMemoryConfigurationEntryStoreFactory.java index 48e60684b6..3e5d3a8dac 100644 --- a/scm-test/src/main/java/sonia/scm/store/InMemoryConfigurationEntryStoreFactory.java +++ b/scm-test/src/main/java/sonia/scm/store/InMemoryConfigurationEntryStoreFactory.java @@ -1,28 +1,23 @@ package sonia.scm.store; +import java.util.HashMap; +import java.util.Map; + public class InMemoryConfigurationEntryStoreFactory implements ConfigurationEntryStoreFactory { + private final Map stores = new HashMap<>(); - - - private ConfigurationEntryStore store; - - public static ConfigurationEntryStoreFactory create() { - return new InMemoryConfigurationEntryStoreFactory(new InMemoryConfigurationEntryStore()); - } - - public InMemoryConfigurationEntryStoreFactory() { - } - - public InMemoryConfigurationEntryStoreFactory(ConfigurationEntryStore store) { - this.store = store; + public static InMemoryConfigurationEntryStoreFactory create() { + return new InMemoryConfigurationEntryStoreFactory(); } @Override public ConfigurationEntryStore getStore(TypedStoreParameters storeParameters) { - if (store != null) { - return store; - } - return new InMemoryConfigurationEntryStore<>(); + String name = storeParameters.getName(); + return get(name); + } + + public InMemoryConfigurationEntryStore get(String name) { + return stores.computeIfAbsent(name, x -> new InMemoryConfigurationEntryStore()); } } diff --git a/scm-test/src/main/java/sonia/scm/store/InMemoryConfigurationStoreFactory.java b/scm-test/src/main/java/sonia/scm/store/InMemoryConfigurationStoreFactory.java index ee21703c4a..aaa5b6c593 100644 --- a/scm-test/src/main/java/sonia/scm/store/InMemoryConfigurationStoreFactory.java +++ b/scm-test/src/main/java/sonia/scm/store/InMemoryConfigurationStoreFactory.java @@ -35,6 +35,9 @@ package sonia.scm.store; //~--- non-JDK imports -------------------------------------------------------- +import java.util.HashMap; +import java.util.Map; + /** * In memory configuration store factory for testing purposes. * @@ -44,24 +47,19 @@ package sonia.scm.store; */ public class InMemoryConfigurationStoreFactory implements ConfigurationStoreFactory { - private ConfigurationStore store; + private final Map stores = new HashMap<>(); - public static ConfigurationStoreFactory create() { - return new InMemoryConfigurationStoreFactory(new InMemoryConfigurationStore()); - } - - public InMemoryConfigurationStoreFactory() { - } - - public InMemoryConfigurationStoreFactory(ConfigurationStore store) { - this.store = store; + public static InMemoryConfigurationStoreFactory create() { + return new InMemoryConfigurationStoreFactory(); } @Override public ConfigurationStore getStore(TypedStoreParameters storeParameters) { - if (store != null) { - return store; - } - return new InMemoryConfigurationStore<>(); + String name = storeParameters.getName(); + return get(name); + } + + public ConfigurationStore get(String name) { + return stores.computeIfAbsent(name, x -> new InMemoryConfigurationStore()); } } diff --git a/scm-webapp/src/test/java/sonia/scm/update/UpdateEngineTest.java b/scm-webapp/src/test/java/sonia/scm/update/UpdateEngineTest.java index 02ff0967bb..746eabc2ea 100644 --- a/scm-webapp/src/test/java/sonia/scm/update/UpdateEngineTest.java +++ b/scm-webapp/src/test/java/sonia/scm/update/UpdateEngineTest.java @@ -16,7 +16,7 @@ import static sonia.scm.version.Version.parse; class UpdateEngineTest { - ConfigurationEntryStoreFactory storeFactory = new InMemoryConfigurationEntryStoreFactory(new InMemoryConfigurationEntryStore()); + ConfigurationEntryStoreFactory storeFactory = new InMemoryConfigurationEntryStoreFactory(); List processedUpdates = new ArrayList<>(); diff --git a/scm-webapp/src/test/java/sonia/scm/update/repository/XmlRepositoryV1UpdateStepTest.java b/scm-webapp/src/test/java/sonia/scm/update/repository/XmlRepositoryV1UpdateStepTest.java index 6c977a3cb6..2145164062 100644 --- a/scm-webapp/src/test/java/sonia/scm/update/repository/XmlRepositoryV1UpdateStepTest.java +++ b/scm-webapp/src/test/java/sonia/scm/update/repository/XmlRepositoryV1UpdateStepTest.java @@ -51,7 +51,7 @@ class XmlRepositoryV1UpdateStepTest { @Mock MigrationStrategyDao migrationStrategyDao; - ConfigurationEntryStoreFactory configurationEntryStoreFactory = new InMemoryConfigurationEntryStoreFactory(new InMemoryConfigurationEntryStore()); + InMemoryConfigurationEntryStoreFactory configurationEntryStoreFactory = new InMemoryConfigurationEntryStoreFactory(); @Captor ArgumentCaptor storeCaptor; @@ -137,7 +137,7 @@ class XmlRepositoryV1UpdateStepTest { void shouldExtractPropertiesFromRepositories() throws JAXBException { updateStep.doUpdate(); - ConfigurationEntryStore store = configurationEntryStoreFactory.withType(null).withName("").build(); + ConfigurationEntryStore store = configurationEntryStoreFactory.get("repository-properties-v1"); assertThat(store.getAll()) .hasSize(3); } diff --git a/scm-webapp/src/test/java/sonia/scm/update/security/XmlSecurityV1UpdateStepTest.java b/scm-webapp/src/test/java/sonia/scm/update/security/XmlSecurityV1UpdateStepTest.java index f8949d73a5..d0115e3426 100644 --- a/scm-webapp/src/test/java/sonia/scm/update/security/XmlSecurityV1UpdateStepTest.java +++ b/scm-webapp/src/test/java/sonia/scm/update/security/XmlSecurityV1UpdateStepTest.java @@ -26,6 +26,7 @@ import java.util.List; import static java.util.stream.Collectors.toList; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.when; +import static sonia.scm.store.InMemoryConfigurationEntryStoreFactory.create; @ExtendWith(MockitoExtension.class) @ExtendWith(TempDirectory.class) @@ -40,8 +41,8 @@ class XmlSecurityV1UpdateStepTest { @BeforeEach void mockScmHome(@TempDirectory.TempDir Path tempDir) { when(contextProvider.getBaseDirectory()).thenReturn(tempDir.toFile()); - assignedPermissionStore = new InMemoryConfigurationEntryStore<>(); - ConfigurationEntryStoreFactory inMemoryConfigurationEntryStoreFactory = new InMemoryConfigurationEntryStoreFactory(assignedPermissionStore); + InMemoryConfigurationEntryStoreFactory inMemoryConfigurationEntryStoreFactory = create(); + assignedPermissionStore = inMemoryConfigurationEntryStoreFactory.get("security"); updateStep = new XmlSecurityV1UpdateStep(contextProvider, inMemoryConfigurationEntryStoreFactory); } diff --git a/scm-webapp/src/test/java/sonia/scm/update/user/XmlUserV1UpdateStepTest.java b/scm-webapp/src/test/java/sonia/scm/update/user/XmlUserV1UpdateStepTest.java index de3ab3cead..9de169d93d 100644 --- a/scm-webapp/src/test/java/sonia/scm/update/user/XmlUserV1UpdateStepTest.java +++ b/scm-webapp/src/test/java/sonia/scm/update/user/XmlUserV1UpdateStepTest.java @@ -12,6 +12,7 @@ import org.mockito.junit.jupiter.MockitoExtension; import sonia.scm.security.AssignedPermission; import sonia.scm.security.DefaultKeyGenerator; import sonia.scm.store.ConfigurationEntryStore; +import sonia.scm.store.ConfigurationEntryStoreFactory; import sonia.scm.store.JAXBConfigurationEntryStoreFactory; import sonia.scm.update.UpdateStepTestUtil; import sonia.scm.user.User; @@ -46,7 +47,7 @@ class XmlUserV1UpdateStepTest { @BeforeEach void mockScmHome(@TempDirectory.TempDir Path tempDir) { testUtil = new UpdateStepTestUtil(tempDir); - JAXBConfigurationEntryStoreFactory storeFactory = new JAXBConfigurationEntryStoreFactory(testUtil.getContextProvider(), null, new DefaultKeyGenerator()); + ConfigurationEntryStoreFactory storeFactory = new JAXBConfigurationEntryStoreFactory(testUtil.getContextProvider(), null, new DefaultKeyGenerator()); updateStep = new XmlUserV1UpdateStep(testUtil.getContextProvider(), userDAO, storeFactory); } From d3b65ac3bdeebd49bb53bd41e2e6d1fa2d19871d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Wed, 19 Jun 2019 10:38:22 +0200 Subject: [PATCH 03/15] Check stored values instead of resulting xml files --- ...nMemoryConfigurationEntryStoreFactory.java | 2 +- .../sonia/scm/update/UpdateStepTestUtil.java | 4 -- .../scm/update/properties/V1Properties.java | 6 +++ .../scm/update/properties/V1Property.java | 39 ++++++++++++++++ .../group/XmlGroupV1UpdateStepTest.java | 31 ++++++------- .../update/user/XmlUserV1UpdateStepTest.java | 46 +++++++------------ 6 files changed, 76 insertions(+), 52 deletions(-) diff --git a/scm-test/src/main/java/sonia/scm/store/InMemoryConfigurationEntryStoreFactory.java b/scm-test/src/main/java/sonia/scm/store/InMemoryConfigurationEntryStoreFactory.java index 3e5d3a8dac..1578a20751 100644 --- a/scm-test/src/main/java/sonia/scm/store/InMemoryConfigurationEntryStoreFactory.java +++ b/scm-test/src/main/java/sonia/scm/store/InMemoryConfigurationEntryStoreFactory.java @@ -17,7 +17,7 @@ public class InMemoryConfigurationEntryStoreFactory implements ConfigurationEntr return get(name); } - public InMemoryConfigurationEntryStore get(String name) { + public InMemoryConfigurationEntryStore get(String name) { return stores.computeIfAbsent(name, x -> new InMemoryConfigurationEntryStore()); } } diff --git a/scm-test/src/main/java/sonia/scm/update/UpdateStepTestUtil.java b/scm-test/src/main/java/sonia/scm/update/UpdateStepTestUtil.java index 7b4487f1a5..51899cdbee 100644 --- a/scm-test/src/main/java/sonia/scm/update/UpdateStepTestUtil.java +++ b/scm-test/src/main/java/sonia/scm/update/UpdateStepTestUtil.java @@ -42,10 +42,6 @@ public class UpdateStepTestUtil { copyTestDatabaseFile(configDir, fileName, targetFileName); } - public Path getFile(String name) { - return tempDir.resolve("config").resolve(name); - } - private void copyTestDatabaseFile(Path configDir, String fileName) throws IOException { Path targetFileName = Paths.get(fileName).getFileName(); copyTestDatabaseFile(configDir, fileName, targetFileName.toString()); diff --git a/scm-webapp/src/main/java/sonia/scm/update/properties/V1Properties.java b/scm-webapp/src/main/java/sonia/scm/update/properties/V1Properties.java index 3c8555a76e..156fdae73f 100644 --- a/scm-webapp/src/main/java/sonia/scm/update/properties/V1Properties.java +++ b/scm-webapp/src/main/java/sonia/scm/update/properties/V1Properties.java @@ -6,9 +6,15 @@ import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlRootElement; import java.util.List; +import static java.util.Collections.unmodifiableList; + @XmlAccessorType(XmlAccessType.FIELD) @XmlRootElement(name = "properties") public class V1Properties { @XmlElement(name = "item") private List properties; + + public List getProperties() { + return unmodifiableList(properties); + } } diff --git a/scm-webapp/src/main/java/sonia/scm/update/properties/V1Property.java b/scm-webapp/src/main/java/sonia/scm/update/properties/V1Property.java index c92c15188d..86f16a085a 100644 --- a/scm-webapp/src/main/java/sonia/scm/update/properties/V1Property.java +++ b/scm-webapp/src/main/java/sonia/scm/update/properties/V1Property.java @@ -2,9 +2,48 @@ package sonia.scm.update.properties; import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; +import java.util.Objects; @XmlAccessorType(XmlAccessType.FIELD) public class V1Property { private String key; private String value; + + public V1Property() { + } + + public V1Property(String key, String value) { + this.key = key; + this.value = value; + } + + public String getKey() { + return key; + } + + public String getValue() { + return value; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + V1Property that = (V1Property) o; + return Objects.equals(key, that.key) && + Objects.equals(value, that.value); + } + + @Override + public int hashCode() { + return Objects.hash(key, value); + } + + @Override + public String toString() { + return "V1Property{" + + "key='" + key + '\'' + + ", value='" + value + '\'' + + '}'; + } } diff --git a/scm-webapp/src/test/java/sonia/scm/update/group/XmlGroupV1UpdateStepTest.java b/scm-webapp/src/test/java/sonia/scm/update/group/XmlGroupV1UpdateStepTest.java index caa7f68fea..415bd508eb 100644 --- a/scm-webapp/src/test/java/sonia/scm/update/group/XmlGroupV1UpdateStepTest.java +++ b/scm-webapp/src/test/java/sonia/scm/update/group/XmlGroupV1UpdateStepTest.java @@ -11,9 +11,11 @@ import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import sonia.scm.group.Group; import sonia.scm.group.xml.XmlGroupDAO; -import sonia.scm.security.DefaultKeyGenerator; -import sonia.scm.store.JAXBConfigurationEntryStoreFactory; +import sonia.scm.store.ConfigurationEntryStore; +import sonia.scm.store.InMemoryConfigurationEntryStoreFactory; import sonia.scm.update.UpdateStepTestUtil; +import sonia.scm.update.properties.V1Properties; +import sonia.scm.update.properties.V1Property; import javax.xml.bind.JAXBException; import java.io.IOException; @@ -22,11 +24,11 @@ import java.util.Optional; import static java.util.Arrays.asList; import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.linesOf; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +import static sonia.scm.store.InMemoryConfigurationEntryStoreFactory.create; @ExtendWith(MockitoExtension.class) @ExtendWith(TempDirectory.class) @@ -38,6 +40,8 @@ class XmlGroupV1UpdateStepTest { @Captor ArgumentCaptor groupCaptor; + InMemoryConfigurationEntryStoreFactory storeFactory = create(); + XmlGroupV1UpdateStep updateStep; private UpdateStepTestUtil testUtil; @@ -46,7 +50,6 @@ class XmlGroupV1UpdateStepTest { @BeforeEach void mockScmHome(@TempDirectory.TempDir Path tempDir) { testUtil = new UpdateStepTestUtil(tempDir); - JAXBConfigurationEntryStoreFactory storeFactory = new JAXBConfigurationEntryStoreFactory(testUtil.getContextProvider(), null, new DefaultKeyGenerator()); updateStep = new XmlGroupV1UpdateStep(testUtil.getContextProvider(), groupDAO, storeFactory); } @@ -86,19 +89,13 @@ class XmlGroupV1UpdateStepTest { @Test void shouldExtractProperties() throws JAXBException { updateStep.doUpdate(); - Path propertiesFile = testUtil.getFile("group-properties-v1.xml"); - assertThat(propertiesFile) - .exists(); - assertThat(linesOf(propertiesFile.toFile())) - .extracting(String::trim) - .containsSequence( - "normals", - "", - "", - "mostly", - "humans", - "", - ""); + ConfigurationEntryStore propertiesStore = storeFactory.get("group-properties-v1"); + assertThat(propertiesStore.get("normals")) + .isNotNull() + .extracting(V1Properties::getProperties) + .first() + .asList() + .contains(new V1Property("mostly", "humans")); } } diff --git a/scm-webapp/src/test/java/sonia/scm/update/user/XmlUserV1UpdateStepTest.java b/scm-webapp/src/test/java/sonia/scm/update/user/XmlUserV1UpdateStepTest.java index 9de169d93d..ec26db00a2 100644 --- a/scm-webapp/src/test/java/sonia/scm/update/user/XmlUserV1UpdateStepTest.java +++ b/scm-webapp/src/test/java/sonia/scm/update/user/XmlUserV1UpdateStepTest.java @@ -10,11 +10,11 @@ import org.mockito.Captor; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import sonia.scm.security.AssignedPermission; -import sonia.scm.security.DefaultKeyGenerator; import sonia.scm.store.ConfigurationEntryStore; -import sonia.scm.store.ConfigurationEntryStoreFactory; -import sonia.scm.store.JAXBConfigurationEntryStoreFactory; +import sonia.scm.store.InMemoryConfigurationEntryStoreFactory; import sonia.scm.update.UpdateStepTestUtil; +import sonia.scm.update.properties.V1Properties; +import sonia.scm.update.properties.V1Property; import sonia.scm.user.User; import sonia.scm.user.xml.XmlUserDAO; @@ -24,11 +24,11 @@ import java.nio.file.Path; import java.util.Optional; import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.linesOf; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +import static sonia.scm.store.InMemoryConfigurationEntryStoreFactory.create; @ExtendWith(MockitoExtension.class) @ExtendWith(TempDirectory.class) @@ -40,6 +40,8 @@ class XmlUserV1UpdateStepTest { @Captor ArgumentCaptor userCaptor; + InMemoryConfigurationEntryStoreFactory storeFactory = create(); + XmlUserV1UpdateStep updateStep; private UpdateStepTestUtil testUtil; @@ -47,7 +49,6 @@ class XmlUserV1UpdateStepTest { @BeforeEach void mockScmHome(@TempDirectory.TempDir Path tempDir) { testUtil = new UpdateStepTestUtil(tempDir); - ConfigurationEntryStoreFactory storeFactory = new JAXBConfigurationEntryStoreFactory(testUtil.getContextProvider(), null, new DefaultKeyGenerator()); updateStep = new XmlUserV1UpdateStep(testUtil.getContextProvider(), userDAO, storeFactory); } @@ -68,7 +69,7 @@ class XmlUserV1UpdateStepTest { void shouldCreateNewPermissionsForV1AdminUser() throws JAXBException { updateStep.doUpdate(); Optional assignedPermission = - getStoreForConfigFile("security") + storeFactory.get("security") .getAll() .values() .stream() @@ -103,30 +104,15 @@ class XmlUserV1UpdateStepTest { @Test void shouldExtractProperties() throws JAXBException { updateStep.doUpdate(); - Path propertiesFile = testUtil.getFile("user-properties-v1.xml"); - assertThat(propertiesFile) - .exists(); - assertThat(linesOf(propertiesFile.toFile())) - .extracting(String::trim) - .containsSequence( - "dent", - "", - "", - "born.on", - "earth", - "", - "", - "last.seen", - "end of the universe", - "", - ""); - } - - private ConfigurationEntryStore getStoreForConfigFile(String name) { - return new JAXBConfigurationEntryStoreFactory(testUtil.getContextProvider(), null, new DefaultKeyGenerator()) - .withType(AssignedPermission.class) - .withName(name) - .build(); + ConfigurationEntryStore propertiesStore = storeFactory.get("user-properties-v1"); + assertThat(propertiesStore.get("dent")) + .isNotNull() + .extracting(V1Properties::getProperties) + .first() + .asList() + .contains( + new V1Property("born.on", "earth"), + new V1Property("last.seen", "end of the universe")); } } From 9581bf946b8dd31b57d73d36f1cdda4e1cd4179e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Thu, 20 Jun 2019 16:12:16 +0200 Subject: [PATCH 04/15] Enable plugins to create config stores for repository config Therefore we have to - add an API to create stores for repository ids, not only for repositories, - make v1 properties available in scm-core - make sure that properties are extracted from repositories before the update step of a plugin runs (this is done by sorting the update steps in a way so that "core" update steps are executed before plugin update steps with the same version) --- .../sonia/scm/store/BlobStoreFactory.java | 19 +++++++++--- .../store/ConfigurationEntryStoreFactory.java | 13 +++++++- .../scm/store/ConfigurationStoreFactory.java | 13 +++++++- .../sonia/scm/store/DataStoreFactory.java | 13 +++++++- .../java/sonia/scm/store/StoreParameters.java | 2 +- .../sonia/scm/store/TypedStoreParameters.java | 2 +- .../scm/store/TypedStoreParametersImpl.java | 12 ++++---- .../update/RepositoryV1PropertyReader.java | 23 ++++++++++++++ .../java/sonia/scm/update}/V1Properties.java | 6 ++-- .../java/sonia/scm/update}/V1Property.java | 2 +- .../java/sonia/scm/update/V1PropertyDAO.java | 5 ++++ .../sonia/scm/update/V1PropertyReader.java | 15 ++++++++++ .../scm/store/FileBasedStoreFactory.java | 19 ++++++------ .../JAXBConfigurationEntryStoreFactory.java | 6 ++-- .../store/JAXBConfigurationStoreFactory.java | 6 +++- .../scm/update/xml/XmlV1PropertyDAO.java | 30 +++++++++++++++++++ .../spi/GitIncomingCommandTest.java | 5 ++-- .../spi/GitOutgoingCommandTest.java | 3 +- .../scm/web/lfs/LfsBlobStoreFactoryTest.java | 2 +- .../java/sonia/scm/boot/BootstrapModule.java | 3 ++ .../java/sonia/scm/update/CoreUpdateStep.java | 6 ++++ .../java/sonia/scm/update/UpdateEngine.java | 10 ++++++- .../update/group/XmlGroupV1UpdateStep.java | 2 +- .../scm/update/repository/V1Repository.java | 2 +- .../repository/XmlRepositoryV1UpdateStep.java | 8 +++-- .../scm/update/user/XmlUserV1UpdateStep.java | 2 +- .../sonia/scm/update/UpdateEngineTest.java | 26 ++++++++++++++-- .../group/XmlGroupV1UpdateStepTest.java | 15 ++++------ .../update/user/XmlUserV1UpdateStepTest.java | 16 ++++------ 29 files changed, 220 insertions(+), 66 deletions(-) create mode 100644 scm-core/src/main/java/sonia/scm/update/RepositoryV1PropertyReader.java rename {scm-webapp/src/main/java/sonia/scm/update/properties => scm-core/src/main/java/sonia/scm/update}/V1Properties.java (71%) rename {scm-webapp/src/main/java/sonia/scm/update/properties => scm-core/src/main/java/sonia/scm/update}/V1Property.java (96%) create mode 100644 scm-core/src/main/java/sonia/scm/update/V1PropertyDAO.java create mode 100644 scm-core/src/main/java/sonia/scm/update/V1PropertyReader.java create mode 100644 scm-dao-xml/src/main/java/sonia/scm/update/xml/XmlV1PropertyDAO.java create mode 100644 scm-webapp/src/main/java/sonia/scm/update/CoreUpdateStep.java diff --git a/scm-core/src/main/java/sonia/scm/store/BlobStoreFactory.java b/scm-core/src/main/java/sonia/scm/store/BlobStoreFactory.java index cf58fc43c7..4cc124c5d7 100644 --- a/scm-core/src/main/java/sonia/scm/store/BlobStoreFactory.java +++ b/scm-core/src/main/java/sonia/scm/store/BlobStoreFactory.java @@ -82,7 +82,7 @@ public interface BlobStoreFactory { final class FloatingStoreParameters implements StoreParameters { private String name; - private Repository repository; + private String repositoryId; private final BlobStoreFactory factory; @@ -96,8 +96,8 @@ final class FloatingStoreParameters implements StoreParameters { } @Override - public Repository getRepository() { - return repository; + public String getRepositoryId() { + return repositoryId; } public class Builder { @@ -113,7 +113,18 @@ final class FloatingStoreParameters implements StoreParameters { * @return Floating API to finish the call. */ public FloatingStoreParameters.Builder forRepository(Repository repository) { - FloatingStoreParameters.this.repository = repository; + FloatingStoreParameters.this.repositoryId = repository.getId(); + return this; + } + + /** + * Use this to create or get a {@link BlobStore} for a specific repository. This step is optional. If you want to + * have a global {@link BlobStore}, omit this. + * @param repositoryId The id of the optional repository for the {@link BlobStore}. + * @return Floating API to finish the call. + */ + public FloatingStoreParameters.Builder forRepository(String repositoryId) { + FloatingStoreParameters.this.repositoryId = repositoryId; return this; } diff --git a/scm-core/src/main/java/sonia/scm/store/ConfigurationEntryStoreFactory.java b/scm-core/src/main/java/sonia/scm/store/ConfigurationEntryStoreFactory.java index 80f9cb3df9..7ff1633dc3 100644 --- a/scm-core/src/main/java/sonia/scm/store/ConfigurationEntryStoreFactory.java +++ b/scm-core/src/main/java/sonia/scm/store/ConfigurationEntryStoreFactory.java @@ -120,7 +120,18 @@ final class TypedFloatingConfigurationEntryStoreParameters { * @return Floating API to finish the call. */ public OptionalRepositoryBuilder forRepository(Repository repository) { - parameters.setRepository(repository); + parameters.setRepositoryId(repository.getId()); + return this; + } + + /** + * Use this to create or get a {@link ConfigurationEntryStore} for a specific repository. This step is optional. If + * you want to have a global {@link ConfigurationEntryStore}, omit this. + * @param repositoryId The id of the optional repository for the {@link ConfigurationEntryStore}. + * @return Floating API to finish the call. + */ + public OptionalRepositoryBuilder forRepository(String repositoryId) { + parameters.setRepositoryId(repositoryId); return this; } diff --git a/scm-core/src/main/java/sonia/scm/store/ConfigurationStoreFactory.java b/scm-core/src/main/java/sonia/scm/store/ConfigurationStoreFactory.java index 6624f307e7..cef39a303b 100644 --- a/scm-core/src/main/java/sonia/scm/store/ConfigurationStoreFactory.java +++ b/scm-core/src/main/java/sonia/scm/store/ConfigurationStoreFactory.java @@ -120,7 +120,18 @@ final class TypedFloatingConfigurationStoreParameters { * @return Floating API to finish the call. */ public OptionalRepositoryBuilder forRepository(Repository repository) { - parameters.setRepository(repository); + parameters.setRepositoryId(repository.getId()); + return this; + } + + /** + * Use this to create or get a {@link ConfigurationStore} for a specific repository. This step is optional. If you + * want to have a global {@link ConfigurationStore}, omit this. + * @param repositoryId The id of the optional repository for the {@link ConfigurationStore}. + * @return Floating API to finish the call. + */ + public OptionalRepositoryBuilder forRepository(String repositoryId) { + parameters.setRepositoryId(repositoryId); return this; } diff --git a/scm-core/src/main/java/sonia/scm/store/DataStoreFactory.java b/scm-core/src/main/java/sonia/scm/store/DataStoreFactory.java index 564c339d3d..cc149d46ce 100644 --- a/scm-core/src/main/java/sonia/scm/store/DataStoreFactory.java +++ b/scm-core/src/main/java/sonia/scm/store/DataStoreFactory.java @@ -117,7 +117,18 @@ final class TypedFloatingDataStoreParameters { * @return Floating API to finish the call. */ public OptionalRepositoryBuilder forRepository(Repository repository) { - parameters.setRepository(repository); + parameters.setRepositoryId(repository.getId()); + return this; + } + + /** + * Use this to create or get a {@link DataStore} for a specific repository. This step is optional. If you + * want to have a global {@link DataStore}, omit this. + * @param repositoryId The id of the optional repository for the {@link DataStore}. + * @return Floating API to finish the call. + */ + public OptionalRepositoryBuilder forRepository(String repositoryId) { + parameters.setRepositoryId(repositoryId); return this; } diff --git a/scm-core/src/main/java/sonia/scm/store/StoreParameters.java b/scm-core/src/main/java/sonia/scm/store/StoreParameters.java index da8ee4c916..38404398cc 100644 --- a/scm-core/src/main/java/sonia/scm/store/StoreParameters.java +++ b/scm-core/src/main/java/sonia/scm/store/StoreParameters.java @@ -12,5 +12,5 @@ public interface StoreParameters { String getName(); - Repository getRepository(); + String getRepositoryId(); } diff --git a/scm-core/src/main/java/sonia/scm/store/TypedStoreParameters.java b/scm-core/src/main/java/sonia/scm/store/TypedStoreParameters.java index 116bccac41..060a9706f8 100644 --- a/scm-core/src/main/java/sonia/scm/store/TypedStoreParameters.java +++ b/scm-core/src/main/java/sonia/scm/store/TypedStoreParameters.java @@ -15,5 +15,5 @@ public interface TypedStoreParameters { String getName(); - Repository getRepository(); + String getRepositoryId(); } diff --git a/scm-core/src/main/java/sonia/scm/store/TypedStoreParametersImpl.java b/scm-core/src/main/java/sonia/scm/store/TypedStoreParametersImpl.java index 50ce6a496b..3abf1d2192 100644 --- a/scm-core/src/main/java/sonia/scm/store/TypedStoreParametersImpl.java +++ b/scm-core/src/main/java/sonia/scm/store/TypedStoreParametersImpl.java @@ -1,11 +1,9 @@ package sonia.scm.store; -import sonia.scm.repository.Repository; - class TypedStoreParametersImpl implements TypedStoreParameters { private Class type; private String name; - private Repository repository; + private String repositoryId; @Override public Class getType() { @@ -26,11 +24,11 @@ class TypedStoreParametersImpl implements TypedStoreParameters { } @Override - public Repository getRepository() { - return repository; + public String getRepositoryId() { + return repositoryId; } - void setRepository(Repository repository) { - this.repository = repository; + void setRepositoryId(String repositoryId) { + this.repositoryId = repositoryId; } } diff --git a/scm-core/src/main/java/sonia/scm/update/RepositoryV1PropertyReader.java b/scm-core/src/main/java/sonia/scm/update/RepositoryV1PropertyReader.java new file mode 100644 index 0000000000..cb9f2d01d9 --- /dev/null +++ b/scm-core/src/main/java/sonia/scm/update/RepositoryV1PropertyReader.java @@ -0,0 +1,23 @@ +package sonia.scm.update; + +import java.util.Map; +import java.util.function.BiConsumer; + +public class RepositoryV1PropertyReader implements V1PropertyReader { + @Override + public String getStoreName() { + return "repository-properties-v1"; + } + + @Override + public Instance createInstance(Map all) { + return propertiesForNameConsumer -> + all + .entrySet() + .forEach(e -> call(e.getKey(), e.getValue(), propertiesForNameConsumer)); + } + + private void call(String repositoryId, V1Properties properties, BiConsumer propertiesForNameConsumer) { + propertiesForNameConsumer.accept(repositoryId, properties); + } +} diff --git a/scm-webapp/src/main/java/sonia/scm/update/properties/V1Properties.java b/scm-core/src/main/java/sonia/scm/update/V1Properties.java similarity index 71% rename from scm-webapp/src/main/java/sonia/scm/update/properties/V1Properties.java rename to scm-core/src/main/java/sonia/scm/update/V1Properties.java index 156fdae73f..5776fc91c7 100644 --- a/scm-webapp/src/main/java/sonia/scm/update/properties/V1Properties.java +++ b/scm-core/src/main/java/sonia/scm/update/V1Properties.java @@ -1,4 +1,4 @@ -package sonia.scm.update.properties; +package sonia.scm.update; import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; @@ -14,7 +14,7 @@ public class V1Properties { @XmlElement(name = "item") private List properties; - public List getProperties() { - return unmodifiableList(properties); + public String get(String key) { + return properties.stream().filter(p -> key.equals(p.getKey())).map(V1Property::getValue).findFirst().orElse(null); } } diff --git a/scm-webapp/src/main/java/sonia/scm/update/properties/V1Property.java b/scm-core/src/main/java/sonia/scm/update/V1Property.java similarity index 96% rename from scm-webapp/src/main/java/sonia/scm/update/properties/V1Property.java rename to scm-core/src/main/java/sonia/scm/update/V1Property.java index 86f16a085a..c3dcf6a590 100644 --- a/scm-webapp/src/main/java/sonia/scm/update/properties/V1Property.java +++ b/scm-core/src/main/java/sonia/scm/update/V1Property.java @@ -1,4 +1,4 @@ -package sonia.scm.update.properties; +package sonia.scm.update; import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; diff --git a/scm-core/src/main/java/sonia/scm/update/V1PropertyDAO.java b/scm-core/src/main/java/sonia/scm/update/V1PropertyDAO.java new file mode 100644 index 0000000000..b8cac19edf --- /dev/null +++ b/scm-core/src/main/java/sonia/scm/update/V1PropertyDAO.java @@ -0,0 +1,5 @@ +package sonia.scm.update; + +public interface V1PropertyDAO { + V1PropertyReader.Instance getProperties(V1PropertyReader reader); +} diff --git a/scm-core/src/main/java/sonia/scm/update/V1PropertyReader.java b/scm-core/src/main/java/sonia/scm/update/V1PropertyReader.java new file mode 100644 index 0000000000..2d20a9aeed --- /dev/null +++ b/scm-core/src/main/java/sonia/scm/update/V1PropertyReader.java @@ -0,0 +1,15 @@ +package sonia.scm.update; + +import java.util.Map; +import java.util.function.BiConsumer; + +public interface V1PropertyReader { + + String getStoreName(); + + Instance createInstance(Map all); + + interface Instance { + void forEachEntry(BiConsumer propertiesForNameConsumer); + } +} diff --git a/scm-dao-xml/src/main/java/sonia/scm/store/FileBasedStoreFactory.java b/scm-dao-xml/src/main/java/sonia/scm/store/FileBasedStoreFactory.java index d31179c1c2..967aa8fb1c 100644 --- a/scm-dao-xml/src/main/java/sonia/scm/store/FileBasedStoreFactory.java +++ b/scm-dao-xml/src/main/java/sonia/scm/store/FileBasedStoreFactory.java @@ -35,7 +35,6 @@ package sonia.scm.store; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import sonia.scm.SCMContextProvider; -import sonia.scm.repository.Repository; import sonia.scm.repository.RepositoryLocationResolver; import sonia.scm.util.IOUtil; @@ -66,18 +65,18 @@ public abstract class FileBasedStoreFactory { } protected File getStoreLocation(StoreParameters storeParameters) { - return getStoreLocation(storeParameters.getName(), null, storeParameters.getRepository()); + return getStoreLocation(storeParameters.getName(), null, storeParameters.getRepositoryId()); } protected File getStoreLocation(TypedStoreParameters storeParameters) { - return getStoreLocation(storeParameters.getName(), storeParameters.getType(), storeParameters.getRepository()); + return getStoreLocation(storeParameters.getName(), storeParameters.getType(), storeParameters.getRepositoryId()); } - protected File getStoreLocation(String name, Class type, Repository repository) { + protected File getStoreLocation(String name, Class type, String repositoryId) { File storeDirectory; - if (repository != null) { - LOG.debug("create store with type: {}, name: {} and repository: {}", type, name, repository.getNamespaceAndName()); - storeDirectory = this.getStoreDirectory(store, repository); + if (repositoryId != null) { + LOG.debug("create store with type: {}, name: {} and repository id: {}", type, name, repositoryId); + storeDirectory = this.getStoreDirectory(store, repositoryId); } else { LOG.debug("create store with type: {} and name: {} ", type, name); storeDirectory = this.getStoreDirectory(store); @@ -89,11 +88,11 @@ public abstract class FileBasedStoreFactory { /** * Get the store directory of a specific repository * @param store the type of the store - * @param repository the repo + * @param repositoryId the id of the repossitory * @return the store directory of a specific repository */ - private File getStoreDirectory(Store store, Repository repository) { - return new File(repositoryLocationResolver.forClass(Path.class).getLocation(repository.getId()).toFile(), store.getRepositoryStoreDirectory()); + private File getStoreDirectory(Store store, String repositoryId) { + return new File(repositoryLocationResolver.forClass(Path.class).getLocation(repositoryId).toFile(), store.getRepositoryStoreDirectory()); } /** diff --git a/scm-dao-xml/src/main/java/sonia/scm/store/JAXBConfigurationEntryStoreFactory.java b/scm-dao-xml/src/main/java/sonia/scm/store/JAXBConfigurationEntryStoreFactory.java index 96403140ef..f66bccb7ba 100644 --- a/scm-dao-xml/src/main/java/sonia/scm/store/JAXBConfigurationEntryStoreFactory.java +++ b/scm-dao-xml/src/main/java/sonia/scm/store/JAXBConfigurationEntryStoreFactory.java @@ -59,7 +59,9 @@ public class JAXBConfigurationEntryStoreFactory extends FileBasedStoreFactory @Override public ConfigurationEntryStore getStore(TypedStoreParameters storeParameters) { - return new JAXBConfigurationEntryStore<>(getStoreLocation(storeParameters.getName().concat(StoreConstants.FILE_EXTENSION), storeParameters.getType(), storeParameters.getRepository()), keyGenerator, storeParameters.getType()); + return new JAXBConfigurationEntryStore<>( + getStoreLocation(storeParameters.getName().concat(StoreConstants.FILE_EXTENSION), storeParameters.getType(), storeParameters.getRepositoryId()), + keyGenerator, + storeParameters.getType()); } - } diff --git a/scm-dao-xml/src/main/java/sonia/scm/store/JAXBConfigurationStoreFactory.java b/scm-dao-xml/src/main/java/sonia/scm/store/JAXBConfigurationStoreFactory.java index bb68ab93dc..e66394cb5d 100644 --- a/scm-dao-xml/src/main/java/sonia/scm/store/JAXBConfigurationStoreFactory.java +++ b/scm-dao-xml/src/main/java/sonia/scm/store/JAXBConfigurationStoreFactory.java @@ -55,6 +55,10 @@ public class JAXBConfigurationStoreFactory extends FileBasedStoreFactory impleme @Override public JAXBConfigurationStore getStore(TypedStoreParameters storeParameters) { - return new JAXBConfigurationStore<>(storeParameters.getType(), getStoreLocation(storeParameters.getName().concat(StoreConstants.FILE_EXTENSION), storeParameters.getType(), storeParameters.getRepository())); + return new JAXBConfigurationStore<>( + storeParameters.getType(), + getStoreLocation(storeParameters.getName().concat(StoreConstants.FILE_EXTENSION), + storeParameters.getType(), + storeParameters.getRepositoryId())); } } diff --git a/scm-dao-xml/src/main/java/sonia/scm/update/xml/XmlV1PropertyDAO.java b/scm-dao-xml/src/main/java/sonia/scm/update/xml/XmlV1PropertyDAO.java new file mode 100644 index 0000000000..5bdee89c68 --- /dev/null +++ b/scm-dao-xml/src/main/java/sonia/scm/update/xml/XmlV1PropertyDAO.java @@ -0,0 +1,30 @@ +package sonia.scm.update.xml; + +import sonia.scm.store.ConfigurationEntryStore; +import sonia.scm.store.ConfigurationEntryStoreFactory; +import sonia.scm.update.V1Properties; +import sonia.scm.update.V1PropertyDAO; +import sonia.scm.update.V1PropertyReader; + +import javax.inject.Inject; +import java.util.Map; + +public class XmlV1PropertyDAO implements V1PropertyDAO { + + private final ConfigurationEntryStoreFactory configurationEntryStoreFactory; + + @Inject + public XmlV1PropertyDAO(ConfigurationEntryStoreFactory configurationEntryStoreFactory) { + this.configurationEntryStoreFactory = configurationEntryStoreFactory; + } + + @Override + public V1PropertyReader.Instance getProperties(V1PropertyReader reader) { + ConfigurationEntryStore propertyStore = configurationEntryStoreFactory + .withType(V1Properties.class) + .withName(reader.getStoreName()) + .build(); + Map all = propertyStore.getAll(); + return reader.createInstance(all); + } +} diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitIncomingCommandTest.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitIncomingCommandTest.java index 376d7cdf7a..fc5c9c9fc2 100644 --- a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitIncomingCommandTest.java +++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitIncomingCommandTest.java @@ -40,6 +40,7 @@ import org.junit.Ignore; import org.junit.Test; import sonia.scm.api.v2.resources.GitRepositoryConfigStoreProvider; import sonia.scm.repository.ChangesetPagingResult; +import sonia.scm.repository.Repository; import sonia.scm.store.InMemoryConfigurationStoreFactory; import java.io.IOException; @@ -189,7 +190,7 @@ public class GitIncomingCommandTest */ private GitIncomingCommand createCommand() { - return new GitIncomingCommand(handler, new GitContext(incomingDirectory, null, new GitRepositoryConfigStoreProvider(new InMemoryConfigurationStoreFactory())), - incomingRepository); + return new GitIncomingCommand(handler, new GitContext(incomingDirectory, incomingRepository, new GitRepositoryConfigStoreProvider(new InMemoryConfigurationStoreFactory())), + this.incomingRepository); } } diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitOutgoingCommandTest.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitOutgoingCommandTest.java index 2525a6fa38..158973e710 100644 --- a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitOutgoingCommandTest.java +++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitOutgoingCommandTest.java @@ -40,6 +40,7 @@ import org.eclipse.jgit.revwalk.RevCommit; import org.junit.Test; import sonia.scm.api.v2.resources.GitRepositoryConfigStoreProvider; import sonia.scm.repository.ChangesetPagingResult; +import sonia.scm.repository.Repository; import sonia.scm.store.InMemoryConfigurationStoreFactory; import java.io.IOException; @@ -160,7 +161,7 @@ public class GitOutgoingCommandTest extends AbstractRemoteCommandTestBase */ private GitOutgoingCommand createCommand() { - return new GitOutgoingCommand(handler, new GitContext(outgoingDirectory, null, new GitRepositoryConfigStoreProvider(new InMemoryConfigurationStoreFactory())), + return new GitOutgoingCommand(handler, new GitContext(outgoingDirectory, outgoingRepository, new GitRepositoryConfigStoreProvider(new InMemoryConfigurationStoreFactory())), outgoingRepository); } } diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/web/lfs/LfsBlobStoreFactoryTest.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/web/lfs/LfsBlobStoreFactoryTest.java index 93eadf8935..9af1125696 100644 --- a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/web/lfs/LfsBlobStoreFactoryTest.java +++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/web/lfs/LfsBlobStoreFactoryTest.java @@ -71,7 +71,7 @@ public class LfsBlobStoreFactoryTest { // the return value (and should not be part of this test) verify(blobStoreFactory).getStore(argThat(blobStoreParameters -> { assertThat(blobStoreParameters.getName()).isEqualTo("the-id-git-lfs"); - assertThat(blobStoreParameters.getRepository()).isEqualTo(repository); + assertThat(blobStoreParameters.getRepositoryId()).isEqualTo("the-id"); return true; })); diff --git a/scm-webapp/src/main/java/sonia/scm/boot/BootstrapModule.java b/scm-webapp/src/main/java/sonia/scm/boot/BootstrapModule.java index 57c05b9d21..29f5770378 100644 --- a/scm-webapp/src/main/java/sonia/scm/boot/BootstrapModule.java +++ b/scm-webapp/src/main/java/sonia/scm/boot/BootstrapModule.java @@ -24,6 +24,8 @@ import sonia.scm.store.FileBlobStoreFactory; import sonia.scm.store.JAXBConfigurationEntryStoreFactory; import sonia.scm.store.JAXBConfigurationStoreFactory; import sonia.scm.store.JAXBDataStoreFactory; +import sonia.scm.update.V1PropertyDAO; +import sonia.scm.update.xml.XmlV1PropertyDAO; public class BootstrapModule extends AbstractModule { @@ -60,6 +62,7 @@ public class BootstrapModule extends AbstractModule { bind(DataStoreFactory.class, JAXBDataStoreFactory.class); bind(BlobStoreFactory.class, FileBlobStoreFactory.class); bind(PluginLoader.class).toInstance(pluginLoader); + bind(V1PropertyDAO.class, XmlV1PropertyDAO.class); } private void bind(Class clazz, Class defaultImplementation) { diff --git a/scm-webapp/src/main/java/sonia/scm/update/CoreUpdateStep.java b/scm-webapp/src/main/java/sonia/scm/update/CoreUpdateStep.java new file mode 100644 index 0000000000..c0468eef39 --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/update/CoreUpdateStep.java @@ -0,0 +1,6 @@ +package sonia.scm.update; + +import sonia.scm.migration.UpdateStep; + +public interface CoreUpdateStep extends UpdateStep { +} diff --git a/scm-webapp/src/main/java/sonia/scm/update/UpdateEngine.java b/scm-webapp/src/main/java/sonia/scm/update/UpdateEngine.java index 910d9ee054..a3c0ffd875 100644 --- a/scm-webapp/src/main/java/sonia/scm/update/UpdateEngine.java +++ b/scm-webapp/src/main/java/sonia/scm/update/UpdateEngine.java @@ -32,12 +32,20 @@ public class UpdateEngine { private List sortSteps(Set steps) { LOG.trace("sorting available update steps:"); List sortedSteps = steps.stream() - .sorted(Comparator.comparing(UpdateStep::getTargetVersion).reversed()) + .sorted( + Comparator + .comparing(UpdateStep::getTargetVersion) + .thenComparing(this::isCoreUpdateStep) + .reversed()) .collect(toList()); sortedSteps.forEach(step -> LOG.trace("{} for version {}", step.getAffectedDataType(), step.getTargetVersion())); return sortedSteps; } + private boolean isCoreUpdateStep(UpdateStep updateStep) { + return updateStep instanceof CoreUpdateStep; + } + public void update() { steps .stream() diff --git a/scm-webapp/src/main/java/sonia/scm/update/group/XmlGroupV1UpdateStep.java b/scm-webapp/src/main/java/sonia/scm/update/group/XmlGroupV1UpdateStep.java index 7130ead4a9..aa853a8b55 100644 --- a/scm-webapp/src/main/java/sonia/scm/update/group/XmlGroupV1UpdateStep.java +++ b/scm-webapp/src/main/java/sonia/scm/update/group/XmlGroupV1UpdateStep.java @@ -11,7 +11,7 @@ import sonia.scm.plugin.Extension; import sonia.scm.store.ConfigurationEntryStore; import sonia.scm.store.ConfigurationEntryStoreFactory; import sonia.scm.store.StoreConstants; -import sonia.scm.update.properties.V1Properties; +import sonia.scm.update.V1Properties; import sonia.scm.version.Version; import javax.inject.Inject; diff --git a/scm-webapp/src/main/java/sonia/scm/update/repository/V1Repository.java b/scm-webapp/src/main/java/sonia/scm/update/repository/V1Repository.java index 256e2ad397..4ce823bd33 100644 --- a/scm-webapp/src/main/java/sonia/scm/update/repository/V1Repository.java +++ b/scm-webapp/src/main/java/sonia/scm/update/repository/V1Repository.java @@ -1,6 +1,6 @@ package sonia.scm.update.repository; -import sonia.scm.update.properties.V1Properties; +import sonia.scm.update.V1Properties; import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; diff --git a/scm-webapp/src/main/java/sonia/scm/update/repository/XmlRepositoryV1UpdateStep.java b/scm-webapp/src/main/java/sonia/scm/update/repository/XmlRepositoryV1UpdateStep.java index ad20b59529..6882d7b1ca 100644 --- a/scm-webapp/src/main/java/sonia/scm/update/repository/XmlRepositoryV1UpdateStep.java +++ b/scm-webapp/src/main/java/sonia/scm/update/repository/XmlRepositoryV1UpdateStep.java @@ -13,7 +13,9 @@ import sonia.scm.repository.xml.XmlRepositoryDAO; import sonia.scm.store.ConfigurationEntryStore; import sonia.scm.store.ConfigurationEntryStoreFactory; import sonia.scm.store.StoreConstants; -import sonia.scm.update.properties.V1Properties; +import sonia.scm.update.CoreUpdateStep; +import sonia.scm.update.V1Properties; +import sonia.scm.update.RepositoryV1PropertyReader; import sonia.scm.version.Version; import javax.inject.Inject; @@ -56,7 +58,7 @@ import static sonia.scm.version.Version.parse; * */ @Extension -public class XmlRepositoryV1UpdateStep implements UpdateStep { +public class XmlRepositoryV1UpdateStep implements CoreUpdateStep { private static Logger LOG = LoggerFactory.getLogger(XmlRepositoryV1UpdateStep.class); @@ -80,7 +82,7 @@ public class XmlRepositoryV1UpdateStep implements UpdateStep { this.injector = injector; this.propertyStore = configurationEntryStoreFactory .withType(V1Properties.class) - .withName("repository-properties-v1") + .withName(new RepositoryV1PropertyReader().getStoreName()) .build(); } diff --git a/scm-webapp/src/main/java/sonia/scm/update/user/XmlUserV1UpdateStep.java b/scm-webapp/src/main/java/sonia/scm/update/user/XmlUserV1UpdateStep.java index 24d96d3654..70bdb55059 100644 --- a/scm-webapp/src/main/java/sonia/scm/update/user/XmlUserV1UpdateStep.java +++ b/scm-webapp/src/main/java/sonia/scm/update/user/XmlUserV1UpdateStep.java @@ -10,7 +10,7 @@ import sonia.scm.security.AssignedPermission; import sonia.scm.store.ConfigurationEntryStore; import sonia.scm.store.ConfigurationEntryStoreFactory; import sonia.scm.store.StoreConstants; -import sonia.scm.update.properties.V1Properties; +import sonia.scm.update.V1Properties; import sonia.scm.user.User; import sonia.scm.user.xml.XmlUserDAO; import sonia.scm.version.Version; diff --git a/scm-webapp/src/test/java/sonia/scm/update/UpdateEngineTest.java b/scm-webapp/src/test/java/sonia/scm/update/UpdateEngineTest.java index 746eabc2ea..f94d7cd27a 100644 --- a/scm-webapp/src/test/java/sonia/scm/update/UpdateEngineTest.java +++ b/scm-webapp/src/test/java/sonia/scm/update/UpdateEngineTest.java @@ -32,7 +32,21 @@ class UpdateEngineTest { updateEngine.update(); assertThat(processedUpdates) - .containsExactly("1.1.0", "1.1.1", "1.2.0"); + .containsExactly("test:1.1.0", "test:1.1.1", "test:1.2.0"); + } + + @Test + void shouldProcessCoreStepsBeforeOther() { + LinkedHashSet updateSteps = new LinkedHashSet<>(); + + updateSteps.add(new FixedVersionUpdateStep("test", "1.2.0")); + updateSteps.add(new CoreFixedVersionUpdateStep("core", "1.2.0")); + + UpdateEngine updateEngine = new UpdateEngine(updateSteps, storeFactory); + updateEngine.update(); + + assertThat(processedUpdates) + .containsExactly("core:1.2.0", "test:1.2.0"); } @Test @@ -67,7 +81,7 @@ class UpdateEngineTest { updateEngine = new UpdateEngine(updateSteps, storeFactory); updateEngine.update(); - assertThat(processedUpdates).containsExactly("1.1.1"); + assertThat(processedUpdates).containsExactly("other:1.1.1"); } class FixedVersionUpdateStep implements UpdateStep { @@ -91,7 +105,13 @@ class UpdateEngineTest { @Override public void doUpdate() { - processedUpdates.add(version); + processedUpdates.add(type + ":" + version); + } + } + + class CoreFixedVersionUpdateStep extends FixedVersionUpdateStep implements CoreUpdateStep { + CoreFixedVersionUpdateStep(String type, String version) { + super(type, version); } } } diff --git a/scm-webapp/src/test/java/sonia/scm/update/group/XmlGroupV1UpdateStepTest.java b/scm-webapp/src/test/java/sonia/scm/update/group/XmlGroupV1UpdateStepTest.java index 415bd508eb..9d550e20b1 100644 --- a/scm-webapp/src/test/java/sonia/scm/update/group/XmlGroupV1UpdateStepTest.java +++ b/scm-webapp/src/test/java/sonia/scm/update/group/XmlGroupV1UpdateStepTest.java @@ -14,8 +14,8 @@ import sonia.scm.group.xml.XmlGroupDAO; import sonia.scm.store.ConfigurationEntryStore; import sonia.scm.store.InMemoryConfigurationEntryStoreFactory; import sonia.scm.update.UpdateStepTestUtil; -import sonia.scm.update.properties.V1Properties; -import sonia.scm.update.properties.V1Property; +import sonia.scm.update.V1Properties; +import sonia.scm.update.V1Property; import javax.xml.bind.JAXBException; import java.io.IOException; @@ -89,13 +89,10 @@ class XmlGroupV1UpdateStepTest { @Test void shouldExtractProperties() throws JAXBException { updateStep.doUpdate(); - ConfigurationEntryStore propertiesStore = storeFactory.get("group-properties-v1"); - assertThat(propertiesStore.get("normals")) - .isNotNull() - .extracting(V1Properties::getProperties) - .first() - .asList() - .contains(new V1Property("mostly", "humans")); + ConfigurationEntryStore propertiesStore = storeFactory.get("group-properties-v1"); + V1Properties properties = propertiesStore.get("normals"); + assertThat(properties).isNotNull(); + assertThat(properties.get("mostly")).isEqualTo("humans"); } } diff --git a/scm-webapp/src/test/java/sonia/scm/update/user/XmlUserV1UpdateStepTest.java b/scm-webapp/src/test/java/sonia/scm/update/user/XmlUserV1UpdateStepTest.java index ec26db00a2..ce3a4a391e 100644 --- a/scm-webapp/src/test/java/sonia/scm/update/user/XmlUserV1UpdateStepTest.java +++ b/scm-webapp/src/test/java/sonia/scm/update/user/XmlUserV1UpdateStepTest.java @@ -13,8 +13,8 @@ import sonia.scm.security.AssignedPermission; import sonia.scm.store.ConfigurationEntryStore; import sonia.scm.store.InMemoryConfigurationEntryStoreFactory; import sonia.scm.update.UpdateStepTestUtil; -import sonia.scm.update.properties.V1Properties; -import sonia.scm.update.properties.V1Property; +import sonia.scm.update.V1Properties; +import sonia.scm.update.V1Property; import sonia.scm.user.User; import sonia.scm.user.xml.XmlUserDAO; @@ -105,14 +105,10 @@ class XmlUserV1UpdateStepTest { void shouldExtractProperties() throws JAXBException { updateStep.doUpdate(); ConfigurationEntryStore propertiesStore = storeFactory.get("user-properties-v1"); - assertThat(propertiesStore.get("dent")) - .isNotNull() - .extracting(V1Properties::getProperties) - .first() - .asList() - .contains( - new V1Property("born.on", "earth"), - new V1Property("last.seen", "end of the universe")); + V1Properties properties = propertiesStore.get("dent"); + assertThat(properties).isNotNull(); + assertThat(properties.get("born.on")).isEqualTo("earth"); + assertThat(properties.get("last.seen")).isEqualTo("end of the universe"); } } From 2aac002a41e9f85752bbfb3610fa0b92e8516829 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Fri, 21 Jun 2019 10:51:20 +0200 Subject: [PATCH 05/15] Introduce key based filters for property access --- .../MapBasedPropertyReaderInstance.java | 36 +++++++++++ .../update/RepositoryV1PropertyReader.java | 9 +-- .../java/sonia/scm/update/V1Properties.java | 22 ++++++- .../sonia/scm/update/V1PropertyReader.java | 4 ++ .../MapBasedPropertyReaderInstanceTest.java | 61 +++++++++++++++++++ 5 files changed, 123 insertions(+), 9 deletions(-) create mode 100644 scm-core/src/main/java/sonia/scm/update/MapBasedPropertyReaderInstance.java create mode 100644 scm-core/src/test/java/sonia/scm/update/MapBasedPropertyReaderInstanceTest.java diff --git a/scm-core/src/main/java/sonia/scm/update/MapBasedPropertyReaderInstance.java b/scm-core/src/main/java/sonia/scm/update/MapBasedPropertyReaderInstance.java new file mode 100644 index 0000000000..65e6a71958 --- /dev/null +++ b/scm-core/src/main/java/sonia/scm/update/MapBasedPropertyReaderInstance.java @@ -0,0 +1,36 @@ +package sonia.scm.update; + +import java.util.Map; +import java.util.function.BiConsumer; +import java.util.stream.Stream; + +public class MapBasedPropertyReaderInstance implements V1PropertyReader.Instance { + private final Stream> all; + + public MapBasedPropertyReaderInstance(Map all) { + this(all.entrySet().stream()); + } + + private MapBasedPropertyReaderInstance(Stream> all) { + this.all = all; + } + + @Override + public void forEachEntry(BiConsumer propertiesForNameConsumer) { + all.forEach(e -> call(e.getKey(), e.getValue(), propertiesForNameConsumer)); + } + + @Override + public V1PropertyReader.Instance havingAnyOf(String... keys) { + return new MapBasedPropertyReaderInstance(all.filter(e -> e.getValue().hasAny(keys))); + } + + @Override + public V1PropertyReader.Instance havingAllOf(String... keys) { + return new MapBasedPropertyReaderInstance(all.filter(e -> e.getValue().hasAll(keys))); + } + + private void call(String repositoryId, V1Properties properties, BiConsumer propertiesForNameConsumer) { + propertiesForNameConsumer.accept(repositoryId, properties); + } +} diff --git a/scm-core/src/main/java/sonia/scm/update/RepositoryV1PropertyReader.java b/scm-core/src/main/java/sonia/scm/update/RepositoryV1PropertyReader.java index cb9f2d01d9..5faf4500cd 100644 --- a/scm-core/src/main/java/sonia/scm/update/RepositoryV1PropertyReader.java +++ b/scm-core/src/main/java/sonia/scm/update/RepositoryV1PropertyReader.java @@ -1,7 +1,6 @@ package sonia.scm.update; import java.util.Map; -import java.util.function.BiConsumer; public class RepositoryV1PropertyReader implements V1PropertyReader { @Override @@ -11,13 +10,7 @@ public class RepositoryV1PropertyReader implements V1PropertyReader { @Override public Instance createInstance(Map all) { - return propertiesForNameConsumer -> - all - .entrySet() - .forEach(e -> call(e.getKey(), e.getValue(), propertiesForNameConsumer)); + return new MapBasedPropertyReaderInstance(all); } - private void call(String repositoryId, V1Properties properties, BiConsumer propertiesForNameConsumer) { - propertiesForNameConsumer.accept(repositoryId, properties); - } } diff --git a/scm-core/src/main/java/sonia/scm/update/V1Properties.java b/scm-core/src/main/java/sonia/scm/update/V1Properties.java index 5776fc91c7..984a1e0fdb 100644 --- a/scm-core/src/main/java/sonia/scm/update/V1Properties.java +++ b/scm-core/src/main/java/sonia/scm/update/V1Properties.java @@ -6,7 +6,8 @@ import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlRootElement; import java.util.List; -import static java.util.Collections.unmodifiableList; +import static java.util.Arrays.asList; +import static java.util.Arrays.stream; @XmlAccessorType(XmlAccessType.FIELD) @XmlRootElement(name = "properties") @@ -14,7 +15,26 @@ public class V1Properties { @XmlElement(name = "item") private List properties; + public V1Properties() { + } + + public V1Properties(V1Property... properties) { + this(asList(properties)); + } + + public V1Properties(List properties) { + this.properties = properties; + } + public String get(String key) { return properties.stream().filter(p -> key.equals(p.getKey())).map(V1Property::getValue).findFirst().orElse(null); } + + public boolean hasAny(String[] keys) { + return properties.stream().anyMatch(p -> stream(keys).anyMatch(k -> k.equals(p.getKey()))); + } + + public boolean hasAll(String[] keys) { + return stream(keys).allMatch(k -> properties.stream().anyMatch(p -> k.equals(p.getKey()))); + } } diff --git a/scm-core/src/main/java/sonia/scm/update/V1PropertyReader.java b/scm-core/src/main/java/sonia/scm/update/V1PropertyReader.java index 2d20a9aeed..2c8381e247 100644 --- a/scm-core/src/main/java/sonia/scm/update/V1PropertyReader.java +++ b/scm-core/src/main/java/sonia/scm/update/V1PropertyReader.java @@ -11,5 +11,9 @@ public interface V1PropertyReader { interface Instance { void forEachEntry(BiConsumer propertiesForNameConsumer); + + Instance havingAnyOf(String... keys); + + Instance havingAllOf(String... keys); } } diff --git a/scm-core/src/test/java/sonia/scm/update/MapBasedPropertyReaderInstanceTest.java b/scm-core/src/test/java/sonia/scm/update/MapBasedPropertyReaderInstanceTest.java new file mode 100644 index 0000000000..f214203866 --- /dev/null +++ b/scm-core/src/test/java/sonia/scm/update/MapBasedPropertyReaderInstanceTest.java @@ -0,0 +1,61 @@ +package sonia.scm.update; + +import com.google.common.collect.ImmutableMap; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.util.HashMap; +import java.util.Map; +import java.util.function.BiConsumer; + +class MapBasedPropertyReaderInstanceTest { + + Map executedCalls = new HashMap<>(); + + BiConsumer consumer = (key, properties) -> executedCalls.put(key, properties); + + MapBasedPropertyReaderInstance instance = new MapBasedPropertyReaderInstance( + ImmutableMap.of( + "o1", new V1Properties( + new V1Property("k1", "v1-1"), + new V1Property("k2", "v1-2"), + new V1Property("k3", "v1-3") + ), + "o2", new V1Properties( + new V1Property("k1", "v2-1"), + new V1Property("k2", "v2-2") + ), + "o3", new V1Properties( + new V1Property("k1", "v3-1") + ) + ) + ); + + @Test + void shouldCallBackForEachObjectIfNotFiltered() { + instance.forEachEntry(consumer); + + Assertions.assertThat(executedCalls).hasSize(3); + } + + @Test + void shouldCallBackOnlyObjectsHavingAtLeastOneOfGivenKey() { + instance.havingAnyOf("k2", "k3").forEachEntry(consumer); + + Assertions.assertThat(executedCalls).hasSize(2).containsKeys("o1", "o2"); + } + + @Test + void shouldCallBackOnlyObjectsHavingAllOfGivenKey() { + instance.havingAllOf("k2", "k3").forEachEntry(consumer); + + Assertions.assertThat(executedCalls).hasSize(1).containsKeys("o1"); + } + + @Test + void shouldCombineFilters() { + instance.havingAnyOf("k2", "k3").havingAllOf("k3").forEachEntry(consumer); + + Assertions.assertThat(executedCalls).hasSize(1).containsKeys("o1"); + } +} From f8954846c42e56d5ad057ebde5db8cb072c1674b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Fri, 21 Jun 2019 11:09:41 +0200 Subject: [PATCH 06/15] Add test support for plugin migration --- .../java/sonia/scm/update/V1Properties.java | 15 ++++++- .../scm/update/V1PropertyDaoTestUtil.java | 40 +++++++++++++++++++ 2 files changed, 54 insertions(+), 1 deletion(-) create mode 100644 scm-test/src/main/java/sonia/scm/update/V1PropertyDaoTestUtil.java diff --git a/scm-core/src/main/java/sonia/scm/update/V1Properties.java b/scm-core/src/main/java/sonia/scm/update/V1Properties.java index 984a1e0fdb..9347d584ce 100644 --- a/scm-core/src/main/java/sonia/scm/update/V1Properties.java +++ b/scm-core/src/main/java/sonia/scm/update/V1Properties.java @@ -5,6 +5,7 @@ import javax.xml.bind.annotation.XmlAccessorType; import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlRootElement; import java.util.List; +import java.util.Optional; import static java.util.Arrays.asList; import static java.util.Arrays.stream; @@ -27,7 +28,19 @@ public class V1Properties { } public String get(String key) { - return properties.stream().filter(p -> key.equals(p.getKey())).map(V1Property::getValue).findFirst().orElse(null); + return getOptional(key).orElse(null); + } + + public Optional getOptional(String key) { + return properties.stream().filter(p -> key.equals(p.getKey())).map(V1Property::getValue).findFirst(); + } + + public Optional getBoolean(String key) { + return getOptional(key).map(Boolean::valueOf); + } + + public > Optional getEnum(String key, Class enumType) { + return getOptional(key).map(name -> Enum.valueOf(enumType, name)); } public boolean hasAny(String[] keys) { diff --git a/scm-test/src/main/java/sonia/scm/update/V1PropertyDaoTestUtil.java b/scm-test/src/main/java/sonia/scm/update/V1PropertyDaoTestUtil.java new file mode 100644 index 0000000000..44020ca8a1 --- /dev/null +++ b/scm-test/src/main/java/sonia/scm/update/V1PropertyDaoTestUtil.java @@ -0,0 +1,40 @@ +package sonia.scm.update; + +import java.util.HashMap; +import java.util.Map; +import java.util.stream.Collectors; + +import static java.util.Arrays.stream; +import static org.mockito.ArgumentMatchers.argThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class V1PropertyDaoTestUtil { + + private final V1PropertyDAO propertyDAO = mock(V1PropertyDAO.class); + + public V1PropertyDAO getPropertyDAO() { + return propertyDAO; + } + + public void mockRepositoryProperties(PropertiesForRepository... mockedPropertiesForRepositories) { + Map map = new HashMap<>(); + stream(mockedPropertiesForRepositories).forEach(p -> map.put(p.repositoryId, p.asProperties())); + V1PropertyReader.Instance v1PropertyReader = new MapBasedPropertyReaderInstance(map); + when(propertyDAO.getProperties(argThat(argument -> argument instanceof RepositoryV1PropertyReader))).thenReturn(v1PropertyReader); + } + + public static class PropertiesForRepository { + private final String repositoryId; + private final Map properties; + + public PropertiesForRepository(String repositoryId, Map properties) { + this.repositoryId = repositoryId; + this.properties = properties; + } + + V1Properties asProperties() { + return new V1Properties(properties.entrySet().stream().map(e -> new V1Property(e.getKey(), e.getValue())).collect(Collectors.toList())); + } + } +} From 91728920975bd750ab01ac366468e00e30a633c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Fri, 21 Jun 2019 11:33:48 +0200 Subject: [PATCH 07/15] Fix NPE for repositories without properties --- .../src/main/java/sonia/scm/update/V1Properties.java | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/scm-core/src/main/java/sonia/scm/update/V1Properties.java b/scm-core/src/main/java/sonia/scm/update/V1Properties.java index 9347d584ce..7df65efdb6 100644 --- a/scm-core/src/main/java/sonia/scm/update/V1Properties.java +++ b/scm-core/src/main/java/sonia/scm/update/V1Properties.java @@ -6,9 +6,11 @@ import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlRootElement; import java.util.List; import java.util.Optional; +import java.util.stream.Stream; import static java.util.Arrays.asList; import static java.util.Arrays.stream; +import static java.util.stream.Stream.empty; @XmlAccessorType(XmlAccessType.FIELD) @XmlRootElement(name = "properties") @@ -32,7 +34,7 @@ public class V1Properties { } public Optional getOptional(String key) { - return properties.stream().filter(p -> key.equals(p.getKey())).map(V1Property::getValue).findFirst(); + return streamProps().filter(p -> key.equals(p.getKey())).map(V1Property::getValue).findFirst(); } public Optional getBoolean(String key) { @@ -44,10 +46,14 @@ public class V1Properties { } public boolean hasAny(String[] keys) { - return properties.stream().anyMatch(p -> stream(keys).anyMatch(k -> k.equals(p.getKey()))); + return streamProps().anyMatch(p -> stream(keys).anyMatch(k -> k.equals(p.getKey()))); } public boolean hasAll(String[] keys) { - return stream(keys).allMatch(k -> properties.stream().anyMatch(p -> k.equals(p.getKey()))); + return stream(keys).allMatch(k -> streamProps().anyMatch(p -> k.equals(p.getKey()))); + } + + private Stream streamProps() { + return properties == null? empty(): properties.stream(); } } From 75cd5165e32edfc5f54810eea2c745aebfcb02af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Fri, 21 Jun 2019 14:35:57 +0200 Subject: [PATCH 08/15] Add file tools for v1 xml stores --- .../sonia/scm/update/PropertyFileAccess.java | 24 +++++ .../scm/store/JAXBPropertyFileAccess.java | 88 +++++++++++++++ .../scm/store/JAXBPropertyFileAccessTest.java | 102 ++++++++++++++++++ .../java/sonia/scm/boot/BootstrapModule.java | 3 + 4 files changed, 217 insertions(+) create mode 100644 scm-core/src/main/java/sonia/scm/update/PropertyFileAccess.java create mode 100644 scm-dao-xml/src/main/java/sonia/scm/store/JAXBPropertyFileAccess.java create mode 100644 scm-dao-xml/src/test/java/sonia/scm/store/JAXBPropertyFileAccessTest.java diff --git a/scm-core/src/main/java/sonia/scm/update/PropertyFileAccess.java b/scm-core/src/main/java/sonia/scm/update/PropertyFileAccess.java new file mode 100644 index 0000000000..52207beaa0 --- /dev/null +++ b/scm-core/src/main/java/sonia/scm/update/PropertyFileAccess.java @@ -0,0 +1,24 @@ +package sonia.scm.update; + +import java.io.IOException; +import java.nio.file.Path; + +public interface PropertyFileAccess { + Target renameGlobalConfigurationFrom(String oldName); + + interface Target { + void to(String newName) throws IOException; + } + + StoreFileTools forStoreName(String name); + + interface StoreFileTools { + void forStoreFiles(FileConsumer storeFileConsumer) throws IOException; + + void moveAsRepositoryStore(Path storeFile, String repositoryId) throws IOException; + } + + public interface FileConsumer { + void accept(Path file, String repositoryId) throws IOException; + } +} diff --git a/scm-dao-xml/src/main/java/sonia/scm/store/JAXBPropertyFileAccess.java b/scm-dao-xml/src/main/java/sonia/scm/store/JAXBPropertyFileAccess.java new file mode 100644 index 0000000000..8a3b3b96ad --- /dev/null +++ b/scm-dao-xml/src/main/java/sonia/scm/store/JAXBPropertyFileAccess.java @@ -0,0 +1,88 @@ +package sonia.scm.store; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import sonia.scm.SCMContextProvider; +import sonia.scm.repository.RepositoryLocationResolver; +import sonia.scm.update.PropertyFileAccess; +import sonia.scm.util.IOUtil; + +import javax.inject.Inject; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; + +public class JAXBPropertyFileAccess implements PropertyFileAccess { + + private static final Logger LOG = LoggerFactory.getLogger(JAXBPropertyFileAccess.class); + + public static final String XML_FILENAME_SUFFIX = ".xml"; + private final SCMContextProvider contextProvider; + private final RepositoryLocationResolver locationResolver; + + @Inject + public JAXBPropertyFileAccess(SCMContextProvider contextProvider, RepositoryLocationResolver locationResolver) { + this.contextProvider = contextProvider; + this.locationResolver = locationResolver; + } + + @Override + public Target renameGlobalConfigurationFrom(String oldName) { + return newName -> { + Path configDir = contextProvider.getBaseDirectory().toPath().resolve(StoreConstants.CONFIG_DIRECTORY_NAME); + Path oldConfigFile = configDir.resolve(oldName + XML_FILENAME_SUFFIX); + Path newConfigFile = configDir.resolve(oldName + XML_FILENAME_SUFFIX); + Files.move(oldConfigFile, newConfigFile); + }; + } + + @Override + public StoreFileTools forStoreName(String storeName) { + return new StoreFileTools() { + @Override + public void forStoreFiles(FileConsumer storeFileConsumer) throws IOException { + Path v1storeDir = computeV1StoreDir(); + if (Files.exists(v1storeDir) && Files.isDirectory(v1storeDir)) { + Files.list(v1storeDir).filter(p -> p.toString().endsWith(XML_FILENAME_SUFFIX)).forEach(p -> { + try { + String storeName = extractStoreName(p); + storeFileConsumer.accept(p, storeName); + } catch (IOException e) { + throw new RuntimeException("could not call consumer for store file " + p + " with name " + storeName, e); + } + }); + } + } + + @Override + public void moveAsRepositoryStore(Path storeFile, String repositoryId) throws IOException { + Path repositoryLocation; + try { + repositoryLocation = locationResolver + .forClass(Path.class) + .getLocation(repositoryId); + } catch (IllegalStateException e) { + LOG.info("ignoring store file {} because there is no repository location for repository id {}", storeFile, repositoryId); + return; + } + Path target = repositoryLocation + .resolve(Store.DATA.getRepositoryStoreDirectory()) + .resolve(storeName); + IOUtil.mkdirs(target.toFile()); + Path resolvedSourceFile = computeV1StoreDir().resolve(storeFile); + Path resolvedTargetFile = target.resolve(storeFile.getFileName()); + LOG.trace("moving file {} to {}", resolvedSourceFile, resolvedTargetFile); + Files.move(resolvedSourceFile, resolvedTargetFile); + } + + private Path computeV1StoreDir() { + return contextProvider.getBaseDirectory().toPath().resolve("var").resolve("data").resolve(storeName); + } + + private String extractStoreName(Path p) { + String fileName = p.getFileName().toString(); + return fileName.substring(0, fileName.length() - XML_FILENAME_SUFFIX.length()); + } + }; + } +} diff --git a/scm-dao-xml/src/test/java/sonia/scm/store/JAXBPropertyFileAccessTest.java b/scm-dao-xml/src/test/java/sonia/scm/store/JAXBPropertyFileAccessTest.java new file mode 100644 index 0000000000..69c3e648df --- /dev/null +++ b/scm-dao-xml/src/test/java/sonia/scm/store/JAXBPropertyFileAccessTest.java @@ -0,0 +1,102 @@ +package sonia.scm.store; + +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junitpioneer.jupiter.TempDirectory; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import sonia.scm.SCMContextProvider; +import sonia.scm.io.DefaultFileSystem; +import sonia.scm.repository.InitialRepositoryLocationResolver; +import sonia.scm.repository.RepositoryLocationResolver; +import sonia.scm.repository.xml.PathBasedRepositoryLocationResolver; +import sonia.scm.update.PropertyFileAccess; +import sonia.scm.util.IOUtil; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.lenient; + +@ExtendWith(TempDirectory.class) +@ExtendWith(MockitoExtension.class) +class JAXBPropertyFileAccessTest { + + public static final String REPOSITORY_ID = "repoId"; + public static final String STORE_NAME = "test"; + + @Mock + SCMContextProvider contextProvider; + + RepositoryLocationResolver locationResolver; + + JAXBPropertyFileAccess fileAccess; + + @BeforeEach + void initTempDir(@TempDirectory.TempDir Path tempDir) { + lenient().when(contextProvider.getBaseDirectory()).thenReturn(tempDir.toFile()); + lenient().when(contextProvider.resolve(any())).thenAnswer(invocation -> tempDir.resolve(invocation.getArgument(0).toString())); + + locationResolver = new PathBasedRepositoryLocationResolver(contextProvider, new InitialRepositoryLocationResolver(), new DefaultFileSystem());//new TempDirRepositoryLocationResolver(tempDir.toFile()); + + fileAccess = new JAXBPropertyFileAccess(contextProvider, locationResolver); + } + + @Nested + class ForExistingRepository { + + + @BeforeEach + void createRepositoryLocation() { + locationResolver.forClass(Path.class).createLocation(REPOSITORY_ID); + } + + @Test + void shouldMoveStoreFileToRepositoryBasedLocation(@TempDirectory.TempDir Path tempDir) throws IOException { + createV1StoreFile(tempDir, "myStore.xml"); + + fileAccess.forStoreName(STORE_NAME).moveAsRepositoryStore(Paths.get("myStore.xml"), REPOSITORY_ID); + + Assertions.assertThat(tempDir.resolve("repositories").resolve(REPOSITORY_ID).resolve("store").resolve("data").resolve(STORE_NAME).resolve("myStore.xml")).exists(); + } + + @Test + void shouldMoveAllStoreFilesToRepositoryBasedLocations(@TempDirectory.TempDir Path tempDir) throws IOException { + locationResolver.forClass(Path.class).createLocation("repoId2"); + + createV1StoreFile(tempDir, REPOSITORY_ID + ".xml"); + createV1StoreFile(tempDir, "repoId2.xml"); + + PropertyFileAccess.StoreFileTools statisticStoreAccess = fileAccess.forStoreName(STORE_NAME); + statisticStoreAccess.forStoreFiles(statisticStoreAccess::moveAsRepositoryStore); + + Assertions.assertThat(tempDir.resolve("repositories").resolve(REPOSITORY_ID).resolve("store").resolve("data").resolve(STORE_NAME).resolve("repoId.xml")).exists(); + Assertions.assertThat(tempDir.resolve("repositories").resolve("repoId2").resolve("store").resolve("data").resolve(STORE_NAME).resolve("repoId2.xml")).exists(); + } + } + + private void createV1StoreFile(@TempDirectory.TempDir Path tempDir, String name) throws IOException { + Path v1Dir = tempDir.resolve("var").resolve("data").resolve(STORE_NAME); + IOUtil.mkdirs(v1Dir.toFile()); + Files.createFile(v1Dir.resolve(name)); + } + + @Nested + class ForMissingRepository { + + @Test + void shouldIgnoreStoreFile(@TempDirectory.TempDir Path tempDir) throws IOException { + createV1StoreFile(tempDir, "myStore.xml"); + + fileAccess.forStoreName(STORE_NAME).moveAsRepositoryStore(Paths.get("myStore.xml"), REPOSITORY_ID); + + Assertions.assertThat(tempDir.resolve("repositories").resolve(REPOSITORY_ID).resolve("store").resolve("data").resolve(STORE_NAME).resolve("myStore.xml")).doesNotExist(); + } + } +} diff --git a/scm-webapp/src/main/java/sonia/scm/boot/BootstrapModule.java b/scm-webapp/src/main/java/sonia/scm/boot/BootstrapModule.java index 29f5770378..4765d9b96e 100644 --- a/scm-webapp/src/main/java/sonia/scm/boot/BootstrapModule.java +++ b/scm-webapp/src/main/java/sonia/scm/boot/BootstrapModule.java @@ -24,6 +24,8 @@ import sonia.scm.store.FileBlobStoreFactory; import sonia.scm.store.JAXBConfigurationEntryStoreFactory; import sonia.scm.store.JAXBConfigurationStoreFactory; import sonia.scm.store.JAXBDataStoreFactory; +import sonia.scm.store.JAXBPropertyFileAccess; +import sonia.scm.update.PropertyFileAccess; import sonia.scm.update.V1PropertyDAO; import sonia.scm.update.xml.XmlV1PropertyDAO; @@ -63,6 +65,7 @@ public class BootstrapModule extends AbstractModule { bind(BlobStoreFactory.class, FileBlobStoreFactory.class); bind(PluginLoader.class).toInstance(pluginLoader); bind(V1PropertyDAO.class, XmlV1PropertyDAO.class); + bind(PropertyFileAccess.class, JAXBPropertyFileAccess.class); } private void bind(Class clazz, Class defaultImplementation) { From 56996395f11186c63339c56edf3a5d95e37fcaad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Fri, 21 Jun 2019 15:04:45 +0200 Subject: [PATCH 09/15] Close file stream --- .../scm/store/JAXBPropertyFileAccess.java | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/scm-dao-xml/src/main/java/sonia/scm/store/JAXBPropertyFileAccess.java b/scm-dao-xml/src/main/java/sonia/scm/store/JAXBPropertyFileAccess.java index 8a3b3b96ad..e974cf0148 100644 --- a/scm-dao-xml/src/main/java/sonia/scm/store/JAXBPropertyFileAccess.java +++ b/scm-dao-xml/src/main/java/sonia/scm/store/JAXBPropertyFileAccess.java @@ -11,6 +11,7 @@ import javax.inject.Inject; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; +import java.util.stream.Stream; public class JAXBPropertyFileAccess implements PropertyFileAccess { @@ -43,14 +44,16 @@ public class JAXBPropertyFileAccess implements PropertyFileAccess { public void forStoreFiles(FileConsumer storeFileConsumer) throws IOException { Path v1storeDir = computeV1StoreDir(); if (Files.exists(v1storeDir) && Files.isDirectory(v1storeDir)) { - Files.list(v1storeDir).filter(p -> p.toString().endsWith(XML_FILENAME_SUFFIX)).forEach(p -> { - try { - String storeName = extractStoreName(p); - storeFileConsumer.accept(p, storeName); - } catch (IOException e) { - throw new RuntimeException("could not call consumer for store file " + p + " with name " + storeName, e); - } - }); + try (Stream fileStream = Files.list(v1storeDir)) { + fileStream.filter(p -> p.toString().endsWith(XML_FILENAME_SUFFIX)).forEach(p -> { + try { + String storeName = extractStoreName(p); + storeFileConsumer.accept(p, storeName); + } catch (IOException e) { + throw new RuntimeException("could not call consumer for store file " + p + " with name " + storeName, e); + } + }); + } } } From e1eb7caeabb26984e80e77c927098f21fab28780 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Fri, 21 Jun 2019 15:15:08 +0200 Subject: [PATCH 10/15] Add documentation --- .../sonia/scm/update/PropertyFileAccess.java | 33 +++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/scm-core/src/main/java/sonia/scm/update/PropertyFileAccess.java b/scm-core/src/main/java/sonia/scm/update/PropertyFileAccess.java index 52207beaa0..726d589d02 100644 --- a/scm-core/src/main/java/sonia/scm/update/PropertyFileAccess.java +++ b/scm-core/src/main/java/sonia/scm/update/PropertyFileAccess.java @@ -4,21 +4,50 @@ import java.io.IOException; import java.nio.file.Path; public interface PropertyFileAccess { + + /** + * Use this to rename a configuration file. + * @param oldName The old file name. + * @return Object to specify the new file name. + */ Target renameGlobalConfigurationFrom(String oldName); interface Target { + /** + * Renames a file to the new name given here. + * @param newName The new file name. + * @throws IOException If the file could not be renamed. + */ void to(String newName) throws IOException; } + /** + * Creates a tool object for store migration from v1 to v2. + * @param name The name of the store to be handled. + * @return The tool object for the named store. + */ StoreFileTools forStoreName(String name); interface StoreFileTools { + /** + * Iterates over all found store files (that is, files ending with ".xml") and calls the + * given consumer for each file, giving the file and the name of the data file. + * @param storeFileConsumer This consumer will be called for each file found. + * @throws IOException May be thrown when an exception occurs. + */ void forStoreFiles(FileConsumer storeFileConsumer) throws IOException; + /** + * Moves a data file to the new location for a repository with the given id. If there + * is no directory for the given repository id, nothing will be done. + * @param storeFile The name of the store file. + * @param repositoryId The id of the repository as the new target for the store file. + * @throws IOException When the file could not be moved. + */ void moveAsRepositoryStore(Path storeFile, String repositoryId) throws IOException; } - public interface FileConsumer { - void accept(Path file, String repositoryId) throws IOException; + interface FileConsumer { + void accept(Path file, String storeName) throws IOException; } } From 553997e7f0dd2075457b6bccfb7a1d83fc81e3d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Fri, 21 Jun 2019 15:32:25 +0200 Subject: [PATCH 11/15] Add documentation --- .../sonia/scm/update/GroupV1PropertyReader.java | 15 +++++++++++++++ .../sonia/scm/update/UserV1PropertyReader.java | 15 +++++++++++++++ .../java/sonia/scm/update/V1PropertyDAO.java | 9 +++++++++ .../java/sonia/scm/update/V1PropertyReader.java | 16 ++++++++++++++++ .../scm/update/group/XmlGroupV1UpdateStep.java | 3 ++- .../repository/XmlRepositoryV1UpdateStep.java | 5 ++--- .../scm/update/user/XmlUserV1UpdateStep.java | 3 ++- 7 files changed, 61 insertions(+), 5 deletions(-) create mode 100644 scm-core/src/main/java/sonia/scm/update/GroupV1PropertyReader.java create mode 100644 scm-core/src/main/java/sonia/scm/update/UserV1PropertyReader.java diff --git a/scm-core/src/main/java/sonia/scm/update/GroupV1PropertyReader.java b/scm-core/src/main/java/sonia/scm/update/GroupV1PropertyReader.java new file mode 100644 index 0000000000..a7703c1b76 --- /dev/null +++ b/scm-core/src/main/java/sonia/scm/update/GroupV1PropertyReader.java @@ -0,0 +1,15 @@ +package sonia.scm.update; + +import java.util.Map; + +public class GroupV1PropertyReader implements V1PropertyReader { + @Override + public String getStoreName() { + return "group-properties-v1"; + } + + @Override + public Instance createInstance(Map all) { + return new MapBasedPropertyReaderInstance(all); + } +} diff --git a/scm-core/src/main/java/sonia/scm/update/UserV1PropertyReader.java b/scm-core/src/main/java/sonia/scm/update/UserV1PropertyReader.java new file mode 100644 index 0000000000..b29c311d1c --- /dev/null +++ b/scm-core/src/main/java/sonia/scm/update/UserV1PropertyReader.java @@ -0,0 +1,15 @@ +package sonia.scm.update; + +import java.util.Map; + +public class UserV1PropertyReader implements V1PropertyReader { + @Override + public String getStoreName() { + return "user-properties-v1"; + } + + @Override + public Instance createInstance(Map all) { + return new MapBasedPropertyReaderInstance(all); + } +} diff --git a/scm-core/src/main/java/sonia/scm/update/V1PropertyDAO.java b/scm-core/src/main/java/sonia/scm/update/V1PropertyDAO.java index b8cac19edf..46b1773c57 100644 --- a/scm-core/src/main/java/sonia/scm/update/V1PropertyDAO.java +++ b/scm-core/src/main/java/sonia/scm/update/V1PropertyDAO.java @@ -1,5 +1,14 @@ package sonia.scm.update; +/** + * Use this to access old properties from an instance of SCM-Manager v1. + */ public interface V1PropertyDAO { + /** + * Creates an instance of a property reader to process old properties. + * @param reader The reader for the origin of the properties (for example + * {@link V1PropertyReader#REPOSITORY_PROPERTY_READER} for properties of repositories). + * @return The reader instance. + */ V1PropertyReader.Instance getProperties(V1PropertyReader reader); } diff --git a/scm-core/src/main/java/sonia/scm/update/V1PropertyReader.java b/scm-core/src/main/java/sonia/scm/update/V1PropertyReader.java index 2c8381e247..2191c3a901 100644 --- a/scm-core/src/main/java/sonia/scm/update/V1PropertyReader.java +++ b/scm-core/src/main/java/sonia/scm/update/V1PropertyReader.java @@ -5,15 +5,31 @@ import java.util.function.BiConsumer; public interface V1PropertyReader { + V1PropertyReader REPOSITORY_PROPERTY_READER = new RepositoryV1PropertyReader(); + V1PropertyReader USER_PROPERTY_READER = new RepositoryV1PropertyReader(); + V1PropertyReader GROUP_PROPERTY_READER = new RepositoryV1PropertyReader(); + String getStoreName(); Instance createInstance(Map all); interface Instance { + /** + * Will call the given consumer for each id of the corresponding entity with its list of + * properties converted from v1. + * For example for repositories this will call the consumer with the id of each repository + * that had properties attached in v1. + */ void forEachEntry(BiConsumer propertiesForNameConsumer); + /** + * Filters for entities only having at least one property with a given key name. + */ Instance havingAnyOf(String... keys); + /** + * Filters for entities only having properties for all given key name. + */ Instance havingAllOf(String... keys); } } diff --git a/scm-webapp/src/main/java/sonia/scm/update/group/XmlGroupV1UpdateStep.java b/scm-webapp/src/main/java/sonia/scm/update/group/XmlGroupV1UpdateStep.java index aa853a8b55..23eb477e09 100644 --- a/scm-webapp/src/main/java/sonia/scm/update/group/XmlGroupV1UpdateStep.java +++ b/scm-webapp/src/main/java/sonia/scm/update/group/XmlGroupV1UpdateStep.java @@ -30,6 +30,7 @@ import java.util.Optional; import static java.util.Optional.empty; import static java.util.Optional.of; +import static sonia.scm.update.V1PropertyReader.GROUP_PROPERTY_READER; import static sonia.scm.version.Version.parse; @Extension @@ -51,7 +52,7 @@ public class XmlGroupV1UpdateStep implements UpdateStep { this.groupDAO = groupDAO; this.propertyStore = configurationEntryStoreFactory .withType(V1Properties.class) - .withName("group-properties-v1") + .withName(GROUP_PROPERTY_READER.getStoreName()) .build(); } diff --git a/scm-webapp/src/main/java/sonia/scm/update/repository/XmlRepositoryV1UpdateStep.java b/scm-webapp/src/main/java/sonia/scm/update/repository/XmlRepositoryV1UpdateStep.java index 6882d7b1ca..f7a4e1ed37 100644 --- a/scm-webapp/src/main/java/sonia/scm/update/repository/XmlRepositoryV1UpdateStep.java +++ b/scm-webapp/src/main/java/sonia/scm/update/repository/XmlRepositoryV1UpdateStep.java @@ -5,7 +5,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import sonia.scm.SCMContextProvider; import sonia.scm.migration.UpdateException; -import sonia.scm.migration.UpdateStep; import sonia.scm.plugin.Extension; import sonia.scm.repository.Repository; import sonia.scm.repository.RepositoryPermission; @@ -15,7 +14,6 @@ import sonia.scm.store.ConfigurationEntryStoreFactory; import sonia.scm.store.StoreConstants; import sonia.scm.update.CoreUpdateStep; import sonia.scm.update.V1Properties; -import sonia.scm.update.RepositoryV1PropertyReader; import sonia.scm.version.Version; import javax.inject.Inject; @@ -38,6 +36,7 @@ import java.util.stream.Stream; import static java.util.Collections.emptyList; import static java.util.Optional.empty; import static java.util.Optional.of; +import static sonia.scm.update.V1PropertyReader.REPOSITORY_PROPERTY_READER; import static sonia.scm.version.Version.parse; /** @@ -82,7 +81,7 @@ public class XmlRepositoryV1UpdateStep implements CoreUpdateStep { this.injector = injector; this.propertyStore = configurationEntryStoreFactory .withType(V1Properties.class) - .withName(new RepositoryV1PropertyReader().getStoreName()) + .withName(REPOSITORY_PROPERTY_READER.getStoreName()) .build(); } diff --git a/scm-webapp/src/main/java/sonia/scm/update/user/XmlUserV1UpdateStep.java b/scm-webapp/src/main/java/sonia/scm/update/user/XmlUserV1UpdateStep.java index 70bdb55059..b2da69fd9b 100644 --- a/scm-webapp/src/main/java/sonia/scm/update/user/XmlUserV1UpdateStep.java +++ b/scm-webapp/src/main/java/sonia/scm/update/user/XmlUserV1UpdateStep.java @@ -31,6 +31,7 @@ import java.util.Optional; import static java.util.Optional.empty; import static java.util.Optional.of; +import static sonia.scm.update.V1PropertyReader.USER_PROPERTY_READER; import static sonia.scm.version.Version.parse; @Extension @@ -50,7 +51,7 @@ public class XmlUserV1UpdateStep implements UpdateStep { this.configurationEntryStoreFactory = configurationEntryStoreFactory; this.propertyStore = configurationEntryStoreFactory .withType(V1Properties.class) - .withName("user-properties-v1") + .withName(USER_PROPERTY_READER.getStoreName()) .build(); } From bd2509e5818fa43c026eae2fdbf18facea069a13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Mon, 24 Jun 2019 09:28:24 +0200 Subject: [PATCH 12/15] Use correct reader instances --- scm-core/src/main/java/sonia/scm/update/V1PropertyReader.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scm-core/src/main/java/sonia/scm/update/V1PropertyReader.java b/scm-core/src/main/java/sonia/scm/update/V1PropertyReader.java index 2191c3a901..4e5cc55115 100644 --- a/scm-core/src/main/java/sonia/scm/update/V1PropertyReader.java +++ b/scm-core/src/main/java/sonia/scm/update/V1PropertyReader.java @@ -6,8 +6,8 @@ import java.util.function.BiConsumer; public interface V1PropertyReader { V1PropertyReader REPOSITORY_PROPERTY_READER = new RepositoryV1PropertyReader(); - V1PropertyReader USER_PROPERTY_READER = new RepositoryV1PropertyReader(); - V1PropertyReader GROUP_PROPERTY_READER = new RepositoryV1PropertyReader(); + V1PropertyReader USER_PROPERTY_READER = new UserV1PropertyReader(); + V1PropertyReader GROUP_PROPERTY_READER = new GroupV1PropertyReader(); String getStoreName(); From de8f15abf571b37816bcf17528c43fc881e3e3ba Mon Sep 17 00:00:00 2001 From: Eduard Heimbuch Date: Tue, 25 Jun 2019 09:12:57 +0200 Subject: [PATCH 13/15] fix renameGlobalConfigFile / add Test --- .../scm/store/JAXBPropertyFileAccess.java | 2 +- .../scm/store/JAXBPropertyFileAccessTest.java | 33 ++++++++++++++++--- 2 files changed, 29 insertions(+), 6 deletions(-) diff --git a/scm-dao-xml/src/main/java/sonia/scm/store/JAXBPropertyFileAccess.java b/scm-dao-xml/src/main/java/sonia/scm/store/JAXBPropertyFileAccess.java index e974cf0148..b8d30bc0c1 100644 --- a/scm-dao-xml/src/main/java/sonia/scm/store/JAXBPropertyFileAccess.java +++ b/scm-dao-xml/src/main/java/sonia/scm/store/JAXBPropertyFileAccess.java @@ -32,7 +32,7 @@ public class JAXBPropertyFileAccess implements PropertyFileAccess { return newName -> { Path configDir = contextProvider.getBaseDirectory().toPath().resolve(StoreConstants.CONFIG_DIRECTORY_NAME); Path oldConfigFile = configDir.resolve(oldName + XML_FILENAME_SUFFIX); - Path newConfigFile = configDir.resolve(oldName + XML_FILENAME_SUFFIX); + Path newConfigFile = configDir.resolve(newName + XML_FILENAME_SUFFIX); Files.move(oldConfigFile, newConfigFile); }; } diff --git a/scm-dao-xml/src/test/java/sonia/scm/store/JAXBPropertyFileAccessTest.java b/scm-dao-xml/src/test/java/sonia/scm/store/JAXBPropertyFileAccessTest.java index 69c3e648df..e534dc9840 100644 --- a/scm-dao-xml/src/test/java/sonia/scm/store/JAXBPropertyFileAccessTest.java +++ b/scm-dao-xml/src/test/java/sonia/scm/store/JAXBPropertyFileAccessTest.java @@ -1,6 +1,7 @@ package sonia.scm.store; import org.assertj.core.api.Assertions; +import org.junit.Assert; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; @@ -16,11 +17,13 @@ import sonia.scm.repository.xml.PathBasedRepositoryLocationResolver; import sonia.scm.update.PropertyFileAccess; import sonia.scm.util.IOUtil; +import java.io.File; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; +import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.lenient; @@ -30,6 +33,9 @@ class JAXBPropertyFileAccessTest { public static final String REPOSITORY_ID = "repoId"; public static final String STORE_NAME = "test"; + public static final String OLD_FILE_NAME = "old"; + public static final String NEW_FILE_NAME = "new"; + public static final String CONFIG_DIR = "config"; @Mock SCMContextProvider contextProvider; @@ -43,11 +49,28 @@ class JAXBPropertyFileAccessTest { lenient().when(contextProvider.getBaseDirectory()).thenReturn(tempDir.toFile()); lenient().when(contextProvider.resolve(any())).thenAnswer(invocation -> tempDir.resolve(invocation.getArgument(0).toString())); - locationResolver = new PathBasedRepositoryLocationResolver(contextProvider, new InitialRepositoryLocationResolver(), new DefaultFileSystem());//new TempDirRepositoryLocationResolver(tempDir.toFile()); + locationResolver = new PathBasedRepositoryLocationResolver(contextProvider, new InitialRepositoryLocationResolver(), new DefaultFileSystem()); fileAccess = new JAXBPropertyFileAccess(contextProvider, locationResolver); } + @Test + void shouldRenameGlobalConfigFile() throws IOException { + Path baseDirectory = contextProvider.getBaseDirectory().toPath(); + Path configDirectory = baseDirectory.resolve(StoreConstants.CONFIG_DIRECTORY_NAME); + + Files.createDirectories(configDirectory); + + Path oldPath = configDirectory.resolve("old" + StoreConstants.FILE_EXTENSION); + Files.createFile(oldPath); + + fileAccess.renameGlobalConfigurationFrom("old").to("new"); + + Path newPath = configDirectory.resolve("new" + StoreConstants.FILE_EXTENSION); + assertThat(oldPath).doesNotExist(); + assertThat(newPath).exists(); + } + @Nested class ForExistingRepository { @@ -63,7 +86,7 @@ class JAXBPropertyFileAccessTest { fileAccess.forStoreName(STORE_NAME).moveAsRepositoryStore(Paths.get("myStore.xml"), REPOSITORY_ID); - Assertions.assertThat(tempDir.resolve("repositories").resolve(REPOSITORY_ID).resolve("store").resolve("data").resolve(STORE_NAME).resolve("myStore.xml")).exists(); + assertThat(tempDir.resolve("repositories").resolve(REPOSITORY_ID).resolve("store").resolve("data").resolve(STORE_NAME).resolve("myStore.xml")).exists(); } @Test @@ -76,8 +99,8 @@ class JAXBPropertyFileAccessTest { PropertyFileAccess.StoreFileTools statisticStoreAccess = fileAccess.forStoreName(STORE_NAME); statisticStoreAccess.forStoreFiles(statisticStoreAccess::moveAsRepositoryStore); - Assertions.assertThat(tempDir.resolve("repositories").resolve(REPOSITORY_ID).resolve("store").resolve("data").resolve(STORE_NAME).resolve("repoId.xml")).exists(); - Assertions.assertThat(tempDir.resolve("repositories").resolve("repoId2").resolve("store").resolve("data").resolve(STORE_NAME).resolve("repoId2.xml")).exists(); + assertThat(tempDir.resolve("repositories").resolve(REPOSITORY_ID).resolve("store").resolve("data").resolve(STORE_NAME).resolve("repoId.xml")).exists(); + assertThat(tempDir.resolve("repositories").resolve("repoId2").resolve("store").resolve("data").resolve(STORE_NAME).resolve("repoId2.xml")).exists(); } } @@ -96,7 +119,7 @@ class JAXBPropertyFileAccessTest { fileAccess.forStoreName(STORE_NAME).moveAsRepositoryStore(Paths.get("myStore.xml"), REPOSITORY_ID); - Assertions.assertThat(tempDir.resolve("repositories").resolve(REPOSITORY_ID).resolve("store").resolve("data").resolve(STORE_NAME).resolve("myStore.xml")).doesNotExist(); + assertThat(tempDir.resolve("repositories").resolve(REPOSITORY_ID).resolve("store").resolve("data").resolve(STORE_NAME).resolve("myStore.xml")).doesNotExist(); } } } From 8e0063e4b789db2348a20b5718ef9a5503032e0d Mon Sep 17 00:00:00 2001 From: Eduard Heimbuch Date: Tue, 25 Jun 2019 09:33:56 +0200 Subject: [PATCH 14/15] cleanup --- .../java/sonia/scm/store/JAXBPropertyFileAccessTest.java | 6 ------ 1 file changed, 6 deletions(-) diff --git a/scm-dao-xml/src/test/java/sonia/scm/store/JAXBPropertyFileAccessTest.java b/scm-dao-xml/src/test/java/sonia/scm/store/JAXBPropertyFileAccessTest.java index e534dc9840..629453e6c4 100644 --- a/scm-dao-xml/src/test/java/sonia/scm/store/JAXBPropertyFileAccessTest.java +++ b/scm-dao-xml/src/test/java/sonia/scm/store/JAXBPropertyFileAccessTest.java @@ -1,7 +1,5 @@ package sonia.scm.store; -import org.assertj.core.api.Assertions; -import org.junit.Assert; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; @@ -17,7 +15,6 @@ import sonia.scm.repository.xml.PathBasedRepositoryLocationResolver; import sonia.scm.update.PropertyFileAccess; import sonia.scm.util.IOUtil; -import java.io.File; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; @@ -33,9 +30,6 @@ class JAXBPropertyFileAccessTest { public static final String REPOSITORY_ID = "repoId"; public static final String STORE_NAME = "test"; - public static final String OLD_FILE_NAME = "old"; - public static final String NEW_FILE_NAME = "new"; - public static final String CONFIG_DIR = "config"; @Mock SCMContextProvider contextProvider; From 07ef8acc0084e70d7988b1eea655b2e7ec80cb99 Mon Sep 17 00:00:00 2001 From: Eduard Heimbuch Date: Tue, 25 Jun 2019 12:25:05 +0000 Subject: [PATCH 15/15] Close branch feature/updatestep_api_for_plugins