Introduce abstraction for repository location

This commit is contained in:
René Pfeuffer
2019-05-09 14:08:18 +02:00
parent d29fa32c8e
commit c44d38cc61
15 changed files with 133 additions and 63 deletions

View File

@@ -46,6 +46,7 @@ import sonia.scm.store.ConfigurationStoreFactory;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.net.URL; import java.net.URL;
import java.nio.file.Path;
//~--- JDK imports ------------------------------------------------------------ //~--- JDK imports ------------------------------------------------------------
@@ -172,6 +173,6 @@ public abstract class AbstractSimpleRepositoryHandler<C extends RepositoryConfig
} }
private File resolveNativeDirectory(String repositoryId) { private File resolveNativeDirectory(String repositoryId) {
return repositoryLocationResolver.getPath(repositoryId).resolve(REPOSITORIES_NATIVE_DIRECTORY).toFile(); return repositoryLocationResolver.forClass(Path.class).getLocation(repositoryId).resolve(REPOSITORIES_NATIVE_DIRECTORY).toFile();
} }
} }

View File

@@ -0,0 +1,15 @@
package sonia.scm.repository;
public abstract class BasicRepositoryLocationResolver<T> extends RepositoryLocationResolver {
private final Class<T> type;
protected BasicRepositoryLocationResolver(Class<T> type) {
this.type = type;
}
@Override
public boolean supportsLocationType(Class<?> type) {
return type.isAssignableFrom(this.type);
}
}

View File

@@ -1,51 +1,19 @@
package sonia.scm.repository; package sonia.scm.repository;
import sonia.scm.SCMContextProvider; public abstract class RepositoryLocationResolver {
import javax.inject.Inject; public abstract boolean supportsLocationType(Class<?> type);
import java.nio.file.Path;
/** protected abstract <T> RepositoryLocationResolverInstance<T> create(Class<T> type);
* A Location Resolver for File based Repository Storage.
* <p>
* <b>WARNING:</b> The Locations provided with this class may not be used from the plugins to store any plugin specific files.
* <p>
* Please use the {@link sonia.scm.store.DataStoreFactory } and the {@link sonia.scm.store.DataStore} classes to store data<br>
* Please use the {@link sonia.scm.store.BlobStoreFactory } and the {@link sonia.scm.store.BlobStore} classes to store binary files<br>
* Please use the {@link sonia.scm.store.ConfigurationStoreFactory} and the {@link sonia.scm.store.ConfigurationStore} classes to store configurations
*
* @author Mohamed Karray
* @since 2.0.0
*/
public class RepositoryLocationResolver {
private final SCMContextProvider contextProvider; public final <T> RepositoryLocationResolverInstance<T> forClass(Class<T> type) {
private final RepositoryDAO repositoryDAO; if (!supportsLocationType(type)) {
private final InitialRepositoryLocationResolver initialRepositoryLocationResolver; throw new IllegalStateException("no support for location of class " + type);
}
@Inject return create(type);
public RepositoryLocationResolver(SCMContextProvider contextProvider, RepositoryDAO repositoryDAO, InitialRepositoryLocationResolver initialRepositoryLocationResolver) {
this.contextProvider = contextProvider;
this.repositoryDAO = repositoryDAO;
this.initialRepositoryLocationResolver = initialRepositoryLocationResolver;
} }
/** public interface RepositoryLocationResolverInstance<T> {
* Returns the path to the repository. T getLocation(String repositoryId);
*
* @param repositoryId repository id
*
* @return path of repository
*/
public Path getPath(String repositoryId) {
Path path;
if (repositoryDAO instanceof PathBasedRepositoryDAO) {
path = ((PathBasedRepositoryDAO) repositoryDAO).getPath(repositoryId);
} else {
path = initialRepositoryLocationResolver.getPath(repositoryId);
}
return contextProvider.resolve(path);
} }
} }

View File

@@ -0,0 +1,53 @@
package sonia.scm.repository.xml;
import sonia.scm.SCMContextProvider;
import sonia.scm.repository.BasicRepositoryLocationResolver;
import sonia.scm.repository.InitialRepositoryLocationResolver;
import sonia.scm.repository.PathBasedRepositoryDAO;
import sonia.scm.repository.RepositoryDAO;
import sonia.scm.repository.RepositoryLocationResolver;
import javax.inject.Inject;
import java.nio.file.Path;
/**
* A Location Resolver for File based Repository Storage.
* <p>
* <b>WARNING:</b> The Locations provided with this class may not be used from the plugins to store any plugin specific files.
* <p>
* Please use the {@link sonia.scm.store.DataStoreFactory } and the {@link sonia.scm.store.DataStore} classes to store data<br>
* Please use the {@link sonia.scm.store.BlobStoreFactory } and the {@link sonia.scm.store.BlobStore} classes to store binary files<br>
* Please use the {@link sonia.scm.store.ConfigurationStoreFactory} and the {@link sonia.scm.store.ConfigurationStore} classes to store configurations
*
* @author Mohamed Karray
* @since 2.0.0
*/
public class PathBasedRepositoryLocationResolver extends BasicRepositoryLocationResolver<Path> {
private final SCMContextProvider contextProvider;
private final InitialRepositoryLocationResolver initialRepositoryLocationResolver;
private final RepositoryDAO repositoryDAO;
@Inject
public PathBasedRepositoryLocationResolver(SCMContextProvider contextProvider, RepositoryDAO repositoryDAO, InitialRepositoryLocationResolver initialRepositoryLocationResolver) {
super(Path.class);
this.contextProvider = contextProvider;
this.initialRepositoryLocationResolver = initialRepositoryLocationResolver;
this.repositoryDAO = repositoryDAO;
}
@Override
protected <T> RepositoryLocationResolverInstance<T> create(Class<T> type) {
return repositoryId -> {
Path path;
if (repositoryDAO instanceof PathBasedRepositoryDAO) {
path = ((PathBasedRepositoryDAO) repositoryDAO).getPath(repositoryId);
} else {
path = initialRepositoryLocationResolver.getPath(repositoryId);
}
return (T) contextProvider.resolve(path);
};
}
}

View File

@@ -40,6 +40,7 @@ import sonia.scm.repository.RepositoryLocationResolver;
import sonia.scm.util.IOUtil; import sonia.scm.util.IOUtil;
import java.io.File; import java.io.File;
import java.nio.file.Path;
//~--- JDK imports ------------------------------------------------------------ //~--- JDK imports ------------------------------------------------------------
@@ -58,7 +59,7 @@ public abstract class FileBasedStoreFactory {
private RepositoryLocationResolver repositoryLocationResolver; private RepositoryLocationResolver repositoryLocationResolver;
private Store store; private Store store;
protected FileBasedStoreFactory(SCMContextProvider contextProvider , RepositoryLocationResolver repositoryLocationResolver, Store store) { protected FileBasedStoreFactory(SCMContextProvider contextProvider, RepositoryLocationResolver repositoryLocationResolver, Store store) {
this.contextProvider = contextProvider; this.contextProvider = contextProvider;
this.repositoryLocationResolver = repositoryLocationResolver; this.repositoryLocationResolver = repositoryLocationResolver;
this.store = store; this.store = store;
@@ -92,7 +93,7 @@ public abstract class FileBasedStoreFactory {
* @return the store directory of a specific repository * @return the store directory of a specific repository
*/ */
private File getStoreDirectory(Store store, Repository repository) { private File getStoreDirectory(Store store, Repository repository) {
return new File(repositoryLocationResolver.getPath(repository.getId()).toFile(), store.getRepositoryStoreDirectory()); return new File(repositoryLocationResolver.forClass(Path.class).getLocation(repository.getId()).toFile(), store.getRepositoryStoreDirectory());
} }
/** /**

View File

@@ -65,7 +65,7 @@ public class FileBlobStoreFactory extends FileBasedStoreFactory implements BlobS
* @param keyGenerator key generator * @param keyGenerator key generator
*/ */
@Inject @Inject
public FileBlobStoreFactory(SCMContextProvider contextProvider ,RepositoryLocationResolver repositoryLocationResolver, KeyGenerator keyGenerator) { public FileBlobStoreFactory(SCMContextProvider contextProvider , RepositoryLocationResolver repositoryLocationResolver, KeyGenerator keyGenerator) {
super(contextProvider, repositoryLocationResolver, Store.BLOB); super(contextProvider, repositoryLocationResolver, Store.BLOB);
this.keyGenerator = keyGenerator; this.keyGenerator = keyGenerator;
} }

View File

@@ -1,4 +1,4 @@
package sonia.scm.repository; package sonia.scm.repository.xml;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
@@ -7,6 +7,9 @@ import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension; import org.mockito.junit.jupiter.MockitoExtension;
import org.mockito.stubbing.Answer; import org.mockito.stubbing.Answer;
import sonia.scm.SCMContextProvider; import sonia.scm.SCMContextProvider;
import sonia.scm.repository.InitialRepositoryLocationResolver;
import sonia.scm.repository.PathBasedRepositoryDAO;
import sonia.scm.repository.RepositoryDAO;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.Paths; import java.nio.file.Paths;
@@ -16,7 +19,7 @@ import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
@ExtendWith({MockitoExtension.class}) @ExtendWith({MockitoExtension.class})
class RepositoryLocationResolverTest { class PathBasedRepositoryLocationResolverTest {
@Mock @Mock
private SCMContextProvider contextProvider; private SCMContextProvider contextProvider;
@@ -30,14 +33,13 @@ class RepositoryLocationResolverTest {
@Mock @Mock
private InitialRepositoryLocationResolver initialRepositoryLocationResolver; private InitialRepositoryLocationResolver initialRepositoryLocationResolver;
@BeforeEach @BeforeEach
void beforeEach() { void beforeEach() {
when(contextProvider.resolve(any(Path.class))).then((Answer<Path>) invocationOnMock -> invocationOnMock.getArgument(0)); when(contextProvider.resolve(any(Path.class))).then((Answer<Path>) invocationOnMock -> invocationOnMock.getArgument(0));
} }
private RepositoryLocationResolver createResolver(RepositoryDAO pathBasedRepositoryDAO) { private PathBasedRepositoryLocationResolver createResolver(RepositoryDAO pathBasedRepositoryDAO) {
return new RepositoryLocationResolver(contextProvider, pathBasedRepositoryDAO, initialRepositoryLocationResolver); return new PathBasedRepositoryLocationResolver(contextProvider, pathBasedRepositoryDAO, initialRepositoryLocationResolver);
} }
@Test @Test
@@ -45,8 +47,8 @@ class RepositoryLocationResolverTest {
Path repositoryPath = Paths.get("repos", "42"); Path repositoryPath = Paths.get("repos", "42");
when(pathBasedRepositoryDAO.getPath("42")).thenReturn(repositoryPath); when(pathBasedRepositoryDAO.getPath("42")).thenReturn(repositoryPath);
RepositoryLocationResolver resolver = createResolver(pathBasedRepositoryDAO); PathBasedRepositoryLocationResolver resolver = createResolver(pathBasedRepositoryDAO);
Path path = resolver.getPath("42"); Path path = resolver.forClass(Path.class).getLocation("42");
assertThat(path).isSameAs(repositoryPath); assertThat(path).isSameAs(repositoryPath);
} }
@@ -56,8 +58,8 @@ class RepositoryLocationResolverTest {
Path repositoryPath = Paths.get("r", "42"); Path repositoryPath = Paths.get("r", "42");
when(initialRepositoryLocationResolver.getPath("42")).thenReturn(repositoryPath); when(initialRepositoryLocationResolver.getPath("42")).thenReturn(repositoryPath);
RepositoryLocationResolver resolver = createResolver(repositoryDAO); PathBasedRepositoryLocationResolver resolver = createResolver(repositoryDAO);
Path path = resolver.getPath("42"); Path path = resolver.forClass(Path.class).getLocation("42");
assertThat(path).isSameAs(repositoryPath); assertThat(path).isSameAs(repositoryPath);
} }

View File

@@ -26,7 +26,7 @@ public class GitRepositoryContextResolver implements RepositoryContextResolver {
public RepositoryContext resolve(String[] args) { public RepositoryContext resolve(String[] args) {
NamespaceAndName namespaceAndName = extractNamespaceAndName(args); NamespaceAndName namespaceAndName = extractNamespaceAndName(args);
Repository repository = repositoryManager.get(namespaceAndName); Repository repository = repositoryManager.get(namespaceAndName);
Path path = locationResolver.getPath(repository.getId()).resolve("data"); Path path = locationResolver.forClass(Path.class).getLocation(repository.getId()).resolve("data");
return new RepositoryContext(repository, path); return new RepositoryContext(repository, path);
} }

View File

@@ -2,6 +2,7 @@ package sonia.scm.protocolcommand.git;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Answers;
import org.mockito.InjectMocks; import org.mockito.InjectMocks;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension; import org.mockito.junit.jupiter.MockitoExtension;
@@ -16,6 +17,7 @@ import java.io.IOException;
import java.nio.file.Path; import java.nio.file.Path;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class) @ExtendWith(MockitoExtension.class)
@@ -25,7 +27,7 @@ class GitRepositoryContextResolverTest {
@Mock @Mock
RepositoryManager repositoryManager; RepositoryManager repositoryManager;
@Mock @Mock(answer = Answers.RETURNS_DEEP_STUBS)
RepositoryLocationResolver locationResolver; RepositoryLocationResolver locationResolver;
@InjectMocks @InjectMocks
@@ -35,7 +37,7 @@ class GitRepositoryContextResolverTest {
void shouldResolveCorrectRepository() throws IOException { void shouldResolveCorrectRepository() throws IOException {
when(repositoryManager.get(new NamespaceAndName("space", "X"))).thenReturn(REPOSITORY); when(repositoryManager.get(new NamespaceAndName("space", "X"))).thenReturn(REPOSITORY);
Path repositoryPath = File.createTempFile("test", "scm").toPath(); Path repositoryPath = File.createTempFile("test", "scm").toPath();
when(locationResolver.getPath("id")).thenReturn(repositoryPath); when(locationResolver.forClass(any()).getLocation("id")).thenReturn(repositoryPath);
RepositoryContext context = resolver.resolve(new String[] {"git", "repo/space/X/something/else"}); RepositoryContext context = resolver.resolve(new String[] {"git", "repo/space/X/something/else"});

View File

@@ -37,6 +37,7 @@ package sonia.scm.repository;
import org.junit.Assume; import org.junit.Assume;
import sonia.scm.SCMContext; import sonia.scm.SCMContext;
import sonia.scm.TempDirRepositoryLocationResolver;
import sonia.scm.store.InMemoryConfigurationStoreFactory; import sonia.scm.store.InMemoryConfigurationStoreFactory;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
@@ -103,7 +104,7 @@ public final class HgTestUtil
PathBasedRepositoryDAO repoDao = mock(PathBasedRepositoryDAO.class); PathBasedRepositoryDAO repoDao = mock(PathBasedRepositoryDAO.class);
RepositoryLocationResolver repositoryLocationResolver = new RepositoryLocationResolver(context, repoDao, new InitialRepositoryLocationResolver()); RepositoryLocationResolver repositoryLocationResolver = new TempDirRepositoryLocationResolver(directory);
HgRepositoryHandler handler = HgRepositoryHandler handler =
new HgRepositoryHandler(new InMemoryConfigurationStoreFactory(), new HgContextProvider(), repositoryLocationResolver, null, null); new HgRepositoryHandler(new InMemoryConfigurationStoreFactory(), new HgContextProvider(), repositoryLocationResolver, null, null);
Path repoDir = directory.toPath(); Path repoDir = directory.toPath();

View File

@@ -91,7 +91,7 @@ public class AbstractTestBase
contextProvider = MockUtil.getSCMContextProvider(tempDirectory); contextProvider = MockUtil.getSCMContextProvider(tempDirectory);
fileSystem = new DefaultFileSystem(); fileSystem = new DefaultFileSystem();
InitialRepositoryLocationResolver initialRepoLocationResolver = new InitialRepositoryLocationResolver(); InitialRepositoryLocationResolver initialRepoLocationResolver = new InitialRepositoryLocationResolver();
repositoryLocationResolver = new RepositoryLocationResolver(contextProvider, repositoryDAO, initialRepoLocationResolver); repositoryLocationResolver = new TempDirRepositoryLocationResolver(tempDirectory);
postSetUp(); postSetUp();
} }
@@ -254,4 +254,5 @@ public class AbstractTestBase
subjectThreadState = createThreadState(subject); subjectThreadState = createThreadState(subject);
subjectThreadState.bind(); subjectThreadState.bind();
} }
} }

View File

@@ -74,7 +74,7 @@ public abstract class ManagerTestBase<T extends ModelObject>
contextProvider = MockUtil.getSCMContextProvider(temp); contextProvider = MockUtil.getSCMContextProvider(temp);
InitialRepositoryLocationResolver initialRepositoryLocationResolver = new InitialRepositoryLocationResolver(); InitialRepositoryLocationResolver initialRepositoryLocationResolver = new InitialRepositoryLocationResolver();
RepositoryDAO repoDao = mock(RepositoryDAO.class); RepositoryDAO repoDao = mock(RepositoryDAO.class);
locationResolver = new RepositoryLocationResolver(contextProvider, repoDao ,initialRepositoryLocationResolver); locationResolver = new TempDirRepositoryLocationResolver(temp);
manager = createManager(); manager = createManager();
manager.init(contextProvider); manager.init(contextProvider);
} }

View File

@@ -0,0 +1,21 @@
package sonia.scm;
import sonia.scm.repository.BasicRepositoryLocationResolver;
import sonia.scm.repository.RepositoryLocationResolver;
import java.io.File;
import java.nio.file.Path;
public class TempDirRepositoryLocationResolver extends BasicRepositoryLocationResolver {
private final File tempDirectory;
public TempDirRepositoryLocationResolver(File tempDirectory) {
super(Path.class);
this.tempDirectory = tempDirectory;
}
@Override
protected <T> RepositoryLocationResolverInstance<T> create(Class<T> type) {
return repositoryId -> (T) tempDirectory.toPath();
}
}

View File

@@ -44,6 +44,7 @@ import java.io.IOException;
import java.nio.file.Path; import java.nio.file.Path;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
@@ -78,7 +79,10 @@ public abstract class SimpleRepositoryHandlerTestBase extends AbstractTestBase {
locationResolver = mock(RepositoryLocationResolver.class); locationResolver = mock(RepositoryLocationResolver.class);
when(locationResolver.getPath(anyString())).then(ic -> { RepositoryLocationResolver.RepositoryLocationResolverInstance instanceMock = mock(RepositoryLocationResolver.RepositoryLocationResolverInstance.class);
when(locationResolver.forClass(any())).thenReturn(instanceMock);
when(instanceMock.getLocation(anyString())).then(ic -> {
String id = ic.getArgument(0); String id = ic.getArgument(0);
return baseDirectory.toPath().resolve(id); return baseDirectory.toPath().resolve(id);
}); });

View File

@@ -59,6 +59,7 @@ import sonia.scm.repository.api.HookContext;
import sonia.scm.repository.api.HookContextFactory; import sonia.scm.repository.api.HookContextFactory;
import sonia.scm.repository.api.HookFeature; import sonia.scm.repository.api.HookFeature;
import sonia.scm.repository.spi.HookContextProvider; import sonia.scm.repository.spi.HookContextProvider;
import sonia.scm.repository.xml.PathBasedRepositoryLocationResolver;
import sonia.scm.repository.xml.XmlRepositoryDAO; import sonia.scm.repository.xml.XmlRepositoryDAO;
import sonia.scm.security.DefaultKeyGenerator; import sonia.scm.security.DefaultKeyGenerator;
import sonia.scm.security.KeyGenerator; import sonia.scm.security.KeyGenerator;
@@ -412,7 +413,7 @@ public class DefaultRepositoryManagerTest extends ManagerTestBase<Repository> {
Set<RepositoryHandler> handlerSet = new HashSet<>(); Set<RepositoryHandler> handlerSet = new HashSet<>();
InitialRepositoryLocationResolver initialRepositoryLocationResolver = new InitialRepositoryLocationResolver(); InitialRepositoryLocationResolver initialRepositoryLocationResolver = new InitialRepositoryLocationResolver();
XmlRepositoryDAO repositoryDAO = new XmlRepositoryDAO(contextProvider, initialRepositoryLocationResolver, fileSystem); XmlRepositoryDAO repositoryDAO = new XmlRepositoryDAO(contextProvider, initialRepositoryLocationResolver, fileSystem);
RepositoryLocationResolver repositoryLocationResolver = new RepositoryLocationResolver(contextProvider, repositoryDAO, initialRepositoryLocationResolver); PathBasedRepositoryLocationResolver repositoryLocationResolver = new PathBasedRepositoryLocationResolver(contextProvider, repositoryDAO, initialRepositoryLocationResolver);
ConfigurationStoreFactory factory = new JAXBConfigurationStoreFactory(contextProvider, repositoryLocationResolver); ConfigurationStoreFactory factory = new JAXBConfigurationStoreFactory(contextProvider, repositoryLocationResolver);
handlerSet.add(new DummyRepositoryHandler(factory, repositoryLocationResolver)); handlerSet.add(new DummyRepositoryHandler(factory, repositoryLocationResolver));
handlerSet.add(new DummyRepositoryHandler(factory, repositoryLocationResolver) { handlerSet.add(new DummyRepositoryHandler(factory, repositoryLocationResolver) {