From 64fcdde749ddf317b001de7e8500189bfdbaf828 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Tue, 21 May 2019 13:09:33 +0200 Subject: [PATCH] Create v2 repository entities for old v1 repositories --- .../PathBasedRepositoryLocationResolver.java | 2 +- .../spi/ZippedRepositoryTestBase.java | 9 +- .../update/XmlRepositoryV1UpdateStep.java | 163 ++++++++++++++++++ .../update/XmlRepositoryV1UpdateStepTest.java | 135 +++++++++++++++ 4 files changed, 306 insertions(+), 3 deletions(-) create mode 100644 scm-webapp/src/main/java/sonia/scm/repository/update/XmlRepositoryV1UpdateStep.java create mode 100644 scm-webapp/src/test/java/sonia/scm/repository/update/XmlRepositoryV1UpdateStepTest.java diff --git a/scm-dao-xml/src/main/java/sonia/scm/repository/xml/PathBasedRepositoryLocationResolver.java b/scm-dao-xml/src/main/java/sonia/scm/repository/xml/PathBasedRepositoryLocationResolver.java index cde4b3c3e2..91c653a31d 100644 --- a/scm-dao-xml/src/main/java/sonia/scm/repository/xml/PathBasedRepositoryLocationResolver.java +++ b/scm-dao-xml/src/main/java/sonia/scm/repository/xml/PathBasedRepositoryLocationResolver.java @@ -30,7 +30,7 @@ import static sonia.scm.ContextEntry.ContextBuilder.entity; */ public class PathBasedRepositoryLocationResolver extends BasicRepositoryLocationResolver { - private static final String STORE_NAME = "repositories"; + private static final String STORE_NAME = "repository-paths"; private final SCMContextProvider contextProvider; private final InitialRepositoryLocationResolver initialRepositoryLocationResolver; diff --git a/scm-test/src/main/java/sonia/scm/repository/spi/ZippedRepositoryTestBase.java b/scm-test/src/main/java/sonia/scm/repository/spi/ZippedRepositoryTestBase.java index c7a3b8f4e9..99f91c7926 100644 --- a/scm-test/src/main/java/sonia/scm/repository/spi/ZippedRepositoryTestBase.java +++ b/scm-test/src/main/java/sonia/scm/repository/spi/ZippedRepositoryTestBase.java @@ -153,7 +153,12 @@ public abstract class ZippedRepositoryTestBase extends AbstractTestBase */ private void extract(File folder) throws IOException { - URL url = Resources.getResource(getZippedRepositoryResource()); + String zippedRepositoryResource = getZippedRepositoryResource(); + extract(folder, zippedRepositoryResource); + } + + public static void extract(File targetFolder, String zippedRepositoryResource) throws IOException { + URL url = Resources.getResource(zippedRepositoryResource); ZipInputStream zip = null; try @@ -164,7 +169,7 @@ public abstract class ZippedRepositoryTestBase extends AbstractTestBase while (entry != null) { - File file = new File(folder, entry.getName()); + File file = new File(targetFolder, entry.getName()); File parent = file.getParentFile(); if (!parent.exists()) diff --git a/scm-webapp/src/main/java/sonia/scm/repository/update/XmlRepositoryV1UpdateStep.java b/scm-webapp/src/main/java/sonia/scm/repository/update/XmlRepositoryV1UpdateStep.java new file mode 100644 index 0000000000..fe1c175960 --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/repository/update/XmlRepositoryV1UpdateStep.java @@ -0,0 +1,163 @@ +package sonia.scm.repository.update; + +import sonia.scm.SCMContextProvider; +import sonia.scm.io.FileSystem; +import sonia.scm.migration.UpdateStep; +import sonia.scm.plugin.Extension; +import sonia.scm.repository.Repository; +import sonia.scm.repository.RepositoryPermission; +import sonia.scm.repository.xml.XmlRepositoryDAO; +import sonia.scm.store.StoreConstants; +import sonia.scm.version.Version; + +import javax.inject.Inject; +import javax.xml.bind.JAXBContext; +import javax.xml.bind.JAXBException; +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlRootElement; +import java.io.File; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import static sonia.scm.version.Version.parse; + +@Extension +public class XmlRepositoryV1UpdateStep implements UpdateStep { + + private final SCMContextProvider contextProvider; + private final XmlRepositoryDAO dao; + + @Inject + public XmlRepositoryV1UpdateStep(SCMContextProvider contextProvider, XmlRepositoryDAO dao) { + this.contextProvider = contextProvider; + this.dao = dao; + } + + @Override + public Version getTargetVersion() { + return parse("0.0.1"); + } + + @Override + public String getAffectedDataType() { + return "sonia.scm.repository.xml"; + } + + @Override + public void doUpdate() throws JAXBException { + JAXBContext jaxbContext = JAXBContext.newInstance(V1RepositoryDatabase.class); + V1RepositoryDatabase v1Database = readV1Database(jaxbContext); + v1Database.repositoryList.repositories.forEach(this::update); + } + + private void update(V1Repository v1Repository) { + Repository repository = new Repository( + v1Repository.id, + v1Repository.type, + getNamespace(v1Repository), + getName(v1Repository), + v1Repository.contact, + v1Repository.description, + createPermissions(v1Repository)); + dao.add(repository); + } + + private RepositoryPermission[] createPermissions(V1Repository v1Repository) { + if (v1Repository.permissions == null) { + return new RepositoryPermission[0]; + } + return v1Repository.permissions + .stream() + .map(this::createPermission) + .toArray(RepositoryPermission[]::new); + } + + private RepositoryPermission createPermission(V1Permission v1Permission) { + return new RepositoryPermission(v1Permission.name, v1Permission.type, v1Permission.groupPermission); + } + + private String getNamespace(V1Repository v1Repository) { + String[] nameParts = getNameParts(v1Repository.name); + return nameParts.length > 1? nameParts[0]: v1Repository.type; + } + + private String getName(V1Repository v1Repository) { + String[] nameParts = getNameParts(v1Repository.name); + return nameParts.length == 1? nameParts[0]: concatPathElements(nameParts); + } + + private String concatPathElements(String[] nameParts) { + return Arrays.stream(nameParts).skip(1).collect(Collectors.joining("_")); + } + + private String[] getNameParts(String v1Name) { + return v1Name.split("/"); + } + + private V1RepositoryDatabase readV1Database(JAXBContext jaxbContext) throws JAXBException { + return (V1RepositoryDatabase) jaxbContext.createUnmarshaller().unmarshal(determineV1File()); + } + + private File determineV1File() { + File configDirectory = new File(contextProvider.getBaseDirectory(), StoreConstants.CONFIG_DIRECTORY_NAME); + return new File(configDirectory, "repositories" + StoreConstants.FILE_EXTENSION); + } + + @XmlAccessorType(XmlAccessType.FIELD) + @XmlRootElement(name = "permissions") + public static class V1Permission { + private boolean groupPermission; + private String name; + private String type; + } + + @XmlAccessorType(XmlAccessType.FIELD) + @XmlRootElement(name = "repositories") + public static class V1Repository { + private Map properties; + private String contact; + private long creationDate; + private Long lastModified; + private String description; + private String id; + private String name; + private boolean isPublic; + private boolean archived; + private String type; + private List permissions; + + @Override + public String toString() { + return "V1Repository{" + + "properties=" + properties + + ", contact='" + contact + '\'' + + ", creationDate=" + creationDate + + ", lastModified=" + lastModified + + ", description='" + description + '\'' + + ", id='" + id + '\'' + + ", name='" + name + '\'' + + ", isPublic=" + isPublic + + ", archived=" + archived + + ", type='" + type + '\'' + + '}'; + } + } + + public static class RepositoryList { + @XmlElement(name = "repository") + private List repositories; + } + + @XmlRootElement(name = "repository-db") + @XmlAccessorType(XmlAccessType.FIELD) + public static class V1RepositoryDatabase { + private long creationTime; + private Long lastModified; + @XmlElement(name = "repositories") + private RepositoryList repositoryList; + } +} diff --git a/scm-webapp/src/test/java/sonia/scm/repository/update/XmlRepositoryV1UpdateStepTest.java b/scm-webapp/src/test/java/sonia/scm/repository/update/XmlRepositoryV1UpdateStepTest.java new file mode 100644 index 0000000000..0592a2e356 --- /dev/null +++ b/scm-webapp/src/test/java/sonia/scm/repository/update/XmlRepositoryV1UpdateStepTest.java @@ -0,0 +1,135 @@ +package sonia.scm.repository.update; + +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junitpioneer.jupiter.TempDirectory; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import sonia.scm.SCMContextProvider; +import sonia.scm.repository.Repository; +import sonia.scm.repository.RepositoryPermission; +import sonia.scm.repository.spi.ZippedRepositoryTestBase; +import sonia.scm.repository.xml.XmlRepositoryDAO; + +import javax.xml.bind.JAXBException; +import java.io.IOException; +import java.nio.file.Path; +import java.util.Optional; + +import static org.assertj.core.api.Assertions.assertThat; +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 org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +@ExtendWith(TempDirectory.class) +class XmlRepositoryV1UpdateStepTest { + + @Mock + SCMContextProvider contextProvider; + @Mock + XmlRepositoryDAO dao; + + @Captor + ArgumentCaptor storeCaptor; + + @InjectMocks + XmlRepositoryV1UpdateStep updateStep; + + @BeforeEach + void createV1Home(@TempDirectory.TempDir Path tempDir) throws IOException { + when(contextProvider.getBaseDirectory()).thenReturn(tempDir.toFile()); + ZippedRepositoryTestBase.extract(tempDir.toFile(), "sonia/scm/repository/update/scm-home.v1.zip"); + } + + @BeforeEach + void captureStoredRepositories() { + doNothing().when(dao).add(storeCaptor.capture()); + } + + @Test + void shouldCreateNewRepositories() throws JAXBException { + updateStep.doUpdate(); + verify(dao, times(3)).add(any()); + } + + @Test + void shouldMapAttributes() throws JAXBException { + updateStep.doUpdate(); + + Optional repository = findByNamespace("git"); + + assertThat(repository) + .get() + .hasFieldOrPropertyWithValue("type", "git") + .hasFieldOrPropertyWithValue("contact", "arthur@dent.uk") + .hasFieldOrPropertyWithValue("description", "A simple repository without directories.") + ; + } + + @Test + void shouldUseRepositoryTypeAsNamespaceForNamesWithSingleElement() throws JAXBException { + updateStep.doUpdate(); + + Optional repository = findByNamespace("git"); + + assertThat(repository) + .get() + .hasFieldOrPropertyWithValue("namespace", "git") + .hasFieldOrPropertyWithValue("name", "simple") + ; + } + + @Test + void shouldUseDirectoriesForNamespaceAndNameForNamesWithTwoElements() throws JAXBException { + updateStep.doUpdate(); + + Optional repository = findByNamespace("one"); + + assertThat(repository) + .get() + .hasFieldOrPropertyWithValue("namespace", "one") + .hasFieldOrPropertyWithValue("name", "directory") + ; + } + + @Test + void shouldUseDirectoriesForNamespaceAndConcatenatedNameForNamesWithMoreThanTwoElements() throws JAXBException { + updateStep.doUpdate(); + + Optional repository = findByNamespace("some"); + + assertThat(repository) + .get() + .hasFieldOrPropertyWithValue("namespace", "some") + .hasFieldOrPropertyWithValue("name", "more_directories_than_one") + ; + } + + @Test + void shouldMapPermissions() throws JAXBException { + updateStep.doUpdate(); + + Optional repository = findByNamespace("git"); + + assertThat(repository.get().getPermissions()) + .hasSize(3) + .contains( + new RepositoryPermission("mice", "WRITE", true), + new RepositoryPermission("dent", "OWNER", false), + new RepositoryPermission("trillian", "READ", false) + ) + ; + } + + private Optional findByNamespace(String namespace) { + return storeCaptor.getAllValues().stream().filter(r -> r.getNamespace().equals(namespace)).findFirst(); + } +}