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 a1ce516069..bfb59f9f35 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 @@ -7,6 +7,7 @@ import sonia.scm.repository.InternalRepositoryException; import sonia.scm.store.StoreConstants; import javax.inject.Inject; +import javax.inject.Singleton; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; @@ -28,6 +29,7 @@ import static sonia.scm.ContextEntry.ContextBuilder.entity; * * @since 2.0.0 */ +@Singleton public class PathBasedRepositoryLocationResolver extends BasicRepositoryLocationResolver { public static final String STORE_NAME = "repository-paths"; @@ -48,7 +50,7 @@ public class PathBasedRepositoryLocationResolver extends BasicRepositoryLocation this(contextProvider, initialRepositoryLocationResolver, Clock.systemUTC()); } - public PathBasedRepositoryLocationResolver(SCMContextProvider contextProvider, InitialRepositoryLocationResolver initialRepositoryLocationResolver, Clock clock) { + PathBasedRepositoryLocationResolver(SCMContextProvider contextProvider, InitialRepositoryLocationResolver initialRepositoryLocationResolver, Clock clock) { super(Path.class); this.contextProvider = contextProvider; this.initialRepositoryLocationResolver = initialRepositoryLocationResolver; @@ -138,4 +140,8 @@ public class PathBasedRepositoryLocationResolver extends BasicRepositoryLocation .resolve(StoreConstants.CONFIG_DIRECTORY_NAME) .resolve(STORE_NAME.concat(StoreConstants.FILE_EXTENSION)); } + + public void refresh() { + this.read(); + } } diff --git a/scm-dao-xml/src/main/java/sonia/scm/repository/xml/XmlRepositoryDAO.java b/scm-dao-xml/src/main/java/sonia/scm/repository/xml/XmlRepositoryDAO.java index f506793d1a..151e8f1281 100644 --- a/scm-dao-xml/src/main/java/sonia/scm/repository/xml/XmlRepositoryDAO.java +++ b/scm-dao-xml/src/main/java/sonia/scm/repository/xml/XmlRepositoryDAO.java @@ -198,4 +198,11 @@ public class XmlRepositoryDAO implements RepositoryDAO { public Long getLastModified() { return repositoryLocationResolver.getLastModified(); } + + public void refresh() { + repositoryLocationResolver.refresh(); + byNamespaceAndName.clear(); + byId.clear(); + init(); + } } diff --git a/scm-dao-xml/src/test/java/sonia/scm/repository/xml/XmlRepositoryDAOTest.java b/scm-dao-xml/src/test/java/sonia/scm/repository/xml/XmlRepositoryDAOTest.java index 5b9a00aec8..d77bfb3c63 100644 --- a/scm-dao-xml/src/test/java/sonia/scm/repository/xml/XmlRepositoryDAOTest.java +++ b/scm-dao-xml/src/test/java/sonia/scm/repository/xml/XmlRepositoryDAOTest.java @@ -8,8 +8,6 @@ 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.ArgumentCaptor; -import org.mockito.Captor; import org.mockito.Mock; import org.mockito.invocation.InvocationOnMock; import org.mockito.junit.jupiter.MockitoExtension; @@ -32,7 +30,9 @@ import static java.util.Arrays.asList; import static java.util.Collections.singletonList; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.fail; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; @@ -47,9 +47,6 @@ class XmlRepositoryDAOTest { @Mock private PathBasedRepositoryLocationResolver locationResolver; - @Captor - private ArgumentCaptor> forAllCaptor; - private FileSystem fileSystem = new DefaultFileSystem(); private XmlRepositoryDAO dao; @@ -268,43 +265,80 @@ class XmlRepositoryDAOTest { verify(locationResolver).updateModificationDate(); } - } - @Test - void shouldReadExistingRepositoriesFromPathDatabase(@TempDirectory.TempDir Path basePath) throws IOException { - doNothing().when(locationResolver).forAllPaths(forAllCaptor.capture()); - XmlRepositoryDAO dao = new XmlRepositoryDAO(locationResolver, fileSystem); + private String getXmlFileContent(String id) { + Path storePath = metadataFile(id); - Path repositoryPath = basePath.resolve("existing"); - Files.createDirectories(repositoryPath); - URL metadataUrl = Resources.getResource("sonia/scm/store/repositoryDaoMetadata.xml"); - Files.copy(metadataUrl.openStream(), repositoryPath.resolve("metadata.xml")); + assertThat(storePath).isRegularFile(); + return content(storePath); + } - forAllCaptor.getValue().accept("existing", repositoryPath); + private Path metadataFile(String id) { + return locationResolver.create(id).resolve("metadata.xml"); + } - assertThat(dao.contains(new NamespaceAndName("space", "existing"))).isTrue(); - } - - private String getXmlFileContent(String id) { - Path storePath = metadataFile(id); - - assertThat(storePath).isRegularFile(); - return content(storePath); - } - - private Path metadataFile(String id) { - return locationResolver.create(id).resolve("metadata.xml"); - } - - private String content(Path storePath) { - try { - return new String(Files.readAllBytes(storePath), Charsets.UTF_8); - } catch (IOException e) { - throw new RuntimeException(e); + private String content(Path storePath) { + try { + return new String(Files.readAllBytes(storePath), Charsets.UTF_8); + } catch (IOException e) { + throw new RuntimeException(e); + } } } - private static Repository createRepository(String id) { + @Nested + class WithExistingRepositories { + + private Path repositoryPath; + + @BeforeEach + void createMetadataFileForRepository(@TempDirectory.TempDir Path basePath) throws IOException { + repositoryPath = basePath.resolve("existing"); + + Files.createDirectories(repositoryPath); + URL metadataUrl = Resources.getResource("sonia/scm/store/repositoryDaoMetadata.xml"); + Files.copy(metadataUrl.openStream(), repositoryPath.resolve("metadata.xml")); + } + + @Test + void shouldReadExistingRepositoriesFromPathDatabase() { + // given + mockExistingPath(); + + // when + XmlRepositoryDAO dao = new XmlRepositoryDAO(locationResolver, fileSystem); + + // then + assertThat(dao.contains(new NamespaceAndName("space", "existing"))).isTrue(); + } + + @Test + void shouldRefreshWithExistingRepositoriesFromPathDatabase() { + // given + doNothing().when(locationResolver).forAllPaths(any()); + XmlRepositoryDAO dao = new XmlRepositoryDAO(locationResolver, fileSystem); + + mockExistingPath(); + + // when + dao.refresh(); + + // then + verify(locationResolver).refresh(); + assertThat(dao.contains(new NamespaceAndName("space", "existing"))).isTrue(); + } + + private void mockExistingPath() { + doAnswer( + invocation -> { + ((BiConsumer) invocation.getArgument(0)).accept("existing", repositoryPath); + return null; + } + ).when(locationResolver).forAllPaths(any()); + } + } + + private Repository createRepository(String id) { return new Repository(id, "xml", "space", id); } } diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/lfs/LfsStoreRemoveListener.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/lfs/LfsStoreRemoveListener.java deleted file mode 100644 index 18fb333c74..0000000000 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/lfs/LfsStoreRemoveListener.java +++ /dev/null @@ -1,97 +0,0 @@ -/** - * Copyright (c) 2010, Sebastian Sdorra - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * 3. Neither the name of SCM-Manager; nor the names of its - * contributors may be used to endorse or promote products derived from this - * software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY - * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON - * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - * http://bitbucket.org/sdorra/scm-manager - * - */ - - -package sonia.scm.web.lfs; - -import com.github.legman.Subscribe; -import com.google.inject.Inject; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import sonia.scm.EagerSingleton; -import sonia.scm.HandlerEventType; -import sonia.scm.plugin.Extension; -import sonia.scm.repository.GitRepositoryHandler; -import sonia.scm.repository.Repository; -import sonia.scm.repository.RepositoryEvent; -import sonia.scm.store.Blob; -import sonia.scm.store.BlobStore; - -/** - * Listener which removes all lfs objects from a blob store, whenever its corresponding git repository gets deleted. - * - * @author Sebastian Sdorra - * @since 1.54 - */ -@Extension -@EagerSingleton -public class LfsStoreRemoveListener { - - private static final Logger LOG = LoggerFactory.getLogger(LfsBlobStoreFactory.class); - - private final LfsBlobStoreFactory lfsBlobStoreFactory; - - @Inject - public LfsStoreRemoveListener(LfsBlobStoreFactory lfsBlobStoreFactory) { - this.lfsBlobStoreFactory = lfsBlobStoreFactory; - } - - /** - * Remove all object from the blob store, if the event is an delete event and the repository is a git repository. - * - * @param event repository event - */ - @Subscribe - public void handleRepositoryEvent(RepositoryEvent event) { - if ( isDeleteEvent(event) && isGitRepositoryEvent(event) ) { - removeLfsStore(event.getItem()); - } - } - - private boolean isDeleteEvent(RepositoryEvent event) { - return HandlerEventType.DELETE == event.getEventType(); - } - - private boolean isGitRepositoryEvent(RepositoryEvent event) { - return event.getItem() != null - && event.getItem().getType().equals(GitRepositoryHandler.TYPE_NAME); - } - - private void removeLfsStore(Repository repository) { - LOG.debug("remove all blobs from store, because corresponding git repository {} was removed", repository.getName()); - BlobStore blobStore = lfsBlobStoreFactory.getLfsBlobStore(repository); - for ( Blob blob : blobStore.getAll() ) { - LOG.trace("remove blob {}, because repository {} was removed", blob.getId(), repository.getName()); - blobStore.remove(blob); - } - } - -} diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/web/lfs/LfsStoreRemoveListenerTest.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/web/lfs/LfsStoreRemoveListenerTest.java deleted file mode 100644 index 34a1a3f4d5..0000000000 --- a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/web/lfs/LfsStoreRemoveListenerTest.java +++ /dev/null @@ -1,122 +0,0 @@ -/** - * Copyright (c) 2010, Sebastian Sdorra - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * 3. Neither the name of SCM-Manager; nor the names of its - * contributors may be used to endorse or promote products derived from this - * software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY - * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON - * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - * http://bitbucket.org/sdorra/scm-manager - * - */ - - -package sonia.scm.web.lfs; - -import com.google.common.collect.Lists; -import java.util.List; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import static org.mockito.Mockito.*; -import org.mockito.junit.MockitoJUnitRunner; -import sonia.scm.HandlerEventType; -import sonia.scm.repository.Repository; -import sonia.scm.repository.RepositoryEvent; -import sonia.scm.repository.RepositoryTestData; -import sonia.scm.store.Blob; -import sonia.scm.store.BlobStore; - -/** - * Unit tests for {@link LfsStoreRemoveListener}. - * - * @author Sebastian Sdorra - */ -@RunWith(MockitoJUnitRunner.class) -public class LfsStoreRemoveListenerTest { - - @Mock - private LfsBlobStoreFactory lfsBlobStoreFactory; - - @Mock - private BlobStore blobStore; - - @InjectMocks - private LfsStoreRemoveListener lfsStoreRemoveListener; - - @Test - public void testHandleRepositoryEventWithNonDeleteEvents() { - lfsStoreRemoveListener.handleRepositoryEvent(event(HandlerEventType.BEFORE_CREATE)); - lfsStoreRemoveListener.handleRepositoryEvent(event(HandlerEventType.CREATE)); - - lfsStoreRemoveListener.handleRepositoryEvent(event(HandlerEventType.BEFORE_MODIFY)); - lfsStoreRemoveListener.handleRepositoryEvent(event(HandlerEventType.MODIFY)); - - lfsStoreRemoveListener.handleRepositoryEvent(event(HandlerEventType.BEFORE_DELETE)); - - verifyZeroInteractions(lfsBlobStoreFactory); - } - - @Test - public void testHandleRepositoryEventWithNonGitRepositories() { - lfsStoreRemoveListener.handleRepositoryEvent(event(HandlerEventType.DELETE, "svn")); - lfsStoreRemoveListener.handleRepositoryEvent(event(HandlerEventType.DELETE, "hg")); - lfsStoreRemoveListener.handleRepositoryEvent(event(HandlerEventType.DELETE, "dummy")); - - verifyZeroInteractions(lfsBlobStoreFactory); - } - - @Test - public void testHandleRepositoryEvent() { - Repository heartOfGold = RepositoryTestData.createHeartOfGold("git"); - - when(lfsBlobStoreFactory.getLfsBlobStore(heartOfGold)).thenReturn(blobStore); - Blob blobA = mockBlob("a"); - Blob blobB = mockBlob("b"); - List blobs = Lists.newArrayList(blobA, blobB); - when(blobStore.getAll()).thenReturn(blobs); - - - lfsStoreRemoveListener.handleRepositoryEvent(new RepositoryEvent(HandlerEventType.DELETE, heartOfGold)); - verify(blobStore).getAll(); - verify(blobStore).remove(blobA); - verify(blobStore).remove(blobB); - - verifyNoMoreInteractions(blobStore); - } - - private Blob mockBlob(String id) { - Blob blob = mock(Blob.class); - when(blob.getId()).thenReturn(id); - return blob; - } - - private RepositoryEvent event(HandlerEventType eventType) { - return event(eventType, "git"); - } - - private RepositoryEvent event(HandlerEventType eventType, String repositoryType) { - return new RepositoryEvent(eventType, RepositoryTestData.create42Puzzle(repositoryType)); - } - -} diff --git a/scm-webapp/src/main/java/sonia/scm/update/repository/XmlRepositoryFileNameUpdateStep.java b/scm-webapp/src/main/java/sonia/scm/update/repository/XmlRepositoryFileNameUpdateStep.java index 62902713f0..9df6f81440 100644 --- a/scm-webapp/src/main/java/sonia/scm/update/repository/XmlRepositoryFileNameUpdateStep.java +++ b/scm-webapp/src/main/java/sonia/scm/update/repository/XmlRepositoryFileNameUpdateStep.java @@ -6,6 +6,7 @@ import sonia.scm.SCMContextProvider; import sonia.scm.migration.UpdateStep; import sonia.scm.plugin.Extension; import sonia.scm.repository.xml.PathBasedRepositoryLocationResolver; +import sonia.scm.repository.xml.XmlRepositoryDAO; import sonia.scm.store.StoreConstants; import sonia.scm.version.Version; @@ -27,10 +28,12 @@ public class XmlRepositoryFileNameUpdateStep implements UpdateStep { private static final Logger LOG = LoggerFactory.getLogger(XmlRepositoryFileNameUpdateStep.class); private final SCMContextProvider contextProvider; + private final XmlRepositoryDAO repositoryDAO; @Inject - public XmlRepositoryFileNameUpdateStep(SCMContextProvider contextProvider) { + public XmlRepositoryFileNameUpdateStep(SCMContextProvider contextProvider, XmlRepositoryDAO repositoryDAO) { this.contextProvider = contextProvider; + this.repositoryDAO = repositoryDAO; } @Override @@ -41,6 +44,7 @@ public class XmlRepositoryFileNameUpdateStep implements UpdateStep { if (Files.exists(oldRepositoriesFile)) { LOG.info("moving old repositories database files to repository-paths file"); Files.move(oldRepositoriesFile, newRepositoryPathsFile); + repositoryDAO.refresh(); } } diff --git a/scm-webapp/src/test/java/sonia/scm/update/repository/XmlRepositoryFileNameUpdateStepTest.java b/scm-webapp/src/test/java/sonia/scm/update/repository/XmlRepositoryFileNameUpdateStepTest.java index 51be47fc82..658330956d 100644 --- a/scm-webapp/src/test/java/sonia/scm/update/repository/XmlRepositoryFileNameUpdateStepTest.java +++ b/scm-webapp/src/test/java/sonia/scm/update/repository/XmlRepositoryFileNameUpdateStepTest.java @@ -7,8 +7,8 @@ import org.junit.jupiter.api.extension.ExtendWith; import org.junitpioneer.jupiter.TempDirectory; import sonia.scm.SCMContextProvider; import sonia.scm.repository.xml.PathBasedRepositoryLocationResolver; +import sonia.scm.repository.xml.XmlRepositoryDAO; -import javax.xml.bind.JAXBException; import java.io.IOException; import java.net.URL; import java.nio.file.Files; @@ -16,12 +16,14 @@ import java.nio.file.Path; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @ExtendWith(TempDirectory.class) class XmlRepositoryFileNameUpdateStepTest { SCMContextProvider contextProvider = mock(SCMContextProvider.class); + XmlRepositoryDAO repositoryDAO = mock(XmlRepositoryDAO.class); @BeforeEach void mockScmHome(@TempDirectory.TempDir Path tempDir) { @@ -29,8 +31,8 @@ class XmlRepositoryFileNameUpdateStepTest { } @Test - void shouldCopyRepositoriesFileToRepositoryPathsFile(@TempDirectory.TempDir Path tempDir) throws JAXBException, IOException { - XmlRepositoryFileNameUpdateStep updateStep = new XmlRepositoryFileNameUpdateStep(contextProvider); + void shouldCopyRepositoriesFileToRepositoryPathsFile(@TempDirectory.TempDir Path tempDir) throws IOException { + XmlRepositoryFileNameUpdateStep updateStep = new XmlRepositoryFileNameUpdateStep(contextProvider, repositoryDAO); URL url = Resources.getResource("sonia/scm/update/repository/formerV2RepositoryFile.xml"); Path configDir = tempDir.resolve("config"); Files.createDirectories(configDir); @@ -40,5 +42,6 @@ class XmlRepositoryFileNameUpdateStepTest { assertThat(configDir.resolve(PathBasedRepositoryLocationResolver.STORE_NAME + ".xml")).exists(); assertThat(configDir.resolve("repositories.xml")).doesNotExist(); + verify(repositoryDAO).refresh(); } }