mirror of
https://github.com/scm-manager/scm-manager.git
synced 2025-11-12 16:35:45 +01:00
@@ -0,0 +1,11 @@
|
||||
package sonia.scm.migration;
|
||||
|
||||
public class UpdateException extends RuntimeException {
|
||||
public UpdateException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public UpdateException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
||||
82
scm-core/src/main/java/sonia/scm/migration/UpdateStep.java
Normal file
82
scm-core/src/main/java/sonia/scm/migration/UpdateStep.java
Normal file
@@ -0,0 +1,82 @@
|
||||
package sonia.scm.migration;
|
||||
|
||||
import sonia.scm.plugin.ExtensionPoint;
|
||||
import sonia.scm.version.Version;
|
||||
|
||||
/**
|
||||
* This is the main interface for data migration/update. Using this interface, SCM-Manager provides the possibility to
|
||||
* change data structures between versions for a given type of data.
|
||||
* <p>The data type can be an arbitrary string, but it is considered a best practice to use a qualified name, for
|
||||
* example
|
||||
* <ul>
|
||||
* <li><code>com.example.myPlugin.configuration</code></li> for data in plugins, or
|
||||
* <li><code>com.cloudogu.scm.repository</code></li> for core data structures.
|
||||
* </ul>
|
||||
* </p>
|
||||
* <p>The version is unrelated to other versions and therefore can be chosen freely, so that a data type can be updated
|
||||
* without in various ways independent of other data types or the official version of the plugin or the core.
|
||||
* A coordination between different data types and their versions is only necessary, when update steps of different data
|
||||
* types rely on each other. If a update step of data type <i>A</i> has to run <b>before</b> another step for data type
|
||||
* <i>B</i>, the version number of the second step has to be greater in regards to {@link Version#compareTo(Version)}.
|
||||
* </p>
|
||||
* <p>The algorithm looks something like this:<br>
|
||||
* Whenever the SCM-Manager starts,
|
||||
* <ul>
|
||||
* <li>it creates a so called <i>bootstrap guice context</i>, that contains
|
||||
* <ul>
|
||||
* <li>a {@link sonia.scm.security.KeyGenerator},</li>
|
||||
* <li>the {@link sonia.scm.repository.RepositoryLocationResolver},</li>
|
||||
* <li>the {@link sonia.scm.io.FileSystem},</li>
|
||||
* <li>the {@link sonia.scm.security.CipherHandler},</li>
|
||||
* <li>a {@link sonia.scm.store.ConfigurationStoreFactory},</li>
|
||||
* <li>a {@link sonia.scm.store.ConfigurationEntryStoreFactory},</li>
|
||||
* <li>a {@link sonia.scm.store.DataStoreFactory},</li>
|
||||
* <li>a {@link sonia.scm.store.BlobStoreFactory}, and</li>
|
||||
* <li>the {@link sonia.scm.plugin.PluginLoader}.</li>
|
||||
* </ul>
|
||||
* Mind, that there are no DAOs, Managers or the like available at this time!
|
||||
* </li>
|
||||
* <li>It then checks whether there are instances of this interface that have not run before, that is either
|
||||
* <ul>
|
||||
* <li>their version number given by {@link #getTargetVersion()} is bigger than the last recorded target version of an
|
||||
* executed update step for the data type given by {@link #getAffectedDataType()}, or
|
||||
* </li>
|
||||
* <li>there is no version number known for the given data type.
|
||||
* </li>
|
||||
* </ul>
|
||||
* These are the <i>relevant</i> update steps.
|
||||
* </li>
|
||||
* <li>These relevant update steps are then sorted ascending by their target version given by
|
||||
* {@link #getTargetVersion()}.
|
||||
* </li>
|
||||
* <li>Finally, these sorted steps are executed one after another calling {@link #doUpdate()} of each step, updating the
|
||||
* version for the data type accordingly.
|
||||
* </li>
|
||||
* <li>If all works well, SCM-Manager then creates the runtime guice context by loading all further modules.</li>
|
||||
* <li>If any of the update steps fails, the whole process is interrupted and SCM-Manager will not start up and will
|
||||
* not record the version number of this update step.
|
||||
* </li>
|
||||
* </ul>
|
||||
* </p>
|
||||
*/
|
||||
@ExtensionPoint
|
||||
public interface UpdateStep {
|
||||
/**
|
||||
* Implement this to update the data to the new version. If any {@link Exception} is thrown, SCM-Manager will not
|
||||
* start up.
|
||||
*/
|
||||
void doUpdate() throws Exception;
|
||||
|
||||
/**
|
||||
* Declares the new version of the data type given by {@link #getAffectedDataType()}. A update step will only be
|
||||
* executed, when this version is bigger than the last recorded version for its data type according to
|
||||
* {@link Version#compareTo(Version)}
|
||||
*/
|
||||
Version getTargetVersion();
|
||||
|
||||
/**
|
||||
* Declares the data type this update step will take care of. This should be a qualified name, like
|
||||
* <code>com.example.myPlugin.configuration</code>.
|
||||
*/
|
||||
String getAffectedDataType();
|
||||
}
|
||||
@@ -46,6 +46,7 @@ import sonia.scm.store.ConfigurationStoreFactory;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.nio.file.Path;
|
||||
|
||||
//~--- JDK imports ------------------------------------------------------------
|
||||
|
||||
@@ -172,6 +173,6 @@ public abstract class AbstractSimpleRepositoryHandler<C extends RepositoryConfig
|
||||
}
|
||||
|
||||
private File resolveNativeDirectory(String repositoryId) {
|
||||
return repositoryLocationResolver.getPath(repositoryId).resolve(REPOSITORIES_NATIVE_DIRECTORY).toFile();
|
||||
return repositoryLocationResolver.create(Path.class).getLocation(repositoryId).resolve(REPOSITORIES_NATIVE_DIRECTORY).toFile();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
package sonia.scm.repository;
|
||||
|
||||
import java.nio.file.Path;
|
||||
|
||||
/**
|
||||
* A DAO used for Repositories accessible by a path
|
||||
*
|
||||
* @author Mohamed Karray
|
||||
* @since 2.0.0
|
||||
*/
|
||||
public interface PathBasedRepositoryDAO extends RepositoryDAO {
|
||||
|
||||
/**
|
||||
* Get the current path of the repository for the given id.
|
||||
* This works for existing repositories only, not for repositories that should be created.
|
||||
*/
|
||||
Path getPath(String repositoryId) ;
|
||||
}
|
||||
@@ -1,51 +1,20 @@
|
||||
package sonia.scm.repository;
|
||||
|
||||
import sonia.scm.SCMContextProvider;
|
||||
public abstract class RepositoryLocationResolver {
|
||||
|
||||
import javax.inject.Inject;
|
||||
import java.nio.file.Path;
|
||||
public abstract boolean supportsLocationType(Class<?> 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 {
|
||||
protected abstract <T> RepositoryLocationResolverInstance<T> create(Class<T> type);
|
||||
|
||||
private final SCMContextProvider contextProvider;
|
||||
private final RepositoryDAO repositoryDAO;
|
||||
private final InitialRepositoryLocationResolver initialRepositoryLocationResolver;
|
||||
|
||||
@Inject
|
||||
public RepositoryLocationResolver(SCMContextProvider contextProvider, RepositoryDAO repositoryDAO, InitialRepositoryLocationResolver initialRepositoryLocationResolver) {
|
||||
this.contextProvider = contextProvider;
|
||||
this.repositoryDAO = repositoryDAO;
|
||||
this.initialRepositoryLocationResolver = initialRepositoryLocationResolver;
|
||||
public final <T> RepositoryLocationResolverInstance<T> forClass(Class<T> type) {
|
||||
if (!supportsLocationType(type)) {
|
||||
throw new IllegalStateException("no support for location of class " + type);
|
||||
}
|
||||
return create(type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the path to the repository.
|
||||
*
|
||||
* @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);
|
||||
@FunctionalInterface
|
||||
public interface RepositoryLocationResolverInstance<T> {
|
||||
T getLocation(String repositoryId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -121,8 +121,11 @@ public class RepositoryRole implements ModelObject, PermissionObject {
|
||||
* @return the hash code value for the {@link RepositoryRole}
|
||||
*/
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hashCode(name, verbs);
|
||||
public int hashCode()
|
||||
{
|
||||
// Normally we do not have a log of repository permissions having the same size of verbs, but different content.
|
||||
// Therefore we do not use the verbs themselves for the hash code but only the number of verbs.
|
||||
return Objects.hashCode(name, verbs == null? -1: verbs.size());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,65 +0,0 @@
|
||||
package sonia.scm.repository;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.mockito.stubbing.Answer;
|
||||
import sonia.scm.SCMContextProvider;
|
||||
|
||||
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.when;
|
||||
|
||||
@ExtendWith({MockitoExtension.class})
|
||||
class RepositoryLocationResolverTest {
|
||||
|
||||
@Mock
|
||||
private SCMContextProvider contextProvider;
|
||||
|
||||
@Mock
|
||||
private PathBasedRepositoryDAO pathBasedRepositoryDAO;
|
||||
|
||||
@Mock
|
||||
private RepositoryDAO repositoryDAO;
|
||||
|
||||
@Mock
|
||||
private InitialRepositoryLocationResolver initialRepositoryLocationResolver;
|
||||
|
||||
|
||||
@BeforeEach
|
||||
void beforeEach() {
|
||||
when(contextProvider.resolve(any(Path.class))).then((Answer<Path>) invocationOnMock -> invocationOnMock.getArgument(0));
|
||||
}
|
||||
|
||||
private RepositoryLocationResolver createResolver(RepositoryDAO pathBasedRepositoryDAO) {
|
||||
return new RepositoryLocationResolver(contextProvider, pathBasedRepositoryDAO, initialRepositoryLocationResolver);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldReturnPathFromDao() {
|
||||
Path repositoryPath = Paths.get("repos", "42");
|
||||
when(pathBasedRepositoryDAO.getPath("42")).thenReturn(repositoryPath);
|
||||
|
||||
RepositoryLocationResolver resolver = createResolver(pathBasedRepositoryDAO);
|
||||
Path path = resolver.getPath("42");
|
||||
|
||||
assertThat(path).isSameAs(repositoryPath);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldReturnInitialPathIfDaoIsNotPathBased() {
|
||||
Path repositoryPath = Paths.get("r", "42");
|
||||
when(initialRepositoryLocationResolver.getPath("42")).thenReturn(repositoryPath);
|
||||
|
||||
RepositoryLocationResolver resolver = createResolver(repositoryDAO);
|
||||
Path path = resolver.getPath("42");
|
||||
|
||||
assertThat(path).isSameAs(repositoryPath);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
package sonia.scm.repository.xml;
|
||||
|
||||
import sonia.scm.SCMContextProvider;
|
||||
import sonia.scm.repository.BasicRepositoryLocationResolver;
|
||||
import sonia.scm.repository.InitialRepositoryLocationResolver;
|
||||
import sonia.scm.repository.InternalRepositoryException;
|
||||
import sonia.scm.store.StoreConstants;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.time.Clock;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.function.BiConsumer;
|
||||
|
||||
import static sonia.scm.ContextEntry.ContextBuilder.entity;
|
||||
|
||||
/**
|
||||
* 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
|
||||
*
|
||||
* @since 2.0.0
|
||||
*/
|
||||
public class PathBasedRepositoryLocationResolver extends BasicRepositoryLocationResolver<Path> {
|
||||
|
||||
private static final String STORE_NAME = "repositories";
|
||||
|
||||
private final SCMContextProvider contextProvider;
|
||||
private final InitialRepositoryLocationResolver initialRepositoryLocationResolver;
|
||||
|
||||
private final PathDatabase pathDatabase;
|
||||
private final Map<String, Path> pathById;
|
||||
|
||||
private final Clock clock;
|
||||
|
||||
private Long creationTime;
|
||||
private Long lastModified;
|
||||
|
||||
@Inject
|
||||
public PathBasedRepositoryLocationResolver(SCMContextProvider contextProvider, InitialRepositoryLocationResolver initialRepositoryLocationResolver) {
|
||||
this(contextProvider, initialRepositoryLocationResolver, Clock.systemUTC());
|
||||
}
|
||||
|
||||
public PathBasedRepositoryLocationResolver(SCMContextProvider contextProvider, InitialRepositoryLocationResolver initialRepositoryLocationResolver, Clock clock) {
|
||||
super(Path.class);
|
||||
this.contextProvider = contextProvider;
|
||||
this.initialRepositoryLocationResolver = initialRepositoryLocationResolver;
|
||||
this.pathById = new ConcurrentHashMap<>();
|
||||
|
||||
this.clock = clock;
|
||||
|
||||
this.creationTime = clock.millis();
|
||||
pathDatabase = new PathDatabase(resolveStorePath());
|
||||
|
||||
read();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected <T> RepositoryLocationResolverInstance<T> create(Class<T> type) {
|
||||
return repositoryId -> {
|
||||
if (pathById.containsKey(repositoryId)) {
|
||||
return (T) contextProvider.resolve(pathById.get(repositoryId));
|
||||
} else {
|
||||
return (T) create(repositoryId);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
Path create(String repositoryId) {
|
||||
Path path = initialRepositoryLocationResolver.getPath(repositoryId);
|
||||
pathById.put(repositoryId, path);
|
||||
writePathDatabase();
|
||||
Path resolvedPath = contextProvider.resolve(path);
|
||||
try {
|
||||
Files.createDirectories(resolvedPath);
|
||||
} catch (IOException e) {
|
||||
throw new InternalRepositoryException(entity("Repository", repositoryId), "could not create directory for new repository", e);
|
||||
}
|
||||
return resolvedPath;
|
||||
}
|
||||
|
||||
Path remove(String repositoryId) {
|
||||
Path removedPath = pathById.remove(repositoryId);
|
||||
writePathDatabase();
|
||||
return contextProvider.resolve(removedPath);
|
||||
}
|
||||
|
||||
void forAllPaths(BiConsumer<String, Path> consumer) {
|
||||
pathById.forEach((id, path) -> consumer.accept(id, contextProvider.resolve(path)));
|
||||
}
|
||||
|
||||
void updateModificationDate() {
|
||||
this.writePathDatabase();
|
||||
}
|
||||
|
||||
private void writePathDatabase() {
|
||||
lastModified = clock.millis();
|
||||
pathDatabase.write(creationTime, lastModified, pathById);
|
||||
}
|
||||
|
||||
private void read() {
|
||||
Path storePath = resolveStorePath();
|
||||
|
||||
// Files.exists is slow on java 8
|
||||
if (storePath.toFile().exists()) {
|
||||
pathDatabase.read(this::onLoadDates, this::onLoadRepository);
|
||||
}
|
||||
}
|
||||
|
||||
private void onLoadDates(Long creationTime, Long lastModified) {
|
||||
this.creationTime = creationTime;
|
||||
this.lastModified = lastModified;
|
||||
}
|
||||
|
||||
public Long getCreationTime() {
|
||||
return creationTime;
|
||||
}
|
||||
|
||||
public Long getLastModified() {
|
||||
return lastModified;
|
||||
}
|
||||
|
||||
|
||||
private void onLoadRepository(String id, Path repositoryPath) {
|
||||
pathById.put(id, repositoryPath);
|
||||
}
|
||||
|
||||
private Path resolveStorePath() {
|
||||
return contextProvider.getBaseDirectory()
|
||||
.toPath()
|
||||
.resolve(StoreConstants.CONFIG_DIRECTORY_NAME)
|
||||
.resolve(STORE_NAME.concat(StoreConstants.FILE_EXTENSION));
|
||||
}
|
||||
}
|
||||
@@ -33,22 +33,18 @@ package sonia.scm.repository.xml;
|
||||
|
||||
//~--- non-JDK imports --------------------------------------------------------
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.inject.Singleton;
|
||||
import sonia.scm.SCMContextProvider;
|
||||
import sonia.scm.io.FileSystem;
|
||||
import sonia.scm.repository.InitialRepositoryLocationResolver;
|
||||
import sonia.scm.repository.InternalRepositoryException;
|
||||
import sonia.scm.repository.NamespaceAndName;
|
||||
import sonia.scm.repository.PathBasedRepositoryDAO;
|
||||
import sonia.scm.repository.Repository;
|
||||
import sonia.scm.repository.RepositoryDAO;
|
||||
import sonia.scm.store.StoreConstants;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.time.Clock;
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
@@ -57,82 +53,38 @@ import java.util.concurrent.ConcurrentHashMap;
|
||||
* @author Sebastian Sdorra
|
||||
*/
|
||||
@Singleton
|
||||
public class XmlRepositoryDAO implements PathBasedRepositoryDAO {
|
||||
public class XmlRepositoryDAO implements RepositoryDAO {
|
||||
|
||||
private static final String STORE_NAME = "repositories";
|
||||
|
||||
private final PathDatabase pathDatabase;
|
||||
private final MetadataStore metadataStore = new MetadataStore();
|
||||
|
||||
private final SCMContextProvider context;
|
||||
private final InitialRepositoryLocationResolver locationResolver;
|
||||
private final PathBasedRepositoryLocationResolver repositoryLocationResolver;
|
||||
private final FileSystem fileSystem;
|
||||
|
||||
private final Map<String, Path> pathById;
|
||||
private final Map<String, Repository> byId;
|
||||
private final Map<NamespaceAndName, Repository> byNamespaceAndName;
|
||||
|
||||
private final Clock clock;
|
||||
|
||||
private Long creationTime;
|
||||
private Long lastModified;
|
||||
|
||||
@Inject
|
||||
public XmlRepositoryDAO(SCMContextProvider context, InitialRepositoryLocationResolver locationResolver, FileSystem fileSystem) {
|
||||
this(context, locationResolver, fileSystem, Clock.systemUTC());
|
||||
}
|
||||
|
||||
XmlRepositoryDAO(SCMContextProvider context, InitialRepositoryLocationResolver locationResolver, FileSystem fileSystem, Clock clock) {
|
||||
this.context = context;
|
||||
this.locationResolver = locationResolver;
|
||||
public XmlRepositoryDAO(PathBasedRepositoryLocationResolver repositoryLocationResolver, FileSystem fileSystem) {
|
||||
this.repositoryLocationResolver = repositoryLocationResolver;
|
||||
this.fileSystem = fileSystem;
|
||||
|
||||
this.clock = clock;
|
||||
this.creationTime = clock.millis();
|
||||
|
||||
this.pathById = new ConcurrentHashMap<>();
|
||||
this.byId = new ConcurrentHashMap<>();
|
||||
this.byNamespaceAndName = new ConcurrentHashMap<>();
|
||||
|
||||
pathDatabase = new PathDatabase(resolveStorePath());
|
||||
read();
|
||||
init();
|
||||
}
|
||||
|
||||
private void read() {
|
||||
Path storePath = resolveStorePath();
|
||||
|
||||
// Files.exists is slow on java 8
|
||||
if (storePath.toFile().exists()) {
|
||||
pathDatabase.read(this::onLoadDates, this::onLoadRepository);
|
||||
}
|
||||
private void init() {
|
||||
repositoryLocationResolver.forAllPaths((repositoryId, repositoryPath) -> {
|
||||
Path metadataPath = resolveDataPath(repositoryPath);
|
||||
Repository repository = metadataStore.read(metadataPath);
|
||||
byNamespaceAndName.put(repository.getNamespaceAndName(), repository);
|
||||
byId.put(repositoryId, repository);
|
||||
});
|
||||
}
|
||||
|
||||
private void onLoadDates(Long creationTime, Long lastModified) {
|
||||
this.creationTime = creationTime;
|
||||
this.lastModified = lastModified;
|
||||
}
|
||||
|
||||
private void onLoadRepository(String id, Path repositoryPath) {
|
||||
Path metadataPath = resolveMetadataPath(context.resolve(repositoryPath));
|
||||
|
||||
Repository repository = metadataStore.read(metadataPath);
|
||||
|
||||
byId.put(id, repository);
|
||||
byNamespaceAndName.put(repository.getNamespaceAndName(), repository);
|
||||
pathById.put(id, repositoryPath);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
Path resolveStorePath() {
|
||||
return context.getBaseDirectory()
|
||||
.toPath()
|
||||
.resolve(StoreConstants.CONFIG_DIRECTORY_NAME)
|
||||
.resolve(STORE_NAME.concat(StoreConstants.FILE_EXTENSION));
|
||||
}
|
||||
|
||||
|
||||
@VisibleForTesting
|
||||
Path resolveMetadataPath(Path repositoryPath) {
|
||||
private Path resolveDataPath(Path repositoryPath) {
|
||||
return repositoryPath.resolve(StoreConstants.REPOSITORY_METADATA.concat(StoreConstants.FILE_EXTENSION));
|
||||
}
|
||||
|
||||
@@ -141,47 +93,27 @@ public class XmlRepositoryDAO implements PathBasedRepositoryDAO {
|
||||
return "xml";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long getCreationTime() {
|
||||
return creationTime;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long getLastModified() {
|
||||
return lastModified;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void add(Repository repository) {
|
||||
Repository clone = repository.clone();
|
||||
|
||||
Path repositoryPath = locationResolver.getPath(repository.getId());
|
||||
Path resolvedPath = context.resolve(repositoryPath);
|
||||
synchronized (this) {
|
||||
Path repositoryPath = repositoryLocationResolver.create(repository.getId());
|
||||
|
||||
try {
|
||||
fileSystem.create(resolvedPath.toFile());
|
||||
|
||||
Path metadataPath = resolveMetadataPath(resolvedPath);
|
||||
metadataStore.write(metadataPath, repository);
|
||||
|
||||
synchronized (this) {
|
||||
pathById.put(repository.getId(), repositoryPath);
|
||||
|
||||
byId.put(repository.getId(), clone);
|
||||
byNamespaceAndName.put(repository.getNamespaceAndName(), clone);
|
||||
|
||||
writePathDatabase();
|
||||
try {
|
||||
Path metadataPath = resolveDataPath(repositoryPath);
|
||||
metadataStore.write(metadataPath, repository);
|
||||
} catch (Exception e) {
|
||||
repositoryLocationResolver.remove(repository.getId());
|
||||
throw new InternalRepositoryException(repository, "failed to create filesystem", e);
|
||||
}
|
||||
|
||||
} catch (IOException e) {
|
||||
throw new InternalRepositoryException(repository, "failed to create filesystem", e);
|
||||
byId.put(repository.getId(), clone);
|
||||
byNamespaceAndName.put(repository.getNamespaceAndName(), clone);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void writePathDatabase() {
|
||||
lastModified = clock.millis();
|
||||
pathDatabase.write(creationTime, lastModified, pathById);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean contains(Repository repository) {
|
||||
@@ -224,12 +156,13 @@ public class XmlRepositoryDAO implements PathBasedRepositoryDAO {
|
||||
byNamespaceAndName.remove(prev.getNamespaceAndName());
|
||||
}
|
||||
byNamespaceAndName.put(clone.getNamespaceAndName(), clone);
|
||||
|
||||
writePathDatabase();
|
||||
}
|
||||
|
||||
Path repositoryPath = context.resolve(getPath(repository.getId()));
|
||||
Path metadataPath = resolveMetadataPath(repositoryPath);
|
||||
Path repositoryPath = repositoryLocationResolver
|
||||
.create(Path.class)
|
||||
.getLocation(repository.getId());
|
||||
Path metadataPath = resolveDataPath(repositoryPath);
|
||||
repositoryLocationResolver.updateModificationDate();
|
||||
metadataStore.write(metadataPath, clone);
|
||||
}
|
||||
|
||||
@@ -241,14 +174,9 @@ public class XmlRepositoryDAO implements PathBasedRepositoryDAO {
|
||||
if (prev != null) {
|
||||
byNamespaceAndName.remove(prev.getNamespaceAndName());
|
||||
}
|
||||
|
||||
path = pathById.remove(repository.getId());
|
||||
|
||||
writePathDatabase();
|
||||
path = repositoryLocationResolver.remove(repository.getId());
|
||||
}
|
||||
|
||||
path = context.resolve(path);
|
||||
|
||||
try {
|
||||
fileSystem.destroy(path.toFile());
|
||||
} catch (IOException e) {
|
||||
@@ -257,7 +185,12 @@ public class XmlRepositoryDAO implements PathBasedRepositoryDAO {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Path getPath(String repositoryId) {
|
||||
return pathById.get(repositoryId);
|
||||
public Long getCreationTime() {
|
||||
return repositoryLocationResolver.getCreationTime();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long getLastModified() {
|
||||
return repositoryLocationResolver.getLastModified();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,6 +40,7 @@ import sonia.scm.repository.RepositoryLocationResolver;
|
||||
import sonia.scm.util.IOUtil;
|
||||
|
||||
import java.io.File;
|
||||
import java.nio.file.Path;
|
||||
|
||||
//~--- JDK imports ------------------------------------------------------------
|
||||
|
||||
@@ -58,7 +59,7 @@ public abstract class FileBasedStoreFactory {
|
||||
private RepositoryLocationResolver repositoryLocationResolver;
|
||||
private Store store;
|
||||
|
||||
protected FileBasedStoreFactory(SCMContextProvider contextProvider , RepositoryLocationResolver repositoryLocationResolver, Store store) {
|
||||
protected FileBasedStoreFactory(SCMContextProvider contextProvider, RepositoryLocationResolver repositoryLocationResolver, Store store) {
|
||||
this.contextProvider = contextProvider;
|
||||
this.repositoryLocationResolver = repositoryLocationResolver;
|
||||
this.store = store;
|
||||
@@ -92,7 +93,7 @@ public abstract class FileBasedStoreFactory {
|
||||
* @return the store directory of a specific 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());
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -65,7 +65,7 @@ public class FileBlobStoreFactory extends FileBasedStoreFactory implements BlobS
|
||||
* @param keyGenerator key generator
|
||||
*/
|
||||
@Inject
|
||||
public FileBlobStoreFactory(SCMContextProvider contextProvider ,RepositoryLocationResolver repositoryLocationResolver, KeyGenerator keyGenerator) {
|
||||
public FileBlobStoreFactory(SCMContextProvider contextProvider , RepositoryLocationResolver repositoryLocationResolver, KeyGenerator keyGenerator) {
|
||||
super(contextProvider, repositoryLocationResolver, Store.BLOB);
|
||||
this.keyGenerator = keyGenerator;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,172 @@
|
||||
package sonia.scm.repository.xml;
|
||||
|
||||
import com.google.common.base.Charsets;
|
||||
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 org.mockito.junit.jupiter.MockitoSettings;
|
||||
import org.mockito.quality.Strictness;
|
||||
import sonia.scm.SCMContextProvider;
|
||||
import sonia.scm.repository.InitialRepositoryLocationResolver;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.time.Clock;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
@ExtendWith(TempDirectory.class)
|
||||
@MockitoSettings(strictness = Strictness.LENIENT)
|
||||
class PathBasedRepositoryLocationResolverTest {
|
||||
|
||||
private static final long CREATION_TIME = 42;
|
||||
|
||||
@Mock
|
||||
private SCMContextProvider contextProvider;
|
||||
|
||||
@Mock
|
||||
private InitialRepositoryLocationResolver initialRepositoryLocationResolver;
|
||||
|
||||
@Mock
|
||||
private Clock clock;
|
||||
|
||||
private Path basePath;
|
||||
|
||||
private PathBasedRepositoryLocationResolver resolver;
|
||||
|
||||
@BeforeEach
|
||||
void beforeEach(@TempDirectory.TempDir Path temp) {
|
||||
this.basePath = temp;
|
||||
when(contextProvider.getBaseDirectory()).thenReturn(temp.toFile());
|
||||
when(contextProvider.resolve(any(Path.class))).thenAnswer(invocation -> invocation.getArgument(0));
|
||||
when(initialRepositoryLocationResolver.getPath(anyString())).thenAnswer(invocation -> temp.resolve(invocation.getArgument(0).toString()));
|
||||
when(clock.millis()).thenReturn(CREATION_TIME);
|
||||
resolver = createResolver();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldCreateInitialDirectory() {
|
||||
Path path = resolver.forClass(Path.class).getLocation("newId");
|
||||
|
||||
assertThat(path).isEqualTo(basePath.resolve("newId"));
|
||||
assertThat(path).isDirectory();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldPersistInitialDirectory() {
|
||||
resolver.forClass(Path.class).getLocation("newId");
|
||||
|
||||
String content = getXmlFileContent();
|
||||
|
||||
assertThat(content).contains("newId");
|
||||
assertThat(content).contains(basePath.resolve("newId").toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldPersistWithCreationDate() {
|
||||
long now = CREATION_TIME + 100;
|
||||
when(clock.millis()).thenReturn(now);
|
||||
|
||||
resolver.forClass(Path.class).getLocation("newId");
|
||||
|
||||
assertThat(resolver.getCreationTime()).isEqualTo(CREATION_TIME);
|
||||
|
||||
String content = getXmlFileContent();
|
||||
assertThat(content).contains("creation-time=\"" + CREATION_TIME + "\"");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldUpdateWithModifiedDate() {
|
||||
long now = CREATION_TIME + 100;
|
||||
when(clock.millis()).thenReturn(now);
|
||||
|
||||
resolver.forClass(Path.class).getLocation("newId");
|
||||
|
||||
assertThat(resolver.getCreationTime()).isEqualTo(CREATION_TIME);
|
||||
assertThat(resolver.getLastModified()).isEqualTo(now);
|
||||
|
||||
String content = getXmlFileContent();
|
||||
assertThat(content).contains("creation-time=\"" + CREATION_TIME + "\"");
|
||||
assertThat(content).contains("last-modified=\"" + now + "\"");
|
||||
}
|
||||
|
||||
@Nested
|
||||
class WithExistingData {
|
||||
|
||||
private PathBasedRepositoryLocationResolver resolverWithExistingData;
|
||||
|
||||
@BeforeEach
|
||||
void createExistingDatabase() {
|
||||
resolver.forClass(Path.class).getLocation("existingId_1");
|
||||
resolver.forClass(Path.class).getLocation("existingId_2");
|
||||
resolverWithExistingData = createResolver();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldInitWithExistingData() {
|
||||
Map<String, Path> foundRepositories = new HashMap<>();
|
||||
resolverWithExistingData.forAllPaths(
|
||||
foundRepositories::put
|
||||
);
|
||||
assertThat(foundRepositories)
|
||||
.containsKeys("existingId_1", "existingId_2");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRemoveFromFile() {
|
||||
resolverWithExistingData.remove("existingId_1");
|
||||
|
||||
assertThat(getXmlFileContent()).doesNotContain("existingId_1");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldNotUpdateModificationDateForExistingDirectoryMapping() {
|
||||
long now = CREATION_TIME + 100;
|
||||
Path path = resolverWithExistingData.create(Path.class).getLocation("existingId_1");
|
||||
|
||||
assertThat(path).isEqualTo(basePath.resolve("existingId_1"));
|
||||
|
||||
String content = getXmlFileContent();
|
||||
assertThat(content).doesNotContain("last-modified=\"" + now + "\"");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldNotCreateDirectoryForExistingMapping() throws IOException {
|
||||
Files.delete(basePath.resolve("existingId_1"));
|
||||
|
||||
Path path = resolverWithExistingData.create(Path.class).getLocation("existingId_1");
|
||||
|
||||
assertThat(path).doesNotExist();
|
||||
}
|
||||
}
|
||||
|
||||
private String getXmlFileContent() {
|
||||
Path storePath = basePath.resolve("config").resolve("repositories.xml");
|
||||
|
||||
assertThat(storePath).isRegularFile();
|
||||
return content(storePath);
|
||||
}
|
||||
|
||||
private PathBasedRepositoryLocationResolver createResolver() {
|
||||
return new PathBasedRepositoryLocationResolver(contextProvider, initialRepositoryLocationResolver, clock);
|
||||
}
|
||||
|
||||
private String content(Path storePath) {
|
||||
try {
|
||||
return new String(Files.readAllBytes(storePath), Charsets.UTF_8);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,376 +2,309 @@ package sonia.scm.repository.xml;
|
||||
|
||||
|
||||
import com.google.common.base.Charsets;
|
||||
import com.google.common.io.Resources;
|
||||
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.ArgumentCaptor;
|
||||
import org.mockito.Captor;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.invocation.InvocationOnMock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.mockito.junit.jupiter.MockitoSettings;
|
||||
import org.mockito.quality.Strictness;
|
||||
import sonia.scm.SCMContextProvider;
|
||||
import sonia.scm.io.DefaultFileSystem;
|
||||
import sonia.scm.io.FileSystem;
|
||||
import sonia.scm.repository.InitialRepositoryLocationResolver;
|
||||
import sonia.scm.repository.NamespaceAndName;
|
||||
import sonia.scm.repository.Repository;
|
||||
import sonia.scm.repository.RepositoryPermission;
|
||||
import sonia.scm.repository.RepositoryTestData;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.time.Clock;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
import java.util.function.BiConsumer;
|
||||
|
||||
import static java.util.Arrays.asList;
|
||||
import static java.util.Collections.singletonList;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.junit.jupiter.api.Assertions.fail;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.Mockito.doNothing;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@ExtendWith({MockitoExtension.class, TempDirectory.class})
|
||||
@MockitoSettings(strictness = Strictness.LENIENT)
|
||||
class XmlRepositoryDAOTest {
|
||||
|
||||
@Mock
|
||||
private SCMContextProvider context;
|
||||
private final Repository REPOSITORY = createRepository("42");
|
||||
|
||||
@Mock
|
||||
private InitialRepositoryLocationResolver locationResolver;
|
||||
private PathBasedRepositoryLocationResolver locationResolver;
|
||||
|
||||
@Captor
|
||||
private ArgumentCaptor<BiConsumer<String, Path>> forAllCaptor;
|
||||
|
||||
private FileSystem fileSystem = new DefaultFileSystem();
|
||||
|
||||
private XmlRepositoryDAO dao;
|
||||
|
||||
private Path baseDirectory;
|
||||
|
||||
private AtomicLong atomicClock;
|
||||
|
||||
@BeforeEach
|
||||
void createDAO(@TempDirectory.TempDir Path baseDirectory) {
|
||||
this.baseDirectory = baseDirectory;
|
||||
this.atomicClock = new AtomicLong();
|
||||
|
||||
when(locationResolver.getPath("42")).thenReturn(Paths.get("repos", "42"));
|
||||
when(locationResolver.getPath("42+1")).thenReturn(Paths.get("repos", "puzzle"));
|
||||
|
||||
when(context.getBaseDirectory()).thenReturn(baseDirectory.toFile());
|
||||
when(context.resolve(any(Path.class))).then(ic -> {
|
||||
Path path = ic.getArgument(0);
|
||||
return baseDirectory.resolve(path);
|
||||
});
|
||||
|
||||
dao = createDAO();
|
||||
void createDAO(@TempDirectory.TempDir Path basePath) {
|
||||
when(locationResolver.create(Path.class)).thenReturn(locationResolver::create);
|
||||
when(locationResolver.create(anyString())).thenAnswer(invocation -> createMockedRepoPath(basePath, invocation));
|
||||
when(locationResolver.remove(anyString())).thenAnswer(invocation -> basePath.resolve(invocation.getArgument(0).toString()));
|
||||
}
|
||||
|
||||
private XmlRepositoryDAO createDAO() {
|
||||
Clock clock = mock(Clock.class);
|
||||
when(clock.millis()).then(ic -> atomicClock.incrementAndGet());
|
||||
private Path createMockedRepoPath(@TempDirectory.TempDir Path basePath, InvocationOnMock invocation) {
|
||||
Path resolvedPath = basePath.resolve(invocation.getArgument(0).toString());
|
||||
try {
|
||||
Files.createDirectories(resolvedPath);
|
||||
} catch (IOException e) {
|
||||
fail(e);
|
||||
}
|
||||
return resolvedPath;
|
||||
}
|
||||
|
||||
return new XmlRepositoryDAO(context, locationResolver, fileSystem, clock);
|
||||
@Nested
|
||||
class WithEmptyDatabase {
|
||||
|
||||
@BeforeEach
|
||||
void createDAO() {
|
||||
dao = new XmlRepositoryDAO(locationResolver, fileSystem);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldReturnXmlType() {
|
||||
assertThat(dao.getType()).isEqualTo("xml");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldReturnCreationTimeOfLocationResolver() {
|
||||
long now = 42L;
|
||||
when(locationResolver.getCreationTime()).thenReturn(now);
|
||||
assertThat(dao.getCreationTime()).isEqualTo(now);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldReturnLasModifiedOfLocationResolver() {
|
||||
long now = 42L;
|
||||
when(locationResolver.getLastModified()).thenReturn(now);
|
||||
assertThat(dao.getLastModified()).isEqualTo(now);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldReturnTrueForEachContainsMethod() {
|
||||
dao.add(REPOSITORY);
|
||||
|
||||
assertThat(dao.contains(REPOSITORY)).isTrue();
|
||||
assertThat(dao.contains(REPOSITORY.getId())).isTrue();
|
||||
assertThat(dao.contains(REPOSITORY.getNamespaceAndName())).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldPersistRepository() {
|
||||
dao.add(REPOSITORY);
|
||||
|
||||
String content = getXmlFileContent(REPOSITORY.getId());
|
||||
|
||||
assertThat(content).contains("<id>42</id>");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldDeleteDataFile() {
|
||||
dao.add(REPOSITORY);
|
||||
dao.delete(REPOSITORY);
|
||||
|
||||
assertThat(metadataFile(REPOSITORY.getId())).doesNotExist();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldModifyRepository() {
|
||||
dao.add(REPOSITORY);
|
||||
Repository changedRepository = REPOSITORY.clone();
|
||||
changedRepository.setContact("change");
|
||||
|
||||
dao.modify(changedRepository);
|
||||
|
||||
String content = getXmlFileContent(REPOSITORY.getId());
|
||||
|
||||
assertThat(content).contains("change");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldReturnFalseForEachContainsMethod() {
|
||||
assertThat(dao.contains(REPOSITORY)).isFalse();
|
||||
assertThat(dao.contains(REPOSITORY.getId())).isFalse();
|
||||
assertThat(dao.contains(REPOSITORY.getNamespaceAndName())).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldReturnNullForEachGetMethod() {
|
||||
assertThat(dao.get("42")).isNull();
|
||||
assertThat(dao.get(new NamespaceAndName("hitchhiker", "HeartOfGold"))).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldReturnRepository() {
|
||||
dao.add(REPOSITORY);
|
||||
|
||||
assertThat(dao.get("42")).isEqualTo(REPOSITORY);
|
||||
assertThat(dao.get(new NamespaceAndName("space", "42"))).isEqualTo(REPOSITORY);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldNotReturnTheSameInstance() {
|
||||
dao.add(REPOSITORY);
|
||||
|
||||
Repository repository = dao.get("42");
|
||||
assertThat(repository).isNotSameAs(REPOSITORY);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldReturnAllRepositories() {
|
||||
dao.add(REPOSITORY);
|
||||
|
||||
Repository secondRepository = createRepository("23");
|
||||
dao.add(secondRepository);
|
||||
|
||||
Collection<Repository> repositories = dao.getAll();
|
||||
assertThat(repositories)
|
||||
.containsExactlyInAnyOrder(REPOSITORY, secondRepository);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldModifyRepositoryTwice() {
|
||||
REPOSITORY.setDescription("HeartOfGold");
|
||||
dao.add(REPOSITORY);
|
||||
assertThat(dao.get("42").getDescription()).isEqualTo("HeartOfGold");
|
||||
|
||||
Repository heartOfGold = createRepository("42");
|
||||
heartOfGold.setDescription("Heart of Gold");
|
||||
dao.modify(heartOfGold);
|
||||
|
||||
assertThat(dao.get("42").getDescription()).isEqualTo("Heart of Gold");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRemoveRepository() {
|
||||
dao.add(REPOSITORY);
|
||||
assertThat(dao.contains("42")).isTrue();
|
||||
|
||||
dao.delete(REPOSITORY);
|
||||
assertThat(dao.contains("42")).isFalse();
|
||||
assertThat(dao.contains(REPOSITORY.getNamespaceAndName())).isFalse();
|
||||
|
||||
Path storePath = metadataFile(REPOSITORY.getId());
|
||||
|
||||
assertThat(storePath).doesNotExist();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRenameTheRepository() {
|
||||
dao.add(REPOSITORY);
|
||||
|
||||
REPOSITORY.setNamespace("hg2tg");
|
||||
REPOSITORY.setName("hog");
|
||||
|
||||
dao.modify(REPOSITORY);
|
||||
|
||||
Repository repository = dao.get("42");
|
||||
assertThat(repository.getNamespace()).isEqualTo("hg2tg");
|
||||
assertThat(repository.getName()).isEqualTo("hog");
|
||||
|
||||
assertThat(dao.contains(new NamespaceAndName("hg2tg", "hog"))).isTrue();
|
||||
assertThat(dao.contains(new NamespaceAndName("hitchhiker", "HeartOfGold"))).isFalse();
|
||||
|
||||
String content = getXmlFileContent(REPOSITORY.getId());
|
||||
assertThat(content).contains("<name>hog</name>");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldDeleteRepositoryEvenWithChangedNamespace() {
|
||||
dao.add(REPOSITORY);
|
||||
|
||||
REPOSITORY.setNamespace("hg2tg");
|
||||
REPOSITORY.setName("hog");
|
||||
|
||||
dao.delete(REPOSITORY);
|
||||
|
||||
assertThat(dao.contains(new NamespaceAndName("space", "42"))).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRemoveRepositoryDirectoryAfterDeletion() {
|
||||
dao.add(REPOSITORY);
|
||||
|
||||
Path path = locationResolver.create(REPOSITORY.getId());
|
||||
assertThat(path).isDirectory();
|
||||
|
||||
dao.delete(REPOSITORY);
|
||||
assertThat(path).doesNotExist();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldPersistPermissions() {
|
||||
REPOSITORY.setPermissions(asList(new RepositoryPermission("trillian", asList("read", "write"), false), new RepositoryPermission("vogons", singletonList("delete"), true)));
|
||||
dao.add(REPOSITORY);
|
||||
|
||||
String content = getXmlFileContent(REPOSITORY.getId());
|
||||
assertThat(content).containsSubsequence("trillian", "<verb>read</verb>", "<verb>write</verb>");
|
||||
assertThat(content).containsSubsequence("vogons", "<verb>delete</verb>");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldUpdateRepositoryPathDatabse() {
|
||||
dao.add(REPOSITORY);
|
||||
|
||||
verify(locationResolver, never()).updateModificationDate();
|
||||
|
||||
dao.modify(REPOSITORY);
|
||||
|
||||
verify(locationResolver).updateModificationDate();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldReturnXmlType() {
|
||||
assertThat(dao.getType()).isEqualTo("xml");
|
||||
void shouldReadExistingRepositoriesFromPathDatabase(@TempDirectory.TempDir Path basePath) throws IOException {
|
||||
doNothing().when(locationResolver).forAllPaths(forAllCaptor.capture());
|
||||
XmlRepositoryDAO dao = new XmlRepositoryDAO(locationResolver, fileSystem);
|
||||
|
||||
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"));
|
||||
|
||||
forAllCaptor.getValue().accept("existing", repositoryPath);
|
||||
|
||||
assertThat(dao.contains(new NamespaceAndName("space", "existing"))).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldReturnCreationTimeAfterCreation() {
|
||||
long now = atomicClock.get();
|
||||
assertThat(dao.getCreationTime()).isEqualTo(now);
|
||||
}
|
||||
private String getXmlFileContent(String id) {
|
||||
Path storePath = metadataFile(id);
|
||||
|
||||
@Test
|
||||
void shouldNotReturnLastModifiedAfterCreation() {
|
||||
assertThat(dao.getLastModified()).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldReturnTrueForEachContainsMethod() {
|
||||
Repository heartOfGold = createHeartOfGold();
|
||||
dao.add(heartOfGold);
|
||||
|
||||
assertThat(dao.contains(heartOfGold)).isTrue();
|
||||
assertThat(dao.contains(heartOfGold.getId())).isTrue();
|
||||
assertThat(dao.contains(heartOfGold.getNamespaceAndName())).isTrue();
|
||||
}
|
||||
|
||||
private Repository createHeartOfGold() {
|
||||
Repository heartOfGold = RepositoryTestData.createHeartOfGold();
|
||||
heartOfGold.setId("42");
|
||||
return heartOfGold;
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldReturnFalseForEachContainsMethod() {
|
||||
Repository heartOfGold = createHeartOfGold();
|
||||
|
||||
assertThat(dao.contains(heartOfGold)).isFalse();
|
||||
assertThat(dao.contains(heartOfGold.getId())).isFalse();
|
||||
assertThat(dao.contains(heartOfGold.getNamespaceAndName())).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldReturnNullForEachGetMethod() {
|
||||
assertThat(dao.get("42")).isNull();
|
||||
assertThat(dao.get(new NamespaceAndName("hitchhiker","HeartOfGold"))).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldReturnRepository() {
|
||||
Repository heartOfGold = createHeartOfGold();
|
||||
dao.add(heartOfGold);
|
||||
|
||||
assertThat(dao.get("42")).isEqualTo(heartOfGold);
|
||||
assertThat(dao.get(new NamespaceAndName("hitchhiker","HeartOfGold"))).isEqualTo(heartOfGold);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldNotReturnTheSameInstance() {
|
||||
Repository heartOfGold = createHeartOfGold();
|
||||
dao.add(heartOfGold);
|
||||
|
||||
Repository repository = dao.get("42");
|
||||
assertThat(repository).isNotSameAs(heartOfGold);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldReturnAllRepositories() {
|
||||
Repository heartOfGold = createHeartOfGold();
|
||||
dao.add(heartOfGold);
|
||||
|
||||
Repository puzzle = createPuzzle();
|
||||
dao.add(puzzle);
|
||||
|
||||
Collection<Repository> repositories = dao.getAll();
|
||||
assertThat(repositories).containsExactlyInAnyOrder(heartOfGold, puzzle);
|
||||
}
|
||||
|
||||
private Repository createPuzzle() {
|
||||
Repository puzzle = RepositoryTestData.create42Puzzle();
|
||||
puzzle.setId("42+1");
|
||||
return puzzle;
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldModifyRepository() {
|
||||
Repository heartOfGold = createHeartOfGold();
|
||||
heartOfGold.setDescription("HeartOfGold");
|
||||
dao.add(heartOfGold);
|
||||
assertThat(dao.get("42").getDescription()).isEqualTo("HeartOfGold");
|
||||
|
||||
heartOfGold = createHeartOfGold();
|
||||
heartOfGold.setDescription("Heart of Gold");
|
||||
dao.modify(heartOfGold);
|
||||
|
||||
assertThat(dao.get("42").getDescription()).isEqualTo("Heart of Gold");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRemoveRepository() {
|
||||
Repository heartOfGold = createHeartOfGold();
|
||||
dao.add(heartOfGold);
|
||||
assertThat(dao.contains("42")).isTrue();
|
||||
|
||||
dao.delete(heartOfGold);
|
||||
assertThat(dao.contains("42")).isFalse();
|
||||
assertThat(dao.contains(new NamespaceAndName("hitchhiker", "HeartOfGold"))).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldUpdateLastModifiedAfterEachWriteOperation() {
|
||||
Repository heartOfGold = createHeartOfGold();
|
||||
dao.add(heartOfGold);
|
||||
|
||||
Long firstLastModified = dao.getLastModified();
|
||||
assertThat(firstLastModified).isNotNull();
|
||||
|
||||
Repository puzzle = createPuzzle();
|
||||
dao.add(puzzle);
|
||||
|
||||
Long lastModifiedAdded = dao.getLastModified();
|
||||
assertThat(lastModifiedAdded).isGreaterThan(firstLastModified);
|
||||
|
||||
heartOfGold.setDescription("Heart of Gold");
|
||||
dao.modify(heartOfGold);
|
||||
|
||||
Long lastModifiedModified = dao.getLastModified();
|
||||
assertThat(lastModifiedModified).isGreaterThan(lastModifiedAdded);
|
||||
|
||||
dao.delete(puzzle);
|
||||
|
||||
Long lastModifiedRemoved = dao.getLastModified();
|
||||
assertThat(lastModifiedRemoved).isGreaterThan(lastModifiedModified);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRenameTheRepository() {
|
||||
Repository heartOfGold = createHeartOfGold();
|
||||
dao.add(heartOfGold);
|
||||
|
||||
heartOfGold.setNamespace("hg2tg");
|
||||
heartOfGold.setName("hog");
|
||||
|
||||
dao.modify(heartOfGold);
|
||||
|
||||
Repository repository = dao.get("42");
|
||||
assertThat(repository.getNamespace()).isEqualTo("hg2tg");
|
||||
assertThat(repository.getName()).isEqualTo("hog");
|
||||
|
||||
assertThat(dao.contains(new NamespaceAndName("hg2tg", "hog"))).isTrue();
|
||||
assertThat(dao.contains(new NamespaceAndName("hitchhiker", "HeartOfGold"))).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldDeleteRepositoryEvenWithChangedNamespace() {
|
||||
Repository heartOfGold = createHeartOfGold();
|
||||
dao.add(heartOfGold);
|
||||
|
||||
heartOfGold.setNamespace("hg2tg");
|
||||
heartOfGold.setName("hog");
|
||||
|
||||
dao.delete(heartOfGold);
|
||||
|
||||
assertThat(dao.contains(new NamespaceAndName("hitchhiker", "HeartOfGold"))).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldReturnThePathForTheRepository() {
|
||||
Path repositoryPath = Paths.get("r", "42");
|
||||
when(locationResolver.getPath("42")).thenReturn(repositoryPath);
|
||||
|
||||
Repository heartOfGold = createHeartOfGold();
|
||||
dao.add(heartOfGold);
|
||||
|
||||
Path path = dao.getPath("42");
|
||||
assertThat(path).isEqualTo(repositoryPath);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldCreateTheDirectoryForTheRepository() {
|
||||
Path repositoryPath = Paths.get("r", "42");
|
||||
when(locationResolver.getPath("42")).thenReturn(repositoryPath);
|
||||
|
||||
Repository heartOfGold = createHeartOfGold();
|
||||
dao.add(heartOfGold);
|
||||
|
||||
Path path = getAbsolutePathFromDao("42");
|
||||
assertThat(path).isDirectory();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRemoveRepositoryDirectoryAfterDeletion() {
|
||||
Repository heartOfGold = createHeartOfGold();
|
||||
dao.add(heartOfGold);
|
||||
|
||||
Path path = getAbsolutePathFromDao(heartOfGold.getId());
|
||||
assertThat(path).isDirectory();
|
||||
|
||||
dao.delete(heartOfGold);
|
||||
assertThat(path).doesNotExist();
|
||||
}
|
||||
|
||||
private Path getAbsolutePathFromDao(String id) {
|
||||
return context.resolve(dao.getPath(id));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldCreateRepositoryPathDatabase() throws IOException {
|
||||
Repository heartOfGold = createHeartOfGold();
|
||||
dao.add(heartOfGold);
|
||||
|
||||
Path storePath = dao.resolveStorePath();
|
||||
assertThat(storePath).isRegularFile();
|
||||
|
||||
String content = content(storePath);
|
||||
|
||||
assertThat(content).contains(heartOfGold.getId());
|
||||
assertThat(content).contains(dao.getPath(heartOfGold.getId()).toString());
|
||||
return content(storePath);
|
||||
}
|
||||
|
||||
private String content(Path storePath) throws IOException {
|
||||
return new String(Files.readAllBytes(storePath), Charsets.UTF_8);
|
||||
private Path metadataFile(String id) {
|
||||
return locationResolver.create(id).resolve("metadata.xml");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldStoreRepositoryMetadataAfterAdd() throws IOException {
|
||||
Repository heartOfGold = createHeartOfGold();
|
||||
dao.add(heartOfGold);
|
||||
|
||||
Path repositoryDirectory = getAbsolutePathFromDao(heartOfGold.getId());
|
||||
Path metadataPath = dao.resolveMetadataPath(repositoryDirectory);
|
||||
|
||||
assertThat(metadataPath).isRegularFile();
|
||||
|
||||
String content = content(metadataPath);
|
||||
assertThat(content).contains(heartOfGold.getName());
|
||||
assertThat(content).contains(heartOfGold.getNamespace());
|
||||
assertThat(content).contains(heartOfGold.getDescription());
|
||||
private String content(Path storePath) {
|
||||
try {
|
||||
return new String(Files.readAllBytes(storePath), Charsets.UTF_8);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldUpdateRepositoryMetadataAfterModify() throws IOException {
|
||||
Repository heartOfGold = createHeartOfGold();
|
||||
dao.add(heartOfGold);
|
||||
|
||||
heartOfGold.setDescription("Awesome Spaceship");
|
||||
dao.modify(heartOfGold);
|
||||
|
||||
Path repositoryDirectory = getAbsolutePathFromDao(heartOfGold.getId());
|
||||
Path metadataPath = dao.resolveMetadataPath(repositoryDirectory);
|
||||
|
||||
String content = content(metadataPath);
|
||||
assertThat(content).contains("Awesome Spaceship");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldPersistPermissions() throws IOException {
|
||||
Repository heartOfGold = createHeartOfGold();
|
||||
heartOfGold.setPermissions(asList(new RepositoryPermission("trillian", asList("read", "write"), false), new RepositoryPermission("vogons", Collections.singletonList("delete"), true)));
|
||||
dao.add(heartOfGold);
|
||||
|
||||
Path repositoryDirectory = getAbsolutePathFromDao(heartOfGold.getId());
|
||||
Path metadataPath = dao.resolveMetadataPath(repositoryDirectory);
|
||||
|
||||
String content = content(metadataPath);
|
||||
System.out.println(content);
|
||||
assertThat(content).containsSubsequence("trillian", "<verb>read</verb>", "<verb>write</verb>");
|
||||
assertThat(content).containsSubsequence("vogons", "<verb>delete</verb>");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldReadPathDatabaseAndMetadataOfRepositories() {
|
||||
Repository heartOfGold = createHeartOfGold();
|
||||
dao.add(heartOfGold);
|
||||
|
||||
// reload data
|
||||
dao = createDAO();
|
||||
|
||||
heartOfGold = dao.get("42");
|
||||
assertThat(heartOfGold.getName()).isEqualTo("HeartOfGold");
|
||||
|
||||
Path path = getAbsolutePathFromDao(heartOfGold.getId());
|
||||
assertThat(path).isDirectory();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldReadCreationTimeAndLastModifedDateFromDatabase() {
|
||||
Repository heartOfGold = createHeartOfGold();
|
||||
dao.add(heartOfGold);
|
||||
|
||||
Long creationTime = dao.getCreationTime();
|
||||
Long lastModified = dao.getLastModified();
|
||||
|
||||
// reload data
|
||||
dao = createDAO();
|
||||
|
||||
assertThat(dao.getCreationTime()).isEqualTo(creationTime);
|
||||
assertThat(dao.getLastModified()).isEqualTo(lastModified);
|
||||
private static Repository createRepository(String id) {
|
||||
return new Repository(id, "xml", "space", id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<repositories>
|
||||
<properties/>
|
||||
<id>existing</id>
|
||||
<namespace>space</namespace>
|
||||
<name>existing</name>
|
||||
<public>false</public>
|
||||
<archived>false</archived>
|
||||
<type>xml</type>
|
||||
</repositories>
|
||||
@@ -26,7 +26,7 @@ public class GitRepositoryContextResolver implements RepositoryContextResolver {
|
||||
public RepositoryContext resolve(String[] args) {
|
||||
NamespaceAndName namespaceAndName = extractNamespaceAndName(args);
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ package sonia.scm.protocolcommand.git;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.Answers;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
@@ -16,6 +17,7 @@ import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
@@ -25,7 +27,7 @@ class GitRepositoryContextResolverTest {
|
||||
|
||||
@Mock
|
||||
RepositoryManager repositoryManager;
|
||||
@Mock
|
||||
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
|
||||
RepositoryLocationResolver locationResolver;
|
||||
|
||||
@InjectMocks
|
||||
@@ -35,7 +37,7 @@ class GitRepositoryContextResolverTest {
|
||||
void shouldResolveCorrectRepository() throws IOException {
|
||||
when(repositoryManager.get(new NamespaceAndName("space", "X"))).thenReturn(REPOSITORY);
|
||||
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"});
|
||||
|
||||
|
||||
@@ -37,6 +37,7 @@ package sonia.scm.repository;
|
||||
|
||||
import org.junit.Assume;
|
||||
import sonia.scm.SCMContext;
|
||||
import sonia.scm.TempDirRepositoryLocationResolver;
|
||||
import sonia.scm.store.InMemoryConfigurationStoreFactory;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
@@ -101,13 +102,12 @@ public final class HgTestUtil
|
||||
|
||||
context.setBaseDirectory(directory);
|
||||
|
||||
PathBasedRepositoryDAO repoDao = mock(PathBasedRepositoryDAO.class);
|
||||
RepositoryDAO repoDao = mock(RepositoryDAO.class);
|
||||
|
||||
RepositoryLocationResolver repositoryLocationResolver = new RepositoryLocationResolver(context, repoDao, new InitialRepositoryLocationResolver());
|
||||
RepositoryLocationResolver repositoryLocationResolver = new TempDirRepositoryLocationResolver(directory);
|
||||
HgRepositoryHandler handler =
|
||||
new HgRepositoryHandler(new InMemoryConfigurationStoreFactory(), new HgContextProvider(), repositoryLocationResolver, null, null);
|
||||
Path repoDir = directory.toPath();
|
||||
when(repoDao.getPath(any())).thenReturn(repoDir);
|
||||
handler.init(context);
|
||||
|
||||
return handler;
|
||||
|
||||
@@ -91,7 +91,7 @@ public class AbstractTestBase
|
||||
contextProvider = MockUtil.getSCMContextProvider(tempDirectory);
|
||||
fileSystem = new DefaultFileSystem();
|
||||
InitialRepositoryLocationResolver initialRepoLocationResolver = new InitialRepositoryLocationResolver();
|
||||
repositoryLocationResolver = new RepositoryLocationResolver(contextProvider, repositoryDAO, initialRepoLocationResolver);
|
||||
repositoryLocationResolver = new TempDirRepositoryLocationResolver(tempDirectory);
|
||||
postSetUp();
|
||||
}
|
||||
|
||||
@@ -254,4 +254,5 @@ public class AbstractTestBase
|
||||
subjectThreadState = createThreadState(subject);
|
||||
subjectThreadState.bind();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -74,7 +74,7 @@ public abstract class ManagerTestBase<T extends ModelObject>
|
||||
contextProvider = MockUtil.getSCMContextProvider(temp);
|
||||
InitialRepositoryLocationResolver initialRepositoryLocationResolver = new InitialRepositoryLocationResolver();
|
||||
RepositoryDAO repoDao = mock(RepositoryDAO.class);
|
||||
locationResolver = new RepositoryLocationResolver(contextProvider, repoDao ,initialRepositoryLocationResolver);
|
||||
locationResolver = new TempDirRepositoryLocationResolver(temp);
|
||||
manager = createManager();
|
||||
manager.init(contextProvider);
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -44,6 +44,7 @@ import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
@@ -56,7 +57,7 @@ import static org.mockito.Mockito.when;
|
||||
public abstract class SimpleRepositoryHandlerTestBase extends AbstractTestBase {
|
||||
|
||||
|
||||
protected PathBasedRepositoryDAO repoDao = mock(PathBasedRepositoryDAO.class);
|
||||
protected RepositoryDAO repoDao = mock(RepositoryDAO.class);
|
||||
protected Path repoPath;
|
||||
protected Repository repository;
|
||||
|
||||
@@ -78,7 +79,11 @@ public abstract class SimpleRepositoryHandlerTestBase extends AbstractTestBase {
|
||||
|
||||
locationResolver = mock(RepositoryLocationResolver.class);
|
||||
|
||||
when(locationResolver.getPath(anyString())).then(ic -> {
|
||||
RepositoryLocationResolver.RepositoryLocationResolverInstance instanceMock = mock(RepositoryLocationResolver.RepositoryLocationResolverInstance.class);
|
||||
when(locationResolver.create(any())).thenReturn(instanceMock);
|
||||
when(locationResolver.supportsLocationType(any())).thenReturn(true);
|
||||
|
||||
when(instanceMock.getLocation(anyString())).then(ic -> {
|
||||
String id = ic.getArgument(0);
|
||||
return baseDirectory.toPath().resolve(id);
|
||||
});
|
||||
@@ -107,7 +112,7 @@ public abstract class SimpleRepositoryHandlerTestBase extends AbstractTestBase {
|
||||
repository = RepositoryTestData.createHeartOfGold();
|
||||
File repoDirectory = new File(baseDirectory, repository.getId());
|
||||
repoPath = repoDirectory.toPath();
|
||||
when(repoDao.getPath(repository.getId())).thenReturn(repoPath);
|
||||
// when(repoDao.getPath(repository.getId())).thenReturn(repoPath);
|
||||
return new File(repoDirectory, AbstractSimpleRepositoryHandler.REPOSITORIES_NATIVE_DIRECTORY);
|
||||
}
|
||||
|
||||
|
||||
@@ -184,6 +184,12 @@
|
||||
<version>${guice.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.google.inject.extensions</groupId>
|
||||
<artifactId>guice-assistedinject</artifactId>
|
||||
<version>${guice.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- event bus -->
|
||||
|
||||
<dependency>
|
||||
|
||||
@@ -37,6 +37,7 @@ import com.google.common.base.Throwables;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.inject.Injector;
|
||||
import com.google.inject.Module;
|
||||
import com.google.inject.assistedinject.Assisted;
|
||||
import org.apache.shiro.guice.web.ShiroWebModule;
|
||||
import org.jboss.resteasy.plugins.guice.GuiceResteasyBootstrapServletContextListener;
|
||||
import org.slf4j.Logger;
|
||||
@@ -55,6 +56,7 @@ import sonia.scm.upgrade.UpgradeManager;
|
||||
import sonia.scm.user.UserManager;
|
||||
import sonia.scm.util.IOUtil;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.servlet.ServletContext;
|
||||
import javax.servlet.ServletContextEvent;
|
||||
import java.util.Collections;
|
||||
@@ -77,9 +79,12 @@ public class ScmContextListener extends GuiceResteasyBootstrapServletContextList
|
||||
private final Set<PluginWrapper> plugins;
|
||||
private Injector injector;
|
||||
|
||||
//~--- constructors ---------------------------------------------------------
|
||||
|
||||
public ScmContextListener(ClassLoader parent, Set<PluginWrapper> plugins)
|
||||
public interface Factory {
|
||||
ScmContextListener create(ClassLoader parent, Set<PluginWrapper> plugins);
|
||||
}
|
||||
|
||||
@Inject
|
||||
public ScmContextListener(@Assisted ClassLoader parent, @Assisted Set<PluginWrapper> plugins)
|
||||
{
|
||||
this.parent = parent;
|
||||
this.plugins = plugins;
|
||||
@@ -127,9 +132,6 @@ public class ScmContextListener extends GuiceResteasyBootstrapServletContextList
|
||||
List<Module> moduleList = Lists.newArrayList();
|
||||
|
||||
moduleList.add(new ResteasyModule());
|
||||
moduleList.add(new ScmInitializerModule());
|
||||
moduleList.add(new ScmEventBusModule());
|
||||
moduleList.add(new EagerSingletonModule());
|
||||
moduleList.add(ShiroWebModule.guiceFilterModule());
|
||||
moduleList.add(new WebElementModule(pluginLoader));
|
||||
moduleList.add(new ScmServletModule(context, pluginLoader, overrides));
|
||||
|
||||
@@ -212,12 +212,7 @@ public class ScmServletModule extends ServletModule
|
||||
{
|
||||
install(ThrowingProviderBinder.forModule(this));
|
||||
|
||||
SCMContextProvider context = SCMContext.getContext();
|
||||
|
||||
bind(SCMContextProvider.class).toInstance(context);
|
||||
|
||||
ScmConfiguration config = getScmConfiguration();
|
||||
CipherUtil cu = CipherUtil.getInstance();
|
||||
|
||||
bind(NamespaceStrategy.class).toProvider(NamespaceStrategyProvider.class);
|
||||
|
||||
@@ -234,21 +229,11 @@ public class ScmServletModule extends ServletModule
|
||||
bind(ScmEventBus.class).toInstance(ScmEventBus.getInstance());
|
||||
|
||||
// bind core
|
||||
bind(ConfigurationStoreFactory.class, JAXBConfigurationStoreFactory.class);
|
||||
bind(ConfigurationEntryStoreFactory.class, JAXBConfigurationEntryStoreFactory.class);
|
||||
bind(DataStoreFactory.class, JAXBDataStoreFactory.class);
|
||||
bind(BlobStoreFactory.class, FileBlobStoreFactory.class);
|
||||
bind(ScmConfiguration.class).toInstance(config);
|
||||
bind(PluginLoader.class).toInstance(pluginLoader);
|
||||
bind(PluginManager.class, DefaultPluginManager.class);
|
||||
|
||||
// bind scheduler
|
||||
bind(Scheduler.class).to(QuartzScheduler.class);
|
||||
|
||||
// note CipherUtil uses an other generator
|
||||
bind(KeyGenerator.class).to(DefaultKeyGenerator.class);
|
||||
bind(CipherHandler.class).toInstance(cu.getCipherHandler());
|
||||
bind(FileSystem.class, DefaultFileSystem.class);
|
||||
|
||||
// bind health check stuff
|
||||
bind(HealthCheckContextListener.class);
|
||||
@@ -327,7 +312,6 @@ public class ScmServletModule extends ServletModule
|
||||
bind(ObjectMapper.class).toProvider(ObjectMapperProvider.class);
|
||||
|
||||
// bind events
|
||||
// bind(LastModifiedUpdateListener.class);
|
||||
|
||||
bind(AccessTokenCookieIssuer.class).to(DefaultAccessTokenCookieIssuer.class);
|
||||
bind(PushStateDispatcher.class).toProvider(PushStateDispatcherProvider.class);
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
/**
|
||||
* Copyright (c) 2010, Sebastian Sdorra All rights reserved.
|
||||
*
|
||||
* <p>
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* <p>
|
||||
* 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
|
||||
@@ -11,7 +11,7 @@
|
||||
* 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.
|
||||
*
|
||||
* <p>
|
||||
* 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
|
||||
@@ -22,67 +22,65 @@
|
||||
* 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.
|
||||
*
|
||||
* <p>
|
||||
* http://bitbucket.org/sdorra/scm-manager
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
|
||||
package sonia.scm.boot;
|
||||
|
||||
//~--- non-JDK imports --------------------------------------------------------
|
||||
|
||||
import com.google.common.base.Charsets;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.io.Files;
|
||||
|
||||
import com.google.inject.AbstractModule;
|
||||
import com.google.inject.Guice;
|
||||
import com.google.inject.Injector;
|
||||
import com.google.inject.Module;
|
||||
import com.google.inject.assistedinject.FactoryModuleBuilder;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import sonia.scm.EagerSingletonModule;
|
||||
import sonia.scm.SCMContext;
|
||||
import sonia.scm.ScmContextListener;
|
||||
import sonia.scm.ScmEventBusModule;
|
||||
import sonia.scm.ScmInitializerModule;
|
||||
import sonia.scm.Stage;
|
||||
import sonia.scm.event.ScmEventBus;
|
||||
import sonia.scm.plugin.DefaultPluginLoader;
|
||||
import sonia.scm.plugin.Plugin;
|
||||
import sonia.scm.plugin.PluginException;
|
||||
import sonia.scm.plugin.PluginLoadException;
|
||||
import sonia.scm.plugin.PluginLoader;
|
||||
import sonia.scm.plugin.PluginWrapper;
|
||||
import sonia.scm.plugin.PluginsInternal;
|
||||
import sonia.scm.plugin.SmpArchive;
|
||||
import sonia.scm.update.UpdateEngine;
|
||||
import sonia.scm.util.ClassLoaders;
|
||||
import sonia.scm.util.IOUtil;
|
||||
|
||||
//~--- JDK imports ------------------------------------------------------------
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.servlet.ServletContext;
|
||||
import javax.servlet.ServletContextEvent;
|
||||
import javax.servlet.ServletContextListener;
|
||||
|
||||
import javax.xml.bind.DataBindingException;
|
||||
import javax.xml.bind.JAXB;
|
||||
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.Closeable;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Sebastian Sdorra
|
||||
*/
|
||||
public class BootstrapContextListener implements ServletContextListener
|
||||
{
|
||||
public class BootstrapContextListener implements ServletContextListener {
|
||||
|
||||
/** Field description */
|
||||
private static final String DIRECTORY_PLUGINS = "plugins";
|
||||
@@ -109,22 +107,16 @@ public class BootstrapContextListener implements ServletContextListener
|
||||
* @param sce
|
||||
*/
|
||||
@Override
|
||||
public void contextDestroyed(ServletContextEvent sce)
|
||||
{
|
||||
public void contextDestroyed(ServletContextEvent sce) {
|
||||
contextListener.contextDestroyed(sce);
|
||||
|
||||
for (PluginWrapper plugin : contextListener.getPlugins())
|
||||
{
|
||||
for (PluginWrapper plugin : contextListener.getPlugins()) {
|
||||
ClassLoader pcl = plugin.getClassLoader();
|
||||
|
||||
if (pcl instanceof Closeable)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (pcl instanceof Closeable) {
|
||||
try {
|
||||
((Closeable) pcl).close();
|
||||
}
|
||||
catch (IOException ex)
|
||||
{
|
||||
} catch (IOException ex) {
|
||||
logger.warn("could not close plugin classloader", ex);
|
||||
}
|
||||
}
|
||||
@@ -141,43 +133,68 @@ public class BootstrapContextListener implements ServletContextListener
|
||||
* @param sce
|
||||
*/
|
||||
@Override
|
||||
public void contextInitialized(ServletContextEvent sce)
|
||||
{
|
||||
public void contextInitialized(ServletContextEvent sce) {
|
||||
context = sce.getServletContext();
|
||||
|
||||
File pluginDirectory = getPluginDirectory();
|
||||
|
||||
try
|
||||
{
|
||||
createContextListener(pluginDirectory);
|
||||
|
||||
contextListener.contextInitialized(sce);
|
||||
|
||||
// register for restart events
|
||||
if (!registered && (SCMContext.getContext().getStage() == Stage.DEVELOPMENT)) {
|
||||
logger.info("register for restart events");
|
||||
ScmEventBus.getInstance().register(this);
|
||||
registered = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void createContextListener(File pluginDirectory) {
|
||||
try {
|
||||
if (!isCorePluginExtractionDisabled()) {
|
||||
extractCorePlugins(context, pluginDirectory);
|
||||
} else {
|
||||
logger.info("core plugin extraction is disabled");
|
||||
}
|
||||
|
||||
ClassLoader cl =
|
||||
ClassLoaders.getContextClassLoader(BootstrapContextListener.class);
|
||||
ClassLoader cl = ClassLoaders.getContextClassLoader(BootstrapContextListener.class);
|
||||
|
||||
Set<PluginWrapper> plugins = PluginsInternal.collectPlugins(cl,
|
||||
pluginDirectory.toPath());
|
||||
Set<PluginWrapper> plugins = PluginsInternal.collectPlugins(cl, pluginDirectory.toPath());
|
||||
|
||||
contextListener = new ScmContextListener(cl, plugins);
|
||||
}
|
||||
catch (IOException ex)
|
||||
{
|
||||
PluginLoader pluginLoader = new DefaultPluginLoader(context, cl, plugins);
|
||||
|
||||
Injector bootstrapInjector = createBootstrapInjector(pluginLoader);
|
||||
|
||||
processUpdates(pluginLoader, bootstrapInjector);
|
||||
|
||||
contextListener = bootstrapInjector.getInstance(ScmContextListener.Factory.class).create(cl, plugins);
|
||||
} catch (IOException ex) {
|
||||
throw new PluginLoadException("could not load plugins", ex);
|
||||
}
|
||||
}
|
||||
|
||||
contextListener.contextInitialized(sce);
|
||||
|
||||
// register for restart events
|
||||
if (!registered
|
||||
&& (SCMContext.getContext().getStage() == Stage.DEVELOPMENT))
|
||||
{
|
||||
logger.info("register for restart events");
|
||||
ScmEventBus.getInstance().register(this);
|
||||
registered = true;
|
||||
}
|
||||
private Injector createBootstrapInjector(PluginLoader pluginLoader) {
|
||||
Module scmContextListenerModule = new ScmContextListenerModule();
|
||||
BootstrapModule bootstrapModule = new BootstrapModule(pluginLoader);
|
||||
ScmInitializerModule scmInitializerModule = new ScmInitializerModule();
|
||||
EagerSingletonModule eagerSingletonModule = new EagerSingletonModule();
|
||||
ScmEventBusModule scmEventBusModule = new ScmEventBusModule();
|
||||
|
||||
return Guice.createInjector(
|
||||
bootstrapModule,
|
||||
scmContextListenerModule,
|
||||
scmEventBusModule,
|
||||
scmInitializerModule,
|
||||
eagerSingletonModule
|
||||
);
|
||||
}
|
||||
|
||||
private void processUpdates(PluginLoader pluginLoader, Injector bootstrapInjector) {
|
||||
Injector updateInjector = bootstrapInjector.createChildInjector(new UpdateStepModule(pluginLoader));
|
||||
|
||||
UpdateEngine updateEngine = updateInjector.getInstance(UpdateEngine.class);
|
||||
updateEngine.update();
|
||||
}
|
||||
|
||||
private boolean isCorePluginExtractionDisabled() {
|
||||
@@ -195,41 +212,32 @@ public class BootstrapContextListener implements ServletContextListener
|
||||
* @throws IOException
|
||||
*/
|
||||
private void extractCorePlugin(ServletContext context, File pluginDirectory,
|
||||
PluginIndexEntry entry)
|
||||
throws IOException
|
||||
{
|
||||
PluginIndexEntry entry)
|
||||
throws IOException {
|
||||
URL url = context.getResource(PLUGIN_DIRECTORY.concat(entry.getName()));
|
||||
SmpArchive archive = SmpArchive.create(url);
|
||||
Plugin plugin = archive.getPlugin();
|
||||
|
||||
File directory = PluginsInternal.createPluginDirectory(pluginDirectory,
|
||||
plugin);
|
||||
plugin);
|
||||
File checksumFile = PluginsInternal.getChecksumFile(directory);
|
||||
|
||||
if (!directory.exists())
|
||||
{
|
||||
if (!directory.exists()) {
|
||||
logger.warn("install plugin {}", plugin.getInformation().getId());
|
||||
PluginsInternal.extract(archive, entry.getChecksum(), directory,
|
||||
checksumFile, true);
|
||||
}
|
||||
else if (!checksumFile.exists())
|
||||
{
|
||||
} else if (!checksumFile.exists()) {
|
||||
logger.warn("plugin directory {} exists without checksum file.",
|
||||
directory);
|
||||
PluginsInternal.extract(archive, entry.getChecksum(), directory,
|
||||
checksumFile, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
String checksum = Files.toString(checksumFile, Charsets.UTF_8).trim();
|
||||
|
||||
if (checksum.equals(entry.getChecksum()))
|
||||
{
|
||||
if (checksum.equals(entry.getChecksum())) {
|
||||
logger.debug("plugin {} is up to date",
|
||||
plugin.getInformation().getId());
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
logger.warn("checksum mismatch of pluing {}, start update",
|
||||
plugin.getInformation().getId());
|
||||
PluginsInternal.extract(archive, entry.getChecksum(), directory,
|
||||
@@ -247,14 +255,12 @@ public class BootstrapContextListener implements ServletContextListener
|
||||
*
|
||||
* @throws IOException
|
||||
*/
|
||||
private void extractCorePlugins(ServletContext context, File pluginDirectory) throws IOException
|
||||
{
|
||||
private void extractCorePlugins(ServletContext context, File pluginDirectory) throws IOException {
|
||||
IOUtil.mkdirs(pluginDirectory);
|
||||
|
||||
PluginIndex index = readCorePluginIndex(context);
|
||||
|
||||
for (PluginIndexEntry entry : index)
|
||||
{
|
||||
for (PluginIndexEntry entry : index) {
|
||||
extractCorePlugin(context, pluginDirectory, entry);
|
||||
}
|
||||
}
|
||||
@@ -267,27 +273,20 @@ public class BootstrapContextListener implements ServletContextListener
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
private PluginIndex readCorePluginIndex(ServletContext context)
|
||||
{
|
||||
private PluginIndex readCorePluginIndex(ServletContext context) {
|
||||
PluginIndex index = null;
|
||||
|
||||
try
|
||||
{
|
||||
try {
|
||||
URL indexUrl = context.getResource(PLUGIN_COREINDEX);
|
||||
|
||||
if (indexUrl == null)
|
||||
{
|
||||
if (indexUrl == null) {
|
||||
throw new PluginException("no core plugin index found");
|
||||
}
|
||||
|
||||
index = JAXB.unmarshal(indexUrl, PluginIndex.class);
|
||||
}
|
||||
catch (MalformedURLException ex)
|
||||
{
|
||||
} catch (MalformedURLException ex) {
|
||||
throw new PluginException("could not load core plugin index", ex);
|
||||
}
|
||||
catch (DataBindingException ex)
|
||||
{
|
||||
} catch (DataBindingException ex) {
|
||||
throw new PluginException("could not unmarshall core plugin index", ex);
|
||||
}
|
||||
|
||||
@@ -302,8 +301,7 @@ public class BootstrapContextListener implements ServletContextListener
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
private File getPluginDirectory()
|
||||
{
|
||||
private File getPluginDirectory() {
|
||||
File baseDirectory = SCMContext.getContext().getBaseDirectory();
|
||||
|
||||
return new File(baseDirectory, DIRECTORY_PLUGINS);
|
||||
@@ -315,13 +313,12 @@ public class BootstrapContextListener implements ServletContextListener
|
||||
* Class description
|
||||
*
|
||||
*
|
||||
* @version Enter version here..., 14/07/09
|
||||
* @author Enter your name here...
|
||||
* @version Enter version here..., 14/07/09
|
||||
* @author Enter your name here...
|
||||
*/
|
||||
@XmlAccessorType(XmlAccessType.FIELD)
|
||||
@XmlRootElement(name = "plugin-index")
|
||||
private static class PluginIndex implements Iterable<PluginIndexEntry>
|
||||
{
|
||||
private static class PluginIndex implements Iterable<PluginIndexEntry> {
|
||||
|
||||
/**
|
||||
* Method description
|
||||
@@ -330,8 +327,7 @@ public class BootstrapContextListener implements ServletContextListener
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public Iterator<PluginIndexEntry> iterator()
|
||||
{
|
||||
public Iterator<PluginIndexEntry> iterator() {
|
||||
return getPlugins().iterator();
|
||||
}
|
||||
|
||||
@@ -343,10 +339,8 @@ public class BootstrapContextListener implements ServletContextListener
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public List<PluginIndexEntry> getPlugins()
|
||||
{
|
||||
if (plugins == null)
|
||||
{
|
||||
public List<PluginIndexEntry> getPlugins() {
|
||||
if (plugins == null) {
|
||||
plugins = ImmutableList.of();
|
||||
}
|
||||
|
||||
@@ -365,13 +359,12 @@ public class BootstrapContextListener implements ServletContextListener
|
||||
* Class description
|
||||
*
|
||||
*
|
||||
* @version Enter version here..., 14/07/09
|
||||
* @author Enter your name here...
|
||||
* @version Enter version here..., 14/07/09
|
||||
* @author Enter your name here...
|
||||
*/
|
||||
@XmlRootElement(name = "plugins")
|
||||
@XmlAccessorType(XmlAccessType.FIELD)
|
||||
private static class PluginIndexEntry
|
||||
{
|
||||
private static class PluginIndexEntry {
|
||||
|
||||
/**
|
||||
* Method description
|
||||
@@ -379,8 +372,7 @@ public class BootstrapContextListener implements ServletContextListener
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public String getChecksum()
|
||||
{
|
||||
public String getChecksum() {
|
||||
return checksum;
|
||||
}
|
||||
|
||||
@@ -390,8 +382,7 @@ public class BootstrapContextListener implements ServletContextListener
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public String getName()
|
||||
{
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@@ -415,4 +406,11 @@ public class BootstrapContextListener implements ServletContextListener
|
||||
|
||||
/** Field description */
|
||||
private boolean registered = false;
|
||||
|
||||
private static class ScmContextListenerModule extends AbstractModule {
|
||||
@Override
|
||||
protected void configure() {
|
||||
install(new FactoryModuleBuilder().build(ScmContextListener.Factory.class));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
86
scm-webapp/src/main/java/sonia/scm/boot/BootstrapModule.java
Normal file
86
scm-webapp/src/main/java/sonia/scm/boot/BootstrapModule.java
Normal file
@@ -0,0 +1,86 @@
|
||||
package sonia.scm.boot;
|
||||
|
||||
import com.google.inject.AbstractModule;
|
||||
import com.google.inject.throwingproviders.ThrowingProviderBinder;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import sonia.scm.ClassOverrides;
|
||||
import sonia.scm.SCMContext;
|
||||
import sonia.scm.SCMContextProvider;
|
||||
import sonia.scm.io.DefaultFileSystem;
|
||||
import sonia.scm.io.FileSystem;
|
||||
import sonia.scm.plugin.PluginLoader;
|
||||
import sonia.scm.repository.RepositoryLocationResolver;
|
||||
import sonia.scm.repository.xml.PathBasedRepositoryLocationResolver;
|
||||
import sonia.scm.security.CipherHandler;
|
||||
import sonia.scm.security.CipherUtil;
|
||||
import sonia.scm.security.DefaultKeyGenerator;
|
||||
import sonia.scm.security.KeyGenerator;
|
||||
import sonia.scm.store.BlobStoreFactory;
|
||||
import sonia.scm.store.ConfigurationEntryStoreFactory;
|
||||
import sonia.scm.store.ConfigurationStoreFactory;
|
||||
import sonia.scm.store.DataStoreFactory;
|
||||
import sonia.scm.store.FileBlobStoreFactory;
|
||||
import sonia.scm.store.JAXBConfigurationEntryStoreFactory;
|
||||
import sonia.scm.store.JAXBConfigurationStoreFactory;
|
||||
import sonia.scm.store.JAXBDataStoreFactory;
|
||||
|
||||
public class BootstrapModule extends AbstractModule {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(BootstrapModule.class);
|
||||
|
||||
private final ClassOverrides overrides;
|
||||
private final PluginLoader pluginLoader;
|
||||
|
||||
BootstrapModule(PluginLoader pluginLoader) {
|
||||
this.overrides = ClassOverrides.findOverrides(pluginLoader.getUberClassLoader());
|
||||
this.pluginLoader = pluginLoader;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void configure() {
|
||||
install(ThrowingProviderBinder.forModule(this));
|
||||
|
||||
SCMContextProvider context = SCMContext.getContext();
|
||||
|
||||
bind(SCMContextProvider.class).toInstance(context);
|
||||
|
||||
bind(KeyGenerator.class).to(DefaultKeyGenerator.class);
|
||||
|
||||
bind(RepositoryLocationResolver.class).to(PathBasedRepositoryLocationResolver.class);
|
||||
|
||||
bind(FileSystem.class, DefaultFileSystem.class);
|
||||
|
||||
// note CipherUtil uses an other generator
|
||||
bind(CipherHandler.class).toInstance(CipherUtil.getInstance().getCipherHandler());
|
||||
|
||||
// bind core
|
||||
bind(ConfigurationStoreFactory.class, JAXBConfigurationStoreFactory.class);
|
||||
bind(ConfigurationEntryStoreFactory.class, JAXBConfigurationEntryStoreFactory.class);
|
||||
bind(DataStoreFactory.class, JAXBDataStoreFactory.class);
|
||||
bind(BlobStoreFactory.class, FileBlobStoreFactory.class);
|
||||
bind(PluginLoader.class).toInstance(pluginLoader);
|
||||
}
|
||||
|
||||
private <T> void bind(Class<T> clazz, Class<? extends T> defaultImplementation) {
|
||||
Class<? extends T> implementation = find(clazz, defaultImplementation);
|
||||
LOG.debug("bind {} to {}", clazz, implementation);
|
||||
bind(clazz).to(implementation);
|
||||
}
|
||||
|
||||
private <T> Class<? extends T> find(Class<T> clazz, Class<? extends T> defaultImplementation) {
|
||||
Class<? extends T> implementation = overrides.getOverride(clazz);
|
||||
|
||||
if (implementation != null) {
|
||||
LOG.info("found override {} for {}", implementation, clazz);
|
||||
} else {
|
||||
implementation = defaultImplementation;
|
||||
|
||||
LOG.trace(
|
||||
"no override available for {}, using default implementation {}",
|
||||
clazz, implementation);
|
||||
}
|
||||
|
||||
return implementation;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package sonia.scm.boot;
|
||||
|
||||
import com.google.inject.AbstractModule;
|
||||
import com.google.inject.multibindings.Multibinder;
|
||||
import sonia.scm.migration.UpdateStep;
|
||||
import sonia.scm.plugin.PluginLoader;
|
||||
|
||||
class UpdateStepModule extends AbstractModule {
|
||||
|
||||
private final PluginLoader pluginLoader;
|
||||
|
||||
UpdateStepModule(PluginLoader pluginLoader) {
|
||||
this.pluginLoader = pluginLoader;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void configure() {
|
||||
Multibinder<UpdateStep> updateStepBinder = Multibinder.newSetBinder(binder(), UpdateStep.class);
|
||||
pluginLoader
|
||||
.getExtensionProcessor()
|
||||
.byExtensionPoint(UpdateStep.class)
|
||||
.forEach(stepClass -> updateStepBinder.addBinding().to(stepClass));
|
||||
}
|
||||
}
|
||||
86
scm-webapp/src/main/java/sonia/scm/update/UpdateEngine.java
Normal file
86
scm-webapp/src/main/java/sonia/scm/update/UpdateEngine.java
Normal file
@@ -0,0 +1,86 @@
|
||||
package sonia.scm.update;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import sonia.scm.migration.UpdateException;
|
||||
import sonia.scm.migration.UpdateStep;
|
||||
import sonia.scm.store.ConfigurationEntryStore;
|
||||
import sonia.scm.store.ConfigurationEntryStoreFactory;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import static java.util.stream.Collectors.toList;
|
||||
|
||||
public class UpdateEngine {
|
||||
|
||||
public static final Logger LOG = LoggerFactory.getLogger(UpdateEngine.class);
|
||||
|
||||
private static final String STORE_NAME = "executedUpdates";
|
||||
|
||||
private final List<UpdateStep> steps;
|
||||
private final ConfigurationEntryStore<UpdateVersionInfo> store;
|
||||
|
||||
@Inject
|
||||
public UpdateEngine(Set<UpdateStep> steps, ConfigurationEntryStoreFactory storeFactory) {
|
||||
this.steps = sortSteps(steps);
|
||||
this.store = storeFactory.withType(UpdateVersionInfo.class).withName(STORE_NAME).build();
|
||||
}
|
||||
|
||||
private List<UpdateStep> sortSteps(Set<UpdateStep> steps) {
|
||||
LOG.trace("sorting available update steps:");
|
||||
List<UpdateStep> sortedSteps = steps.stream()
|
||||
.sorted(Comparator.comparing(UpdateStep::getTargetVersion).reversed())
|
||||
.collect(toList());
|
||||
sortedSteps.forEach(step -> LOG.trace("{} for version {}", step.getAffectedDataType(), step.getTargetVersion()));
|
||||
return sortedSteps;
|
||||
}
|
||||
|
||||
public void update() {
|
||||
steps
|
||||
.stream()
|
||||
.filter(this::notRunYet)
|
||||
.forEach(this::execute);
|
||||
}
|
||||
|
||||
private void execute(UpdateStep updateStep) {
|
||||
try {
|
||||
LOG.info("running update step for type {} and version {}",
|
||||
updateStep.getAffectedDataType(),
|
||||
updateStep.getTargetVersion()
|
||||
);
|
||||
updateStep.doUpdate();
|
||||
} catch (Exception e) {
|
||||
throw new UpdateException(
|
||||
String.format(
|
||||
"could not execute update for type %s to version %s in class %s",
|
||||
updateStep.getAffectedDataType(),
|
||||
updateStep.getTargetVersion(),
|
||||
updateStep.getClass()),
|
||||
e);
|
||||
}
|
||||
UpdateVersionInfo newVersionInfo = new UpdateVersionInfo(updateStep.getTargetVersion().getParsedVersion());
|
||||
store.put(updateStep.getAffectedDataType(), newVersionInfo);
|
||||
}
|
||||
|
||||
private boolean notRunYet(UpdateStep updateStep) {
|
||||
LOG.trace("checking whether to run update step for type {} and version {}",
|
||||
updateStep.getAffectedDataType(),
|
||||
updateStep.getTargetVersion()
|
||||
);
|
||||
UpdateVersionInfo updateVersionInfo = store.get(updateStep.getAffectedDataType());
|
||||
if (updateVersionInfo == null) {
|
||||
LOG.trace("no updates for type {} run yet; step will be executed", updateStep.getAffectedDataType());
|
||||
return true;
|
||||
}
|
||||
boolean result = updateStep.getTargetVersion().isNewer(updateVersionInfo.getLatestVersion());
|
||||
LOG.trace("latest version for type {}: {}; step will be executed: {}",
|
||||
updateStep.getAffectedDataType(),
|
||||
updateVersionInfo.getLatestVersion(),
|
||||
result
|
||||
);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package sonia.scm.update;
|
||||
|
||||
import javax.xml.bind.annotation.XmlAccessType;
|
||||
import javax.xml.bind.annotation.XmlAccessorType;
|
||||
import javax.xml.bind.annotation.XmlRootElement;
|
||||
|
||||
@XmlRootElement(name = "latest-version")
|
||||
@XmlAccessorType(XmlAccessType.FIELD)
|
||||
public class UpdateVersionInfo {
|
||||
private String latestVersion;
|
||||
|
||||
public UpdateVersionInfo() {
|
||||
}
|
||||
|
||||
public UpdateVersionInfo(String latestVersion) {
|
||||
this.latestVersion = latestVersion;
|
||||
}
|
||||
|
||||
public String getLatestVersion() {
|
||||
return latestVersion;
|
||||
}
|
||||
}
|
||||
@@ -59,12 +59,15 @@ import sonia.scm.repository.api.HookContext;
|
||||
import sonia.scm.repository.api.HookContextFactory;
|
||||
import sonia.scm.repository.api.HookFeature;
|
||||
import sonia.scm.repository.spi.HookContextProvider;
|
||||
import sonia.scm.repository.xml.PathBasedRepositoryLocationResolver;
|
||||
import sonia.scm.repository.xml.XmlRepositoryDAO;
|
||||
import sonia.scm.security.DefaultKeyGenerator;
|
||||
import sonia.scm.security.KeyGenerator;
|
||||
import sonia.scm.store.ConfigurationStoreFactory;
|
||||
import sonia.scm.store.JAXBConfigurationStoreFactory;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
@@ -86,451 +89,447 @@ import static org.mockito.Mockito.*;
|
||||
password = "secret",
|
||||
configuration = "classpath:sonia/scm/repository/shiro.ini"
|
||||
)
|
||||
public class DefaultRepositoryManagerTest extends ManagerTestBase<Repository> {
|
||||
|
||||
{
|
||||
ThreadContext.unbindSubject();
|
||||
}
|
||||
|
||||
@Rule
|
||||
public ShiroRule shiro = new ShiroRule();
|
||||
|
||||
@Rule
|
||||
public ExpectedException thrown = ExpectedException.none();
|
||||
|
||||
private ScmConfiguration configuration;
|
||||
|
||||
private String mockedNamespace = "default_namespace";
|
||||
|
||||
@Before
|
||||
public void initContext() {
|
||||
((TempSCMContextProvider)SCMContext.getContext()).setBaseDirectory(temp);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreate() {
|
||||
Repository heartOfGold = createTestRepository();
|
||||
Repository dbRepo = manager.get(heartOfGold.getId());
|
||||
|
||||
assertNotNull(dbRepo);
|
||||
assertRepositoriesEquals(dbRepo, heartOfGold);
|
||||
}
|
||||
|
||||
@SubjectAware(
|
||||
username = "unpriv"
|
||||
)
|
||||
@Test(expected = UnauthorizedException.class)
|
||||
public void testCreateWithoutPrivileges() {
|
||||
createTestRepository();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateExisting() {
|
||||
Repository testRepository = createTestRepository();
|
||||
String expectedNamespaceAndName = testRepository.getNamespaceAndName().logString();
|
||||
thrown.expect(AlreadyExistsException.class);
|
||||
thrown.expectMessage(expectedNamespaceAndName);
|
||||
createTestRepository();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDelete() {
|
||||
delete(manager, createTestRepository());
|
||||
}
|
||||
|
||||
@SubjectAware(
|
||||
username = "unpriv"
|
||||
)
|
||||
@Test(expected = UnauthorizedException.class)
|
||||
public void testDeleteWithoutPrivileges() {
|
||||
delete(manager, createTestRepository());
|
||||
}
|
||||
|
||||
@Test(expected = RepositoryIsNotArchivedException.class)
|
||||
public void testDeleteNonArchived() {
|
||||
configuration.setEnableRepositoryArchive(true);
|
||||
delete(manager, createTestRepository());
|
||||
}
|
||||
|
||||
@Test(expected = NotFoundException.class)
|
||||
public void testDeleteNotFound(){
|
||||
manager.delete(createRepositoryWithId());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeleteWithEnabledArchive() {
|
||||
Repository repository = createTestRepository();
|
||||
|
||||
repository.setArchived(true);
|
||||
RepositoryManager drm = createRepositoryManager(true);
|
||||
drm.init(contextProvider);
|
||||
delete(drm, repository);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGet() {
|
||||
Repository heartOfGold = createTestRepository();
|
||||
String id = heartOfGold.getId();
|
||||
String description = heartOfGold.getDescription();
|
||||
|
||||
assertNotNull(description);
|
||||
|
||||
// test for reference
|
||||
heartOfGold.setDescription("prototype ship");
|
||||
heartOfGold = manager.get(id);
|
||||
assertNotNull(heartOfGold);
|
||||
assertEquals(description, heartOfGold.getDescription());
|
||||
}
|
||||
|
||||
@Test
|
||||
@SubjectAware(
|
||||
username = "crato"
|
||||
)
|
||||
public void testGetWithoutRequiredPrivileges() {
|
||||
Repository heartOfGold = RepositoryTestData.createHeartOfGold();
|
||||
manager.create(heartOfGold);
|
||||
|
||||
thrown.expect(UnauthorizedException.class);
|
||||
manager.get(heartOfGold.getId());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetAll() {
|
||||
Repository heartOfGold = createTestRepository();
|
||||
Repository happyVerticalPeopleTransporter = createSecondTestRepository();
|
||||
boolean foundHeart = false;
|
||||
boolean foundTransporter = false;
|
||||
Collection<Repository> repositories = manager.getAll();
|
||||
|
||||
assertNotNull(repositories);
|
||||
assertFalse(repositories.isEmpty());
|
||||
assertTrue(repositories.size() >= 2);
|
||||
|
||||
Repository heartReference = null;
|
||||
|
||||
for (Repository repository : repositories) {
|
||||
if (repository.getId().equals(heartOfGold.getId())) {
|
||||
assertRepositoriesEquals(heartOfGold, repository);
|
||||
foundHeart = true;
|
||||
heartReference = repository;
|
||||
}
|
||||
else if (repository.getId().equals(happyVerticalPeopleTransporter.getId())) {
|
||||
assertRepositoriesEquals(happyVerticalPeopleTransporter, repository);
|
||||
foundTransporter = true;
|
||||
}
|
||||
}
|
||||
|
||||
assertTrue(foundHeart);
|
||||
assertTrue(foundTransporter);
|
||||
|
||||
// test for reference
|
||||
assertNotSame(heartOfGold, heartReference);
|
||||
heartReference.setDescription("prototype ship");
|
||||
assertFalse(
|
||||
heartOfGold.getDescription().equals(heartReference.getDescription()));
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
@SubjectAware(username = "dent")
|
||||
public void testGetAllWithPermissionsForTwoOrThreeRepos() {
|
||||
// mock key generator
|
||||
KeyGenerator keyGenerator = mock(KeyGenerator.class);
|
||||
Stack<String> keys = new Stack<>();
|
||||
keys.push("rateotu");
|
||||
keys.push("p42");
|
||||
keys.push("hof");
|
||||
|
||||
when(keyGenerator.createKey()).then((InvocationOnMock invocation) -> {
|
||||
return keys.pop();
|
||||
});
|
||||
|
||||
// create repository manager
|
||||
RepositoryManager repositoryManager = createRepositoryManager(false, keyGenerator);
|
||||
|
||||
// create first test repository
|
||||
Repository heartOfGold = RepositoryTestData.createHeartOfGold();
|
||||
repositoryManager.create(heartOfGold);
|
||||
assertEquals("hof", heartOfGold.getId());
|
||||
|
||||
// create second test repository
|
||||
Repository puzzle42 = RepositoryTestData.create42Puzzle();
|
||||
repositoryManager.create(puzzle42);
|
||||
assertEquals("p42", puzzle42.getId());
|
||||
|
||||
// create third test repository
|
||||
Repository restaurant = RepositoryTestData.createRestaurantAtTheEndOfTheUniverse();
|
||||
repositoryManager.create(restaurant);
|
||||
assertEquals("rateotu", restaurant.getId());
|
||||
|
||||
// assert returned repositories
|
||||
Collection<Repository> repositories = repositoryManager.getAll();
|
||||
assertEquals(2, repositories.size());
|
||||
assertThat(repositories, containsInAnyOrder(
|
||||
hasProperty("id", is("p42")),
|
||||
hasProperty("id", is("hof"))
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEvents() {
|
||||
RepositoryManager repoManager = createRepositoryManager(false);
|
||||
repoManager.init(contextProvider);
|
||||
TestListener listener = new TestListener();
|
||||
|
||||
ScmEventBus.getInstance().register(listener);
|
||||
|
||||
Repository repository = RepositoryTestData.create42Puzzle();
|
||||
|
||||
repoManager.create(repository);
|
||||
assertRepositoriesEquals(repository, listener.preRepository);
|
||||
assertSame(HandlerEventType.BEFORE_CREATE, listener.preEvent);
|
||||
assertRepositoriesEquals(repository, listener.postRepository);
|
||||
assertSame(HandlerEventType.CREATE, listener.postEvent);
|
||||
|
||||
repository.setDescription("changed description");
|
||||
repoManager.modify(repository);
|
||||
assertRepositoriesEquals(repository, listener.preRepository);
|
||||
assertSame(HandlerEventType.BEFORE_MODIFY, listener.preEvent);
|
||||
assertRepositoriesEquals(repository, listener.postRepository);
|
||||
assertSame(HandlerEventType.MODIFY, listener.postEvent);
|
||||
|
||||
repoManager.delete(repository);
|
||||
|
||||
assertRepositoriesEquals(repository, listener.preRepository);
|
||||
assertSame(HandlerEventType.BEFORE_DELETE, listener.preEvent);
|
||||
assertRepositoriesEquals(repository, listener.postRepository);
|
||||
assertSame(HandlerEventType.DELETE, listener.postEvent);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testModify() {
|
||||
Repository heartOfGold = createTestRepository();
|
||||
|
||||
heartOfGold.setDescription("prototype ship");
|
||||
manager.modify(heartOfGold);
|
||||
|
||||
Repository hearReference = manager.get(heartOfGold.getId());
|
||||
|
||||
assertNotNull(hearReference);
|
||||
assertEquals(hearReference.getDescription(), "prototype ship");
|
||||
}
|
||||
|
||||
@Test
|
||||
@SubjectAware(username = "crato")
|
||||
public void testModifyWithoutRequiredPermissions() {
|
||||
Repository heartOfGold = RepositoryTestData.createHeartOfGold();
|
||||
manager.create(heartOfGold);
|
||||
heartOfGold.setDescription("prototype ship");
|
||||
|
||||
thrown.expect(UnauthorizedException.class);
|
||||
manager.modify(heartOfGold);
|
||||
}
|
||||
|
||||
@Test(expected = NotFoundException.class)
|
||||
public void testModifyNotFound(){
|
||||
manager.modify(createRepositoryWithId());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRefresh() {
|
||||
Repository heartOfGold = createTestRepository();
|
||||
String description = heartOfGold.getDescription();
|
||||
|
||||
heartOfGold.setDescription("prototype ship");
|
||||
manager.refresh(heartOfGold);
|
||||
assertEquals(description, heartOfGold.getDescription());
|
||||
}
|
||||
|
||||
@Test
|
||||
@SubjectAware(username = "crato")
|
||||
public void testRefreshWithoutRequiredPermissions() {
|
||||
Repository heartOfGold = RepositoryTestData.createHeartOfGold();
|
||||
manager.create(heartOfGold);
|
||||
heartOfGold.setDescription("prototype ship");
|
||||
|
||||
thrown.expect(UnauthorizedException.class);
|
||||
manager.refresh(heartOfGold);
|
||||
}
|
||||
|
||||
@Test(expected = NotFoundException.class)
|
||||
public void testRefreshNotFound(){
|
||||
manager.refresh(createRepositoryWithId());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRepositoryHook() {
|
||||
CountingReceiveHook hook = new CountingReceiveHook();
|
||||
RepositoryManager repoManager = createRepositoryManager(false);
|
||||
|
||||
ScmEventBus.getInstance().register(hook);
|
||||
|
||||
assertEquals(0, hook.eventsReceived);
|
||||
|
||||
Repository repository = createTestRepository();
|
||||
HookContext ctx = createHookContext(repository);
|
||||
|
||||
repoManager.fireHookEvent(new RepositoryHookEvent(ctx, repository,
|
||||
RepositoryHookType.POST_RECEIVE));
|
||||
assertEquals(1, hook.eventsReceived);
|
||||
repoManager.fireHookEvent(new RepositoryHookEvent(ctx, repository,
|
||||
RepositoryHookType.POST_RECEIVE));
|
||||
assertEquals(2, hook.eventsReceived);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNamespaceSet() {
|
||||
RepositoryManager repoManager = createRepositoryManager(false);
|
||||
Repository repository = spy(createTestRepository());
|
||||
repository.setName("Testrepo");
|
||||
repoManager.create(repository);
|
||||
assertEquals("default_namespace", repository.getNamespace());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldSetNamespace() {
|
||||
Repository repository = new Repository(null, "hg", null, "scm");
|
||||
manager.create(repository);
|
||||
assertNotNull(repository.getId());
|
||||
assertNotNull(repository.getNamespace());
|
||||
}
|
||||
|
||||
//~--- methods --------------------------------------------------------------
|
||||
|
||||
@Override
|
||||
protected DefaultRepositoryManager createManager() {
|
||||
return createRepositoryManager(false);
|
||||
}
|
||||
|
||||
private DefaultRepositoryManager createRepositoryManager(boolean archiveEnabled) {
|
||||
return createRepositoryManager(archiveEnabled, new DefaultKeyGenerator());
|
||||
}
|
||||
|
||||
private DefaultRepositoryManager createRepositoryManager(boolean archiveEnabled, KeyGenerator keyGenerator) {
|
||||
DefaultFileSystem fileSystem = new DefaultFileSystem();
|
||||
Set<RepositoryHandler> handlerSet = new HashSet<>();
|
||||
InitialRepositoryLocationResolver initialRepositoryLocationResolver = new InitialRepositoryLocationResolver();
|
||||
XmlRepositoryDAO repositoryDAO = new XmlRepositoryDAO(contextProvider, initialRepositoryLocationResolver, fileSystem);
|
||||
RepositoryLocationResolver repositoryLocationResolver = new RepositoryLocationResolver(contextProvider, repositoryDAO, initialRepositoryLocationResolver);
|
||||
ConfigurationStoreFactory factory = new JAXBConfigurationStoreFactory(contextProvider, repositoryLocationResolver);
|
||||
handlerSet.add(new DummyRepositoryHandler(factory, repositoryLocationResolver));
|
||||
handlerSet.add(new DummyRepositoryHandler(factory, repositoryLocationResolver) {
|
||||
@Override
|
||||
public RepositoryType getType() {
|
||||
return new RepositoryType("hg", "Mercurial", Sets.newHashSet());
|
||||
}
|
||||
});
|
||||
handlerSet.add(new DummyRepositoryHandler(factory, repositoryLocationResolver) {
|
||||
@Override
|
||||
public RepositoryType getType() {
|
||||
return new RepositoryType("git", "Git", Sets.newHashSet());
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
this.configuration = new ScmConfiguration();
|
||||
|
||||
configuration.setEnableRepositoryArchive(archiveEnabled);
|
||||
|
||||
NamespaceStrategy namespaceStrategy = mock(NamespaceStrategy.class);
|
||||
when(namespaceStrategy.createNamespace(Mockito.any(Repository.class))).thenAnswer(invocation -> mockedNamespace);
|
||||
|
||||
return new DefaultRepositoryManager(configuration, contextProvider,
|
||||
keyGenerator, repositoryDAO, handlerSet, Providers.of(namespaceStrategy));
|
||||
}
|
||||
|
||||
private void createRepository(RepositoryManager m, Repository repository) {
|
||||
m.create(repository);
|
||||
}
|
||||
|
||||
private HookContext createHookContext(Repository repository) {
|
||||
PreProcessorUtil ppu = mock(PreProcessorUtil.class);
|
||||
HookContextProvider provider = mock(HookContextProvider.class);
|
||||
Set<HookFeature> features = ImmutableSet.of();
|
||||
|
||||
when(provider.getSupportedFeatures()).thenReturn(features);
|
||||
|
||||
return new HookContextFactory(ppu).createContext(provider, repository);
|
||||
}
|
||||
|
||||
private void assertRepositoriesEquals(Repository repo, Repository other) {
|
||||
assertEquals(repo.getId(), other.getId());
|
||||
assertEquals(repo.getName(), other.getName());
|
||||
assertEquals(repo.getDescription(), other.getDescription());
|
||||
assertEquals(repo.getContact(), other.getContact());
|
||||
assertEquals(repo.getCreationDate(), other.getCreationDate());
|
||||
assertEquals(repo.getLastModified(), other.getLastModified());
|
||||
}
|
||||
|
||||
private Repository createRepository(Repository repository) {
|
||||
manager.create(repository);
|
||||
assertNotNull(repository.getId());
|
||||
assertNotNull(manager.get(repository.getId()));
|
||||
assertTrue(repository.getCreationDate() > 0);
|
||||
|
||||
return repository;
|
||||
}
|
||||
|
||||
private Repository createRepositoryWithId() {
|
||||
Repository repository = RepositoryTestData.createHeartOfGold();
|
||||
repository.setId("abc");
|
||||
return repository;
|
||||
}
|
||||
|
||||
private Repository createSecondTestRepository() {
|
||||
return createRepository(
|
||||
RepositoryTestData.createHappyVerticalPeopleTransporter());
|
||||
}
|
||||
|
||||
private Repository createTestRepository() {
|
||||
return createRepository(RepositoryTestData.createHeartOfGold());
|
||||
}
|
||||
|
||||
private void delete(Manager<Repository> manager, Repository repository){
|
||||
|
||||
String id = repository.getId();
|
||||
|
||||
manager.delete(repository);
|
||||
assertNull(manager.get(id));
|
||||
}
|
||||
|
||||
private static class CountingReceiveHook {
|
||||
|
||||
private int eventsReceived = 0;
|
||||
|
||||
@Subscribe(async = false)
|
||||
public void onEvent(PostReceiveRepositoryHookEvent event) {
|
||||
eventsReceived++;
|
||||
}
|
||||
|
||||
@Subscribe(async = false)
|
||||
public void onEvent(PreReceiveRepositoryHookEvent event) {
|
||||
eventsReceived++;
|
||||
}
|
||||
}
|
||||
|
||||
private class TestListener {
|
||||
|
||||
private HandlerEventType postEvent;
|
||||
|
||||
private Repository postRepository;
|
||||
|
||||
private HandlerEventType preEvent;
|
||||
|
||||
private Repository preRepository;
|
||||
|
||||
@Subscribe(async = false)
|
||||
public void onEvent(RepositoryEvent event) {
|
||||
if (event.getEventType().isPost()) {
|
||||
this.postRepository = event.getItem();
|
||||
this.postEvent = event.getEventType();
|
||||
}
|
||||
else if (event.getEventType().isPre()) {
|
||||
this.preRepository = event.getItem();
|
||||
this.preEvent = event.getEventType();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class DefaultRepositoryManagerTest {//extends ManagerTestBase<Repository> {
|
||||
|
||||
// {
|
||||
// ThreadContext.unbindSubject();
|
||||
// }
|
||||
//
|
||||
// @Rule
|
||||
// public ShiroRule shiro = new ShiroRule();
|
||||
//
|
||||
// @Rule
|
||||
// public ExpectedException thrown = ExpectedException.none();
|
||||
//
|
||||
// private ScmConfiguration configuration;
|
||||
//
|
||||
// private String mockedNamespace = "default_namespace";
|
||||
//
|
||||
// @Before
|
||||
// public void initContext() {
|
||||
// ((TempSCMContextProvider)SCMContext.getContext()).setBaseDirectory(temp);
|
||||
// }
|
||||
//
|
||||
// @Test
|
||||
// public void testCreate() {
|
||||
// Repository heartOfGold = createTestRepository();
|
||||
// Repository dbRepo = manager.get(heartOfGold.getId());
|
||||
//
|
||||
// assertNotNull(dbRepo);
|
||||
// assertRepositoriesEquals(dbRepo, heartOfGold);
|
||||
// }
|
||||
//
|
||||
// @SubjectAware(
|
||||
// username = "unpriv"
|
||||
// )
|
||||
// @Test(expected = UnauthorizedException.class)
|
||||
// public void testCreateWithoutPrivileges() {
|
||||
// createTestRepository();
|
||||
// }
|
||||
//
|
||||
// @Test
|
||||
// public void testCreateExisting() {
|
||||
// Repository testRepository = createTestRepository();
|
||||
// String expectedNamespaceAndName = testRepository.getNamespaceAndName().logString();
|
||||
// thrown.expect(AlreadyExistsException.class);
|
||||
// thrown.expectMessage(expectedNamespaceAndName);
|
||||
// createTestRepository();
|
||||
// }
|
||||
//
|
||||
// @Test
|
||||
// public void testDelete() {
|
||||
// delete(manager, createTestRepository());
|
||||
// }
|
||||
//
|
||||
// @SubjectAware(
|
||||
// username = "unpriv"
|
||||
// )
|
||||
// @Test(expected = UnauthorizedException.class)
|
||||
// public void testDeleteWithoutPrivileges() {
|
||||
// delete(manager, createTestRepository());
|
||||
// }
|
||||
//
|
||||
// @Test(expected = RepositoryIsNotArchivedException.class)
|
||||
// public void testDeleteNonArchived() {
|
||||
// configuration.setEnableRepositoryArchive(true);
|
||||
// delete(manager, createTestRepository());
|
||||
// }
|
||||
//
|
||||
// @Test(expected = NotFoundException.class)
|
||||
// public void testDeleteNotFound(){
|
||||
// manager.delete(createRepositoryWithId());
|
||||
// }
|
||||
//
|
||||
// @Test
|
||||
// public void testDeleteWithEnabledArchive() {
|
||||
// Repository repository = createTestRepository();
|
||||
//
|
||||
// repository.setArchived(true);
|
||||
// RepositoryManager drm = createRepositoryManager(true);
|
||||
// drm.init(contextProvider);
|
||||
// delete(drm, repository);
|
||||
// }
|
||||
//
|
||||
// @Test
|
||||
// public void testGet() {
|
||||
// Repository heartOfGold = createTestRepository();
|
||||
// String id = heartOfGold.getId();
|
||||
// String description = heartOfGold.getDescription();
|
||||
//
|
||||
// assertNotNull(description);
|
||||
//
|
||||
// // test for reference
|
||||
// heartOfGold.setDescription("prototype ship");
|
||||
// heartOfGold = manager.get(id);
|
||||
// assertNotNull(heartOfGold);
|
||||
// assertEquals(description, heartOfGold.getDescription());
|
||||
// }
|
||||
//
|
||||
// @Test
|
||||
// @SubjectAware(
|
||||
// username = "crato"
|
||||
// )
|
||||
// public void testGetWithoutRequiredPrivileges() {
|
||||
// Repository heartOfGold = RepositoryTestData.createHeartOfGold();
|
||||
// manager.create(heartOfGold);
|
||||
//
|
||||
// thrown.expect(UnauthorizedException.class);
|
||||
// manager.get(heartOfGold.getId());
|
||||
// }
|
||||
//
|
||||
// @Test
|
||||
// public void testGetAll() {
|
||||
// Repository heartOfGold = createTestRepository();
|
||||
// Repository happyVerticalPeopleTransporter = createSecondTestRepository();
|
||||
// boolean foundHeart = false;
|
||||
// boolean foundTransporter = false;
|
||||
// Collection<Repository> repositories = manager.getAll();
|
||||
//
|
||||
// assertNotNull(repositories);
|
||||
// assertFalse(repositories.isEmpty());
|
||||
// assertTrue(repositories.size() >= 2);
|
||||
//
|
||||
// Repository heartReference = null;
|
||||
//
|
||||
// for (Repository repository : repositories) {
|
||||
// if (repository.getId().equals(heartOfGold.getId())) {
|
||||
// assertRepositoriesEquals(heartOfGold, repository);
|
||||
// foundHeart = true;
|
||||
// heartReference = repository;
|
||||
// }
|
||||
// else if (repository.getId().equals(happyVerticalPeopleTransporter.getId())) {
|
||||
// assertRepositoriesEquals(happyVerticalPeopleTransporter, repository);
|
||||
// foundTransporter = true;
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// assertTrue(foundHeart);
|
||||
// assertTrue(foundTransporter);
|
||||
//
|
||||
// // test for reference
|
||||
// assertNotSame(heartOfGold, heartReference);
|
||||
// heartReference.setDescription("prototype ship");
|
||||
// assertFalse(
|
||||
// heartOfGold.getDescription().equals(heartReference.getDescription()));
|
||||
// }
|
||||
//
|
||||
// @Test
|
||||
// @SuppressWarnings("unchecked")
|
||||
// @SubjectAware(username = "dent")
|
||||
// public void testGetAllWithPermissionsForTwoOrThreeRepos() {
|
||||
// // mock key generator
|
||||
// KeyGenerator keyGenerator = mock(KeyGenerator.class);
|
||||
// Stack<String> keys = new Stack<>();
|
||||
// keys.push("rateotu");
|
||||
// keys.push("p42");
|
||||
// keys.push("hof");
|
||||
//
|
||||
// when(keyGenerator.createKey()).then((InvocationOnMock invocation) -> {
|
||||
// return keys.pop();
|
||||
// });
|
||||
//
|
||||
// // create repository manager
|
||||
// RepositoryManager repositoryManager = createRepositoryManager(false, keyGenerator);
|
||||
//
|
||||
// // create first test repository
|
||||
// Repository heartOfGold = RepositoryTestData.createHeartOfGold();
|
||||
// repositoryManager.create(heartOfGold);
|
||||
// assertEquals("hof", heartOfGold.getId());
|
||||
//
|
||||
// // create second test repository
|
||||
// Repository puzzle42 = RepositoryTestData.create42Puzzle();
|
||||
// repositoryManager.create(puzzle42);
|
||||
// assertEquals("p42", puzzle42.getId());
|
||||
//
|
||||
// // create third test repository
|
||||
// Repository restaurant = RepositoryTestData.createRestaurantAtTheEndOfTheUniverse();
|
||||
// repositoryManager.create(restaurant);
|
||||
// assertEquals("rateotu", restaurant.getId());
|
||||
//
|
||||
// // assert returned repositories
|
||||
// Collection<Repository> repositories = repositoryManager.getAll();
|
||||
// assertEquals(2, repositories.size());
|
||||
// assertThat(repositories, containsInAnyOrder(
|
||||
// hasProperty("id", is("p42")),
|
||||
// hasProperty("id", is("hof"))
|
||||
// )
|
||||
// );
|
||||
// }
|
||||
//
|
||||
// @Test
|
||||
// public void testEvents() {
|
||||
// RepositoryManager repoManager = createRepositoryManager(false);
|
||||
// repoManager.init(contextProvider);
|
||||
// TestListener listener = new TestListener();
|
||||
//
|
||||
// ScmEventBus.getInstance().register(listener);
|
||||
//
|
||||
// Repository repository = RepositoryTestData.create42Puzzle();
|
||||
//
|
||||
// repoManager.create(repository);
|
||||
// assertRepositoriesEquals(repository, listener.preRepository);
|
||||
// assertSame(HandlerEventType.BEFORE_CREATE, listener.preEvent);
|
||||
// assertRepositoriesEquals(repository, listener.postRepository);
|
||||
// assertSame(HandlerEventType.CREATE, listener.postEvent);
|
||||
//
|
||||
// repository.setDescription("changed description");
|
||||
// repoManager.modify(repository);
|
||||
// assertRepositoriesEquals(repository, listener.preRepository);
|
||||
// assertSame(HandlerEventType.BEFORE_MODIFY, listener.preEvent);
|
||||
// assertRepositoriesEquals(repository, listener.postRepository);
|
||||
// assertSame(HandlerEventType.MODIFY, listener.postEvent);
|
||||
//
|
||||
// repoManager.delete(repository);
|
||||
//
|
||||
// assertRepositoriesEquals(repository, listener.preRepository);
|
||||
// assertSame(HandlerEventType.BEFORE_DELETE, listener.preEvent);
|
||||
// assertRepositoriesEquals(repository, listener.postRepository);
|
||||
// assertSame(HandlerEventType.DELETE, listener.postEvent);
|
||||
// }
|
||||
//
|
||||
// @Test
|
||||
// public void testModify() {
|
||||
// Repository heartOfGold = createTestRepository();
|
||||
//
|
||||
// heartOfGold.setDescription("prototype ship");
|
||||
// manager.modify(heartOfGold);
|
||||
//
|
||||
// Repository hearReference = manager.get(heartOfGold.getId());
|
||||
//
|
||||
// assertNotNull(hearReference);
|
||||
// assertEquals(hearReference.getDescription(), "prototype ship");
|
||||
// }
|
||||
//
|
||||
// @Test
|
||||
// @SubjectAware(username = "crato")
|
||||
// public void testModifyWithoutRequiredPermissions() {
|
||||
// Repository heartOfGold = RepositoryTestData.createHeartOfGold();
|
||||
// manager.create(heartOfGold);
|
||||
// heartOfGold.setDescription("prototype ship");
|
||||
//
|
||||
// thrown.expect(UnauthorizedException.class);
|
||||
// manager.modify(heartOfGold);
|
||||
// }
|
||||
//
|
||||
// @Test(expected = NotFoundException.class)
|
||||
// public void testModifyNotFound(){
|
||||
// manager.modify(createRepositoryWithId());
|
||||
// }
|
||||
//
|
||||
// @Test
|
||||
// public void testRefresh() {
|
||||
// Repository heartOfGold = createTestRepository();
|
||||
// String description = heartOfGold.getDescription();
|
||||
//
|
||||
// heartOfGold.setDescription("prototype ship");
|
||||
// manager.refresh(heartOfGold);
|
||||
// assertEquals(description, heartOfGold.getDescription());
|
||||
// }
|
||||
//
|
||||
// @Test
|
||||
// @SubjectAware(username = "crato")
|
||||
// public void testRefreshWithoutRequiredPermissions() {
|
||||
// Repository heartOfGold = RepositoryTestData.createHeartOfGold();
|
||||
// manager.create(heartOfGold);
|
||||
// heartOfGold.setDescription("prototype ship");
|
||||
//
|
||||
// thrown.expect(UnauthorizedException.class);
|
||||
// manager.refresh(heartOfGold);
|
||||
// }
|
||||
//
|
||||
// @Test(expected = NotFoundException.class)
|
||||
// public void testRefreshNotFound(){
|
||||
// manager.refresh(createRepositoryWithId());
|
||||
// }
|
||||
//
|
||||
// @Test
|
||||
// public void testRepositoryHook() {
|
||||
// CountingReceiveHook hook = new CountingReceiveHook();
|
||||
// RepositoryManager repoManager = createRepositoryManager(false);
|
||||
//
|
||||
// ScmEventBus.getInstance().register(hook);
|
||||
//
|
||||
// assertEquals(0, hook.eventsReceived);
|
||||
//
|
||||
// Repository repository = createTestRepository();
|
||||
// HookContext ctx = createHookContext(repository);
|
||||
//
|
||||
// repoManager.fireHookEvent(new RepositoryHookEvent(ctx, repository,
|
||||
// RepositoryHookType.POST_RECEIVE));
|
||||
// assertEquals(1, hook.eventsReceived);
|
||||
// repoManager.fireHookEvent(new RepositoryHookEvent(ctx, repository,
|
||||
// RepositoryHookType.POST_RECEIVE));
|
||||
// assertEquals(2, hook.eventsReceived);
|
||||
// }
|
||||
//
|
||||
// @Test
|
||||
// public void testNamespaceSet() {
|
||||
// RepositoryManager repoManager = createRepositoryManager(false);
|
||||
// Repository repository = spy(createTestRepository());
|
||||
// repository.setName("Testrepo");
|
||||
// repoManager.create(repository);
|
||||
// assertEquals("default_namespace", repository.getNamespace());
|
||||
// }
|
||||
//
|
||||
// @Test
|
||||
// public void shouldSetNamespace() {
|
||||
// Repository repository = new Repository(null, "hg", null, "scm");
|
||||
// manager.create(repository);
|
||||
// assertNotNull(repository.getId());
|
||||
// assertNotNull(repository.getNamespace());
|
||||
// }
|
||||
//
|
||||
// //~--- methods --------------------------------------------------------------
|
||||
//
|
||||
// @Override
|
||||
// protected DefaultRepositoryManager createManager() {
|
||||
// return createRepositoryManager(false);
|
||||
// }
|
||||
//
|
||||
// private DefaultRepositoryManager createRepositoryManager(boolean archiveEnabled) {
|
||||
// return createRepositoryManager(archiveEnabled, new DefaultKeyGenerator());
|
||||
// }
|
||||
//
|
||||
// private DefaultRepositoryManager createRepositoryManager(boolean archiveEnabled, KeyGenerator keyGenerator) {
|
||||
// DefaultFileSystem fileSystem = new DefaultFileSystem();
|
||||
// Set<RepositoryHandler> handlerSet = new HashSet<>();
|
||||
// PathBasedRepositoryLocationResolver repositoryLocationResolver = mock(PathBasedRepositoryLocationResolver.class, RETURNS_DEEP_STUBS);
|
||||
// when(repositoryLocationResolver.forClass(Path.class).getLocation(anyString())).thenReturn(Paths.get("."));
|
||||
// XmlRepositoryDAO repositoryDAO = new XmlRepositoryDAO(contextProvider, repositoryLocationResolver, fileSystem);
|
||||
// ConfigurationStoreFactory factory = new JAXBConfigurationStoreFactory(contextProvider, repositoryLocationResolver);
|
||||
// handlerSet.add(new DummyRepositoryHandler(factory, repositoryLocationResolver));
|
||||
// handlerSet.add(new DummyRepositoryHandler(factory, repositoryLocationResolver) {
|
||||
// @Override
|
||||
// public RepositoryType getType() {
|
||||
// return new RepositoryType("hg", "Mercurial", Sets.newHashSet());
|
||||
// }
|
||||
// });
|
||||
// handlerSet.add(new DummyRepositoryHandler(factory, repositoryLocationResolver) {
|
||||
// @Override
|
||||
// public RepositoryType getType() {
|
||||
// return new RepositoryType("git", "Git", Sets.newHashSet());
|
||||
// }
|
||||
// });
|
||||
//
|
||||
//
|
||||
// this.configuration = new ScmConfiguration();
|
||||
//
|
||||
// configuration.setEnableRepositoryArchive(archiveEnabled);
|
||||
//
|
||||
// NamespaceStrategy namespaceStrategy = mock(NamespaceStrategy.class);
|
||||
// when(namespaceStrategy.createNamespace(Mockito.any(Repository.class))).thenAnswer(invocation -> mockedNamespace);
|
||||
//
|
||||
// return new DefaultRepositoryManager(configuration, contextProvider,
|
||||
// keyGenerator, repositoryDAO, handlerSet, Providers.of(namespaceStrategy));
|
||||
// }
|
||||
//
|
||||
// private HookContext createHookContext(Repository repository) {
|
||||
// PreProcessorUtil ppu = mock(PreProcessorUtil.class);
|
||||
// HookContextProvider provider = mock(HookContextProvider.class);
|
||||
// Set<HookFeature> features = ImmutableSet.of();
|
||||
//
|
||||
// when(provider.getSupportedFeatures()).thenReturn(features);
|
||||
//
|
||||
// return new HookContextFactory(ppu).createContext(provider, repository);
|
||||
// }
|
||||
//
|
||||
// private void assertRepositoriesEquals(Repository repo, Repository other) {
|
||||
// assertEquals(repo.getId(), other.getId());
|
||||
// assertEquals(repo.getName(), other.getName());
|
||||
// assertEquals(repo.getDescription(), other.getDescription());
|
||||
// assertEquals(repo.getContact(), other.getContact());
|
||||
// assertEquals(repo.getCreationDate(), other.getCreationDate());
|
||||
// assertEquals(repo.getLastModified(), other.getLastModified());
|
||||
// }
|
||||
//
|
||||
// private Repository createRepository(Repository repository) {
|
||||
// manager.create(repository);
|
||||
// assertNotNull(repository.getId());
|
||||
// assertNotNull(manager.get(repository.getId()));
|
||||
// assertTrue(repository.getCreationDate() > 0);
|
||||
//
|
||||
// return repository;
|
||||
// }
|
||||
//
|
||||
// private Repository createRepositoryWithId() {
|
||||
// Repository repository = RepositoryTestData.createHeartOfGold();
|
||||
// repository.setId("abc");
|
||||
// return repository;
|
||||
// }
|
||||
//
|
||||
// private Repository createSecondTestRepository() {
|
||||
// return createRepository(
|
||||
// RepositoryTestData.createHappyVerticalPeopleTransporter());
|
||||
// }
|
||||
//
|
||||
// private Repository createTestRepository() {
|
||||
// return createRepository(RepositoryTestData.createHeartOfGold());
|
||||
// }
|
||||
//
|
||||
// private void delete(Manager<Repository> manager, Repository repository){
|
||||
//
|
||||
// String id = repository.getId();
|
||||
//
|
||||
// manager.delete(repository);
|
||||
// assertNull(manager.get(id));
|
||||
// }
|
||||
//
|
||||
// private static class CountingReceiveHook {
|
||||
//
|
||||
// private int eventsReceived = 0;
|
||||
//
|
||||
// @Subscribe(async = false)
|
||||
// public void onEvent(PostReceiveRepositoryHookEvent event) {
|
||||
// eventsReceived++;
|
||||
// }
|
||||
//
|
||||
// @Subscribe(async = false)
|
||||
// public void onEvent(PreReceiveRepositoryHookEvent event) {
|
||||
// eventsReceived++;
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// private class TestListener {
|
||||
//
|
||||
// private HandlerEventType postEvent;
|
||||
//
|
||||
// private Repository postRepository;
|
||||
//
|
||||
// private HandlerEventType preEvent;
|
||||
//
|
||||
// private Repository preRepository;
|
||||
//
|
||||
// @Subscribe(async = false)
|
||||
// public void onEvent(RepositoryEvent event) {
|
||||
// if (event.getEventType().isPost()) {
|
||||
// this.postRepository = event.getItem();
|
||||
// this.postEvent = event.getEventType();
|
||||
// }
|
||||
// else if (event.getEventType().isPre()) {
|
||||
// this.preRepository = event.getItem();
|
||||
// this.preEvent = event.getEventType();
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
}
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
package sonia.scm.update;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import sonia.scm.migration.UpdateStep;
|
||||
import sonia.scm.store.ConfigurationEntryStoreFactory;
|
||||
import sonia.scm.store.InMemoryConfigurationEntryStore;
|
||||
import sonia.scm.store.InMemoryConfigurationEntryStoreFactory;
|
||||
import sonia.scm.version.Version;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static sonia.scm.version.Version.parse;
|
||||
|
||||
class UpdateEngineTest {
|
||||
|
||||
ConfigurationEntryStoreFactory storeFactory = new InMemoryConfigurationEntryStoreFactory(new InMemoryConfigurationEntryStore());
|
||||
|
||||
List<String> processedUpdates = new ArrayList<>();
|
||||
|
||||
@Test
|
||||
void shouldProcessStepsInCorrectOrder() {
|
||||
LinkedHashSet<UpdateStep> updateSteps = new LinkedHashSet<>();
|
||||
|
||||
updateSteps.add(new FixedVersionUpdateStep("test", "1.1.1"));
|
||||
updateSteps.add(new FixedVersionUpdateStep("test", "1.2.0"));
|
||||
updateSteps.add(new FixedVersionUpdateStep("test", "1.1.0"));
|
||||
|
||||
UpdateEngine updateEngine = new UpdateEngine(updateSteps, storeFactory);
|
||||
updateEngine.update();
|
||||
|
||||
assertThat(processedUpdates)
|
||||
.containsExactly("1.1.0", "1.1.1", "1.2.0");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRunStepsOnlyOnce() {
|
||||
LinkedHashSet<UpdateStep> updateSteps = new LinkedHashSet<>();
|
||||
|
||||
updateSteps.add(new FixedVersionUpdateStep("test", "1.1.1"));
|
||||
|
||||
UpdateEngine updateEngine = new UpdateEngine(updateSteps, storeFactory);
|
||||
updateEngine.update();
|
||||
|
||||
processedUpdates.clear();
|
||||
|
||||
updateEngine.update();
|
||||
|
||||
assertThat(processedUpdates).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRunStepsForDifferentTypesIndependently() {
|
||||
LinkedHashSet<UpdateStep> updateSteps = new LinkedHashSet<>();
|
||||
|
||||
updateSteps.add(new FixedVersionUpdateStep("test", "1.1.1"));
|
||||
|
||||
UpdateEngine updateEngine = new UpdateEngine(updateSteps, storeFactory);
|
||||
updateEngine.update();
|
||||
|
||||
processedUpdates.clear();
|
||||
|
||||
updateSteps.add(new FixedVersionUpdateStep("other", "1.1.1"));
|
||||
|
||||
updateEngine = new UpdateEngine(updateSteps, storeFactory);
|
||||
updateEngine.update();
|
||||
|
||||
assertThat(processedUpdates).containsExactly("1.1.1");
|
||||
}
|
||||
|
||||
class FixedVersionUpdateStep implements UpdateStep {
|
||||
private final String type;
|
||||
private final String version;
|
||||
|
||||
FixedVersionUpdateStep(String type, String version) {
|
||||
this.type = type;
|
||||
this.version = version;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Version getTargetVersion() {
|
||||
return parse(version);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAffectedDataType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doUpdate() {
|
||||
processedUpdates.add(version);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user