mirror of
https://github.com/scm-manager/scm-manager.git
synced 2025-11-12 08:25:44 +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.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
|
||||||
//~--- JDK imports ------------------------------------------------------------
|
//~--- JDK imports ------------------------------------------------------------
|
||||||
|
|
||||||
@@ -172,6 +173,6 @@ public abstract class AbstractSimpleRepositoryHandler<C extends RepositoryConfig
|
|||||||
}
|
}
|
||||||
|
|
||||||
private File resolveNativeDirectory(String repositoryId) {
|
private File resolveNativeDirectory(String repositoryId) {
|
||||||
return repositoryLocationResolver.getPath(repositoryId).resolve(REPOSITORIES_NATIVE_DIRECTORY).toFile();
|
return repositoryLocationResolver.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;
|
package sonia.scm.repository;
|
||||||
|
|
||||||
import sonia.scm.SCMContextProvider;
|
public abstract class RepositoryLocationResolver {
|
||||||
|
|
||||||
import javax.inject.Inject;
|
public abstract boolean supportsLocationType(Class<?> type);
|
||||||
import java.nio.file.Path;
|
|
||||||
|
|
||||||
/**
|
protected abstract <T> RepositoryLocationResolverInstance<T> create(Class<T> type);
|
||||||
* A Location Resolver for File based Repository Storage.
|
|
||||||
* <p>
|
|
||||||
* <b>WARNING:</b> The Locations provided with this class may not be used from the plugins to store any plugin specific files.
|
|
||||||
* <p>
|
|
||||||
* Please use the {@link sonia.scm.store.DataStoreFactory } and the {@link sonia.scm.store.DataStore} classes to store data<br>
|
|
||||||
* Please use the {@link sonia.scm.store.BlobStoreFactory } and the {@link sonia.scm.store.BlobStore} classes to store binary files<br>
|
|
||||||
* Please use the {@link sonia.scm.store.ConfigurationStoreFactory} and the {@link sonia.scm.store.ConfigurationStore} classes to store configurations
|
|
||||||
*
|
|
||||||
* @author Mohamed Karray
|
|
||||||
* @since 2.0.0
|
|
||||||
*/
|
|
||||||
public class RepositoryLocationResolver {
|
|
||||||
|
|
||||||
private final SCMContextProvider contextProvider;
|
public final <T> RepositoryLocationResolverInstance<T> forClass(Class<T> type) {
|
||||||
private final RepositoryDAO repositoryDAO;
|
if (!supportsLocationType(type)) {
|
||||||
private final InitialRepositoryLocationResolver initialRepositoryLocationResolver;
|
throw new IllegalStateException("no support for location of class " + type);
|
||||||
|
}
|
||||||
@Inject
|
return create(type);
|
||||||
public RepositoryLocationResolver(SCMContextProvider contextProvider, RepositoryDAO repositoryDAO, InitialRepositoryLocationResolver initialRepositoryLocationResolver) {
|
|
||||||
this.contextProvider = contextProvider;
|
|
||||||
this.repositoryDAO = repositoryDAO;
|
|
||||||
this.initialRepositoryLocationResolver = initialRepositoryLocationResolver;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
@FunctionalInterface
|
||||||
* Returns the path to the repository.
|
public interface RepositoryLocationResolverInstance<T> {
|
||||||
*
|
T getLocation(String repositoryId);
|
||||||
* @param repositoryId repository id
|
|
||||||
*
|
|
||||||
* @return path of repository
|
|
||||||
*/
|
|
||||||
public Path getPath(String repositoryId) {
|
|
||||||
Path path;
|
|
||||||
|
|
||||||
if (repositoryDAO instanceof PathBasedRepositoryDAO) {
|
|
||||||
path = ((PathBasedRepositoryDAO) repositoryDAO).getPath(repositoryId);
|
|
||||||
} else {
|
|
||||||
path = initialRepositoryLocationResolver.getPath(repositoryId);
|
|
||||||
}
|
|
||||||
|
|
||||||
return contextProvider.resolve(path);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -121,8 +121,11 @@ public class RepositoryRole implements ModelObject, PermissionObject {
|
|||||||
* @return the hash code value for the {@link RepositoryRole}
|
* @return the hash code value for the {@link RepositoryRole}
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public int hashCode() {
|
public int hashCode()
|
||||||
return Objects.hashCode(name, verbs);
|
{
|
||||||
|
// 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
|
@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 --------------------------------------------------------
|
//~--- non-JDK imports --------------------------------------------------------
|
||||||
|
|
||||||
import com.google.common.annotations.VisibleForTesting;
|
|
||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
import com.google.inject.Singleton;
|
import com.google.inject.Singleton;
|
||||||
import sonia.scm.SCMContextProvider;
|
|
||||||
import sonia.scm.io.FileSystem;
|
import sonia.scm.io.FileSystem;
|
||||||
import sonia.scm.repository.InitialRepositoryLocationResolver;
|
|
||||||
import sonia.scm.repository.InternalRepositoryException;
|
import sonia.scm.repository.InternalRepositoryException;
|
||||||
import sonia.scm.repository.NamespaceAndName;
|
import sonia.scm.repository.NamespaceAndName;
|
||||||
import sonia.scm.repository.PathBasedRepositoryDAO;
|
|
||||||
import sonia.scm.repository.Repository;
|
import sonia.scm.repository.Repository;
|
||||||
|
import sonia.scm.repository.RepositoryDAO;
|
||||||
import sonia.scm.store.StoreConstants;
|
import sonia.scm.store.StoreConstants;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.time.Clock;
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
@@ -57,82 +53,38 @@ import java.util.concurrent.ConcurrentHashMap;
|
|||||||
* @author Sebastian Sdorra
|
* @author Sebastian Sdorra
|
||||||
*/
|
*/
|
||||||
@Singleton
|
@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 MetadataStore metadataStore = new MetadataStore();
|
||||||
|
|
||||||
private final SCMContextProvider context;
|
private final PathBasedRepositoryLocationResolver repositoryLocationResolver;
|
||||||
private final InitialRepositoryLocationResolver locationResolver;
|
|
||||||
private final FileSystem fileSystem;
|
private final FileSystem fileSystem;
|
||||||
|
|
||||||
private final Map<String, Path> pathById;
|
|
||||||
private final Map<String, Repository> byId;
|
private final Map<String, Repository> byId;
|
||||||
private final Map<NamespaceAndName, Repository> byNamespaceAndName;
|
private final Map<NamespaceAndName, Repository> byNamespaceAndName;
|
||||||
|
|
||||||
private final Clock clock;
|
|
||||||
|
|
||||||
private Long creationTime;
|
|
||||||
private Long lastModified;
|
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public XmlRepositoryDAO(SCMContextProvider context, InitialRepositoryLocationResolver locationResolver, FileSystem fileSystem) {
|
public XmlRepositoryDAO(PathBasedRepositoryLocationResolver repositoryLocationResolver, FileSystem fileSystem) {
|
||||||
this(context, locationResolver, fileSystem, Clock.systemUTC());
|
this.repositoryLocationResolver = repositoryLocationResolver;
|
||||||
}
|
|
||||||
|
|
||||||
XmlRepositoryDAO(SCMContextProvider context, InitialRepositoryLocationResolver locationResolver, FileSystem fileSystem, Clock clock) {
|
|
||||||
this.context = context;
|
|
||||||
this.locationResolver = locationResolver;
|
|
||||||
this.fileSystem = fileSystem;
|
this.fileSystem = fileSystem;
|
||||||
|
|
||||||
this.clock = clock;
|
|
||||||
this.creationTime = clock.millis();
|
|
||||||
|
|
||||||
this.pathById = new ConcurrentHashMap<>();
|
|
||||||
this.byId = new ConcurrentHashMap<>();
|
this.byId = new ConcurrentHashMap<>();
|
||||||
this.byNamespaceAndName = new ConcurrentHashMap<>();
|
this.byNamespaceAndName = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
pathDatabase = new PathDatabase(resolveStorePath());
|
init();
|
||||||
read();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void read() {
|
private void init() {
|
||||||
Path storePath = resolveStorePath();
|
repositoryLocationResolver.forAllPaths((repositoryId, repositoryPath) -> {
|
||||||
|
Path metadataPath = resolveDataPath(repositoryPath);
|
||||||
// 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void onLoadRepository(String id, Path repositoryPath) {
|
|
||||||
Path metadataPath = resolveMetadataPath(context.resolve(repositoryPath));
|
|
||||||
|
|
||||||
Repository repository = metadataStore.read(metadataPath);
|
Repository repository = metadataStore.read(metadataPath);
|
||||||
|
|
||||||
byId.put(id, repository);
|
|
||||||
byNamespaceAndName.put(repository.getNamespaceAndName(), repository);
|
byNamespaceAndName.put(repository.getNamespaceAndName(), repository);
|
||||||
pathById.put(id, repositoryPath);
|
byId.put(repositoryId, repository);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@VisibleForTesting
|
private Path resolveDataPath(Path repositoryPath) {
|
||||||
Path resolveStorePath() {
|
|
||||||
return context.getBaseDirectory()
|
|
||||||
.toPath()
|
|
||||||
.resolve(StoreConstants.CONFIG_DIRECTORY_NAME)
|
|
||||||
.resolve(STORE_NAME.concat(StoreConstants.FILE_EXTENSION));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@VisibleForTesting
|
|
||||||
Path resolveMetadataPath(Path repositoryPath) {
|
|
||||||
return repositoryPath.resolve(StoreConstants.REPOSITORY_METADATA.concat(StoreConstants.FILE_EXTENSION));
|
return repositoryPath.resolve(StoreConstants.REPOSITORY_METADATA.concat(StoreConstants.FILE_EXTENSION));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -141,47 +93,27 @@ public class XmlRepositoryDAO implements PathBasedRepositoryDAO {
|
|||||||
return "xml";
|
return "xml";
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public Long getCreationTime() {
|
|
||||||
return creationTime;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Long getLastModified() {
|
|
||||||
return lastModified;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void add(Repository repository) {
|
public void add(Repository repository) {
|
||||||
Repository clone = repository.clone();
|
Repository clone = repository.clone();
|
||||||
|
|
||||||
Path repositoryPath = locationResolver.getPath(repository.getId());
|
synchronized (this) {
|
||||||
Path resolvedPath = context.resolve(repositoryPath);
|
Path repositoryPath = repositoryLocationResolver.create(repository.getId());
|
||||||
|
|
||||||
try {
|
try {
|
||||||
fileSystem.create(resolvedPath.toFile());
|
Path metadataPath = resolveDataPath(repositoryPath);
|
||||||
|
|
||||||
Path metadataPath = resolveMetadataPath(resolvedPath);
|
|
||||||
metadataStore.write(metadataPath, repository);
|
metadataStore.write(metadataPath, repository);
|
||||||
|
} catch (Exception e) {
|
||||||
synchronized (this) {
|
repositoryLocationResolver.remove(repository.getId());
|
||||||
pathById.put(repository.getId(), repositoryPath);
|
throw new InternalRepositoryException(repository, "failed to create filesystem", e);
|
||||||
|
}
|
||||||
|
|
||||||
byId.put(repository.getId(), clone);
|
byId.put(repository.getId(), clone);
|
||||||
byNamespaceAndName.put(repository.getNamespaceAndName(), clone);
|
byNamespaceAndName.put(repository.getNamespaceAndName(), clone);
|
||||||
|
|
||||||
writePathDatabase();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new InternalRepositoryException(repository, "failed to create filesystem", e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void writePathDatabase() {
|
|
||||||
lastModified = clock.millis();
|
|
||||||
pathDatabase.write(creationTime, lastModified, pathById);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean contains(Repository repository) {
|
public boolean contains(Repository repository) {
|
||||||
@@ -224,12 +156,13 @@ public class XmlRepositoryDAO implements PathBasedRepositoryDAO {
|
|||||||
byNamespaceAndName.remove(prev.getNamespaceAndName());
|
byNamespaceAndName.remove(prev.getNamespaceAndName());
|
||||||
}
|
}
|
||||||
byNamespaceAndName.put(clone.getNamespaceAndName(), clone);
|
byNamespaceAndName.put(clone.getNamespaceAndName(), clone);
|
||||||
|
|
||||||
writePathDatabase();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Path repositoryPath = context.resolve(getPath(repository.getId()));
|
Path repositoryPath = repositoryLocationResolver
|
||||||
Path metadataPath = resolveMetadataPath(repositoryPath);
|
.create(Path.class)
|
||||||
|
.getLocation(repository.getId());
|
||||||
|
Path metadataPath = resolveDataPath(repositoryPath);
|
||||||
|
repositoryLocationResolver.updateModificationDate();
|
||||||
metadataStore.write(metadataPath, clone);
|
metadataStore.write(metadataPath, clone);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -241,14 +174,9 @@ public class XmlRepositoryDAO implements PathBasedRepositoryDAO {
|
|||||||
if (prev != null) {
|
if (prev != null) {
|
||||||
byNamespaceAndName.remove(prev.getNamespaceAndName());
|
byNamespaceAndName.remove(prev.getNamespaceAndName());
|
||||||
}
|
}
|
||||||
|
path = repositoryLocationResolver.remove(repository.getId());
|
||||||
path = pathById.remove(repository.getId());
|
|
||||||
|
|
||||||
writePathDatabase();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
path = context.resolve(path);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
fileSystem.destroy(path.toFile());
|
fileSystem.destroy(path.toFile());
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
@@ -257,7 +185,12 @@ public class XmlRepositoryDAO implements PathBasedRepositoryDAO {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Path getPath(String repositoryId) {
|
public Long getCreationTime() {
|
||||||
return pathById.get(repositoryId);
|
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 sonia.scm.util.IOUtil;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
|
||||||
//~--- JDK imports ------------------------------------------------------------
|
//~--- JDK imports ------------------------------------------------------------
|
||||||
|
|
||||||
@@ -92,7 +93,7 @@ public abstract class FileBasedStoreFactory {
|
|||||||
* @return the store directory of a specific repository
|
* @return the store directory of a specific repository
|
||||||
*/
|
*/
|
||||||
private File getStoreDirectory(Store store, Repository repository) {
|
private File getStoreDirectory(Store store, Repository repository) {
|
||||||
return new File(repositoryLocationResolver.getPath(repository.getId()).toFile(), store.getRepositoryStoreDirectory());
|
return new File(repositoryLocationResolver.forClass(Path.class).getLocation(repository.getId()).toFile(), store.getRepositoryStoreDirectory());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -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,78 +2,81 @@ package sonia.scm.repository.xml;
|
|||||||
|
|
||||||
|
|
||||||
import com.google.common.base.Charsets;
|
import com.google.common.base.Charsets;
|
||||||
|
import com.google.common.io.Resources;
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Nested;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.junit.jupiter.api.extension.ExtendWith;
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
import org.junitpioneer.jupiter.TempDirectory;
|
import org.junitpioneer.jupiter.TempDirectory;
|
||||||
|
import org.mockito.ArgumentCaptor;
|
||||||
|
import org.mockito.Captor;
|
||||||
import org.mockito.Mock;
|
import org.mockito.Mock;
|
||||||
|
import org.mockito.invocation.InvocationOnMock;
|
||||||
import org.mockito.junit.jupiter.MockitoExtension;
|
import org.mockito.junit.jupiter.MockitoExtension;
|
||||||
import org.mockito.junit.jupiter.MockitoSettings;
|
import org.mockito.junit.jupiter.MockitoSettings;
|
||||||
import org.mockito.quality.Strictness;
|
import org.mockito.quality.Strictness;
|
||||||
import sonia.scm.SCMContextProvider;
|
|
||||||
import sonia.scm.io.DefaultFileSystem;
|
import sonia.scm.io.DefaultFileSystem;
|
||||||
import sonia.scm.io.FileSystem;
|
import sonia.scm.io.FileSystem;
|
||||||
import sonia.scm.repository.InitialRepositoryLocationResolver;
|
|
||||||
import sonia.scm.repository.NamespaceAndName;
|
import sonia.scm.repository.NamespaceAndName;
|
||||||
import sonia.scm.repository.Repository;
|
import sonia.scm.repository.Repository;
|
||||||
import sonia.scm.repository.RepositoryPermission;
|
import sonia.scm.repository.RepositoryPermission;
|
||||||
import sonia.scm.repository.RepositoryTestData;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.net.URL;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.nio.file.Paths;
|
|
||||||
import java.time.Clock;
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.function.BiConsumer;
|
||||||
import java.util.concurrent.atomic.AtomicLong;
|
|
||||||
|
|
||||||
import static java.util.Arrays.asList;
|
import static java.util.Arrays.asList;
|
||||||
|
import static java.util.Collections.singletonList;
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
import static org.mockito.ArgumentMatchers.any;
|
import static org.junit.jupiter.api.Assertions.fail;
|
||||||
import static org.mockito.Mockito.mock;
|
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;
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
@ExtendWith({MockitoExtension.class, TempDirectory.class})
|
@ExtendWith({MockitoExtension.class, TempDirectory.class})
|
||||||
@MockitoSettings(strictness = Strictness.LENIENT)
|
@MockitoSettings(strictness = Strictness.LENIENT)
|
||||||
class XmlRepositoryDAOTest {
|
class XmlRepositoryDAOTest {
|
||||||
|
|
||||||
@Mock
|
private final Repository REPOSITORY = createRepository("42");
|
||||||
private SCMContextProvider context;
|
|
||||||
|
|
||||||
@Mock
|
@Mock
|
||||||
private InitialRepositoryLocationResolver locationResolver;
|
private PathBasedRepositoryLocationResolver locationResolver;
|
||||||
|
|
||||||
|
@Captor
|
||||||
|
private ArgumentCaptor<BiConsumer<String, Path>> forAllCaptor;
|
||||||
|
|
||||||
private FileSystem fileSystem = new DefaultFileSystem();
|
private FileSystem fileSystem = new DefaultFileSystem();
|
||||||
|
|
||||||
private XmlRepositoryDAO dao;
|
private XmlRepositoryDAO dao;
|
||||||
|
|
||||||
private Path baseDirectory;
|
|
||||||
|
|
||||||
private AtomicLong atomicClock;
|
|
||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
void createDAO(@TempDirectory.TempDir Path baseDirectory) {
|
void createDAO(@TempDirectory.TempDir Path basePath) {
|
||||||
this.baseDirectory = baseDirectory;
|
when(locationResolver.create(Path.class)).thenReturn(locationResolver::create);
|
||||||
this.atomicClock = new AtomicLong();
|
when(locationResolver.create(anyString())).thenAnswer(invocation -> createMockedRepoPath(basePath, invocation));
|
||||||
|
when(locationResolver.remove(anyString())).thenAnswer(invocation -> basePath.resolve(invocation.getArgument(0).toString()));
|
||||||
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();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private XmlRepositoryDAO createDAO() {
|
private Path createMockedRepoPath(@TempDirectory.TempDir Path basePath, InvocationOnMock invocation) {
|
||||||
Clock clock = mock(Clock.class);
|
Path resolvedPath = basePath.resolve(invocation.getArgument(0).toString());
|
||||||
when(clock.millis()).then(ic -> atomicClock.incrementAndGet());
|
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
|
@Test
|
||||||
@@ -82,39 +85,63 @@ class XmlRepositoryDAOTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void shouldReturnCreationTimeAfterCreation() {
|
void shouldReturnCreationTimeOfLocationResolver() {
|
||||||
long now = atomicClock.get();
|
long now = 42L;
|
||||||
|
when(locationResolver.getCreationTime()).thenReturn(now);
|
||||||
assertThat(dao.getCreationTime()).isEqualTo(now);
|
assertThat(dao.getCreationTime()).isEqualTo(now);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void shouldNotReturnLastModifiedAfterCreation() {
|
void shouldReturnLasModifiedOfLocationResolver() {
|
||||||
assertThat(dao.getLastModified()).isNull();
|
long now = 42L;
|
||||||
|
when(locationResolver.getLastModified()).thenReturn(now);
|
||||||
|
assertThat(dao.getLastModified()).isEqualTo(now);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void shouldReturnTrueForEachContainsMethod() {
|
void shouldReturnTrueForEachContainsMethod() {
|
||||||
Repository heartOfGold = createHeartOfGold();
|
dao.add(REPOSITORY);
|
||||||
dao.add(heartOfGold);
|
|
||||||
|
|
||||||
assertThat(dao.contains(heartOfGold)).isTrue();
|
assertThat(dao.contains(REPOSITORY)).isTrue();
|
||||||
assertThat(dao.contains(heartOfGold.getId())).isTrue();
|
assertThat(dao.contains(REPOSITORY.getId())).isTrue();
|
||||||
assertThat(dao.contains(heartOfGold.getNamespaceAndName())).isTrue();
|
assertThat(dao.contains(REPOSITORY.getNamespaceAndName())).isTrue();
|
||||||
}
|
}
|
||||||
|
|
||||||
private Repository createHeartOfGold() {
|
@Test
|
||||||
Repository heartOfGold = RepositoryTestData.createHeartOfGold();
|
void shouldPersistRepository() {
|
||||||
heartOfGold.setId("42");
|
dao.add(REPOSITORY);
|
||||||
return heartOfGold;
|
|
||||||
|
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
|
@Test
|
||||||
void shouldReturnFalseForEachContainsMethod() {
|
void shouldReturnFalseForEachContainsMethod() {
|
||||||
Repository heartOfGold = createHeartOfGold();
|
assertThat(dao.contains(REPOSITORY)).isFalse();
|
||||||
|
assertThat(dao.contains(REPOSITORY.getId())).isFalse();
|
||||||
assertThat(dao.contains(heartOfGold)).isFalse();
|
assertThat(dao.contains(REPOSITORY.getNamespaceAndName())).isFalse();
|
||||||
assertThat(dao.contains(heartOfGold.getId())).isFalse();
|
|
||||||
assertThat(dao.contains(heartOfGold.getNamespaceAndName())).isFalse();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -125,48 +152,39 @@ class XmlRepositoryDAOTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
void shouldReturnRepository() {
|
void shouldReturnRepository() {
|
||||||
Repository heartOfGold = createHeartOfGold();
|
dao.add(REPOSITORY);
|
||||||
dao.add(heartOfGold);
|
|
||||||
|
|
||||||
assertThat(dao.get("42")).isEqualTo(heartOfGold);
|
assertThat(dao.get("42")).isEqualTo(REPOSITORY);
|
||||||
assertThat(dao.get(new NamespaceAndName("hitchhiker","HeartOfGold"))).isEqualTo(heartOfGold);
|
assertThat(dao.get(new NamespaceAndName("space", "42"))).isEqualTo(REPOSITORY);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void shouldNotReturnTheSameInstance() {
|
void shouldNotReturnTheSameInstance() {
|
||||||
Repository heartOfGold = createHeartOfGold();
|
dao.add(REPOSITORY);
|
||||||
dao.add(heartOfGold);
|
|
||||||
|
|
||||||
Repository repository = dao.get("42");
|
Repository repository = dao.get("42");
|
||||||
assertThat(repository).isNotSameAs(heartOfGold);
|
assertThat(repository).isNotSameAs(REPOSITORY);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void shouldReturnAllRepositories() {
|
void shouldReturnAllRepositories() {
|
||||||
Repository heartOfGold = createHeartOfGold();
|
dao.add(REPOSITORY);
|
||||||
dao.add(heartOfGold);
|
|
||||||
|
|
||||||
Repository puzzle = createPuzzle();
|
Repository secondRepository = createRepository("23");
|
||||||
dao.add(puzzle);
|
dao.add(secondRepository);
|
||||||
|
|
||||||
Collection<Repository> repositories = dao.getAll();
|
Collection<Repository> repositories = dao.getAll();
|
||||||
assertThat(repositories).containsExactlyInAnyOrder(heartOfGold, puzzle);
|
assertThat(repositories)
|
||||||
}
|
.containsExactlyInAnyOrder(REPOSITORY, secondRepository);
|
||||||
|
|
||||||
private Repository createPuzzle() {
|
|
||||||
Repository puzzle = RepositoryTestData.create42Puzzle();
|
|
||||||
puzzle.setId("42+1");
|
|
||||||
return puzzle;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void shouldModifyRepository() {
|
void shouldModifyRepositoryTwice() {
|
||||||
Repository heartOfGold = createHeartOfGold();
|
REPOSITORY.setDescription("HeartOfGold");
|
||||||
heartOfGold.setDescription("HeartOfGold");
|
dao.add(REPOSITORY);
|
||||||
dao.add(heartOfGold);
|
|
||||||
assertThat(dao.get("42").getDescription()).isEqualTo("HeartOfGold");
|
assertThat(dao.get("42").getDescription()).isEqualTo("HeartOfGold");
|
||||||
|
|
||||||
heartOfGold = createHeartOfGold();
|
Repository heartOfGold = createRepository("42");
|
||||||
heartOfGold.setDescription("Heart of Gold");
|
heartOfGold.setDescription("Heart of Gold");
|
||||||
dao.modify(heartOfGold);
|
dao.modify(heartOfGold);
|
||||||
|
|
||||||
@@ -175,50 +193,26 @@ class XmlRepositoryDAOTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
void shouldRemoveRepository() {
|
void shouldRemoveRepository() {
|
||||||
Repository heartOfGold = createHeartOfGold();
|
dao.add(REPOSITORY);
|
||||||
dao.add(heartOfGold);
|
|
||||||
assertThat(dao.contains("42")).isTrue();
|
assertThat(dao.contains("42")).isTrue();
|
||||||
|
|
||||||
dao.delete(heartOfGold);
|
dao.delete(REPOSITORY);
|
||||||
assertThat(dao.contains("42")).isFalse();
|
assertThat(dao.contains("42")).isFalse();
|
||||||
assertThat(dao.contains(new NamespaceAndName("hitchhiker", "HeartOfGold"))).isFalse();
|
assertThat(dao.contains(REPOSITORY.getNamespaceAndName())).isFalse();
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
Path storePath = metadataFile(REPOSITORY.getId());
|
||||||
void shouldUpdateLastModifiedAfterEachWriteOperation() {
|
|
||||||
Repository heartOfGold = createHeartOfGold();
|
|
||||||
dao.add(heartOfGold);
|
|
||||||
|
|
||||||
Long firstLastModified = dao.getLastModified();
|
assertThat(storePath).doesNotExist();
|
||||||
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
|
@Test
|
||||||
void shouldRenameTheRepository() {
|
void shouldRenameTheRepository() {
|
||||||
Repository heartOfGold = createHeartOfGold();
|
dao.add(REPOSITORY);
|
||||||
dao.add(heartOfGold);
|
|
||||||
|
|
||||||
heartOfGold.setNamespace("hg2tg");
|
REPOSITORY.setNamespace("hg2tg");
|
||||||
heartOfGold.setName("hog");
|
REPOSITORY.setName("hog");
|
||||||
|
|
||||||
dao.modify(heartOfGold);
|
dao.modify(REPOSITORY);
|
||||||
|
|
||||||
Repository repository = dao.get("42");
|
Repository repository = dao.get("42");
|
||||||
assertThat(repository.getNamespace()).isEqualTo("hg2tg");
|
assertThat(repository.getNamespace()).isEqualTo("hg2tg");
|
||||||
@@ -226,152 +220,91 @@ class XmlRepositoryDAOTest {
|
|||||||
|
|
||||||
assertThat(dao.contains(new NamespaceAndName("hg2tg", "hog"))).isTrue();
|
assertThat(dao.contains(new NamespaceAndName("hg2tg", "hog"))).isTrue();
|
||||||
assertThat(dao.contains(new NamespaceAndName("hitchhiker", "HeartOfGold"))).isFalse();
|
assertThat(dao.contains(new NamespaceAndName("hitchhiker", "HeartOfGold"))).isFalse();
|
||||||
|
|
||||||
|
String content = getXmlFileContent(REPOSITORY.getId());
|
||||||
|
assertThat(content).contains("<name>hog</name>");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void shouldDeleteRepositoryEvenWithChangedNamespace() {
|
void shouldDeleteRepositoryEvenWithChangedNamespace() {
|
||||||
Repository heartOfGold = createHeartOfGold();
|
dao.add(REPOSITORY);
|
||||||
dao.add(heartOfGold);
|
|
||||||
|
|
||||||
heartOfGold.setNamespace("hg2tg");
|
REPOSITORY.setNamespace("hg2tg");
|
||||||
heartOfGold.setName("hog");
|
REPOSITORY.setName("hog");
|
||||||
|
|
||||||
dao.delete(heartOfGold);
|
dao.delete(REPOSITORY);
|
||||||
|
|
||||||
assertThat(dao.contains(new NamespaceAndName("hitchhiker", "HeartOfGold"))).isFalse();
|
assertThat(dao.contains(new NamespaceAndName("space", "42"))).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
|
@Test
|
||||||
void shouldRemoveRepositoryDirectoryAfterDeletion() {
|
void shouldRemoveRepositoryDirectoryAfterDeletion() {
|
||||||
Repository heartOfGold = createHeartOfGold();
|
dao.add(REPOSITORY);
|
||||||
dao.add(heartOfGold);
|
|
||||||
|
|
||||||
Path path = getAbsolutePathFromDao(heartOfGold.getId());
|
Path path = locationResolver.create(REPOSITORY.getId());
|
||||||
assertThat(path).isDirectory();
|
assertThat(path).isDirectory();
|
||||||
|
|
||||||
dao.delete(heartOfGold);
|
dao.delete(REPOSITORY);
|
||||||
assertThat(path).doesNotExist();
|
assertThat(path).doesNotExist();
|
||||||
}
|
}
|
||||||
|
|
||||||
private Path getAbsolutePathFromDao(String id) {
|
|
||||||
return context.resolve(dao.getPath(id));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void shouldCreateRepositoryPathDatabase() throws IOException {
|
void shouldPersistPermissions() {
|
||||||
Repository heartOfGold = createHeartOfGold();
|
REPOSITORY.setPermissions(asList(new RepositoryPermission("trillian", asList("read", "write"), false), new RepositoryPermission("vogons", singletonList("delete"), true)));
|
||||||
dao.add(heartOfGold);
|
dao.add(REPOSITORY);
|
||||||
|
|
||||||
Path storePath = dao.resolveStorePath();
|
String content = getXmlFileContent(REPOSITORY.getId());
|
||||||
assertThat(storePath).isRegularFile();
|
|
||||||
|
|
||||||
String content = content(storePath);
|
|
||||||
|
|
||||||
assertThat(content).contains(heartOfGold.getId());
|
|
||||||
assertThat(content).contains(dao.getPath(heartOfGold.getId()).toString());
|
|
||||||
}
|
|
||||||
|
|
||||||
private String content(Path storePath) throws IOException {
|
|
||||||
return new String(Files.readAllBytes(storePath), Charsets.UTF_8);
|
|
||||||
}
|
|
||||||
|
|
||||||
@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());
|
|
||||||
}
|
|
||||||
|
|
||||||
@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("trillian", "<verb>read</verb>", "<verb>write</verb>");
|
||||||
assertThat(content).containsSubsequence("vogons", "<verb>delete</verb>");
|
assertThat(content).containsSubsequence("vogons", "<verb>delete</verb>");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void shouldReadPathDatabaseAndMetadataOfRepositories() {
|
void shouldUpdateRepositoryPathDatabse() {
|
||||||
Repository heartOfGold = createHeartOfGold();
|
dao.add(REPOSITORY);
|
||||||
dao.add(heartOfGold);
|
|
||||||
|
|
||||||
// reload data
|
verify(locationResolver, never()).updateModificationDate();
|
||||||
dao = createDAO();
|
|
||||||
|
|
||||||
heartOfGold = dao.get("42");
|
dao.modify(REPOSITORY);
|
||||||
assertThat(heartOfGold.getName()).isEqualTo("HeartOfGold");
|
|
||||||
|
|
||||||
Path path = getAbsolutePathFromDao(heartOfGold.getId());
|
verify(locationResolver).updateModificationDate();
|
||||||
assertThat(path).isDirectory();
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void shouldReadCreationTimeAndLastModifedDateFromDatabase() {
|
void shouldReadExistingRepositoriesFromPathDatabase(@TempDirectory.TempDir Path basePath) throws IOException {
|
||||||
Repository heartOfGold = createHeartOfGold();
|
doNothing().when(locationResolver).forAllPaths(forAllCaptor.capture());
|
||||||
dao.add(heartOfGold);
|
XmlRepositoryDAO dao = new XmlRepositoryDAO(locationResolver, fileSystem);
|
||||||
|
|
||||||
Long creationTime = dao.getCreationTime();
|
Path repositoryPath = basePath.resolve("existing");
|
||||||
Long lastModified = dao.getLastModified();
|
Files.createDirectories(repositoryPath);
|
||||||
|
URL metadataUrl = Resources.getResource("sonia/scm/store/repositoryDaoMetadata.xml");
|
||||||
|
Files.copy(metadataUrl.openStream(), repositoryPath.resolve("metadata.xml"));
|
||||||
|
|
||||||
// reload data
|
forAllCaptor.getValue().accept("existing", repositoryPath);
|
||||||
dao = createDAO();
|
|
||||||
|
|
||||||
assertThat(dao.getCreationTime()).isEqualTo(creationTime);
|
assertThat(dao.contains(new NamespaceAndName("space", "existing"))).isTrue();
|
||||||
assertThat(dao.getLastModified()).isEqualTo(lastModified);
|
}
|
||||||
|
|
||||||
|
private String getXmlFileContent(String id) {
|
||||||
|
Path storePath = metadataFile(id);
|
||||||
|
|
||||||
|
assertThat(storePath).isRegularFile();
|
||||||
|
return content(storePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Path metadataFile(String id) {
|
||||||
|
return locationResolver.create(id).resolve("metadata.xml");
|
||||||
|
}
|
||||||
|
|
||||||
|
private String content(Path storePath) {
|
||||||
|
try {
|
||||||
|
return new String(Files.readAllBytes(storePath), Charsets.UTF_8);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private 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) {
|
public RepositoryContext resolve(String[] args) {
|
||||||
NamespaceAndName namespaceAndName = extractNamespaceAndName(args);
|
NamespaceAndName namespaceAndName = extractNamespaceAndName(args);
|
||||||
Repository repository = repositoryManager.get(namespaceAndName);
|
Repository repository = repositoryManager.get(namespaceAndName);
|
||||||
Path path = locationResolver.getPath(repository.getId()).resolve("data");
|
Path path = locationResolver.forClass(Path.class).getLocation(repository.getId()).resolve("data");
|
||||||
return new RepositoryContext(repository, path);
|
return new RepositoryContext(repository, path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package sonia.scm.protocolcommand.git;
|
|||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.junit.jupiter.api.extension.ExtendWith;
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
import org.mockito.Answers;
|
||||||
import org.mockito.InjectMocks;
|
import org.mockito.InjectMocks;
|
||||||
import org.mockito.Mock;
|
import org.mockito.Mock;
|
||||||
import org.mockito.junit.jupiter.MockitoExtension;
|
import org.mockito.junit.jupiter.MockitoExtension;
|
||||||
@@ -16,6 +17,7 @@ import java.io.IOException;
|
|||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
@ExtendWith(MockitoExtension.class)
|
@ExtendWith(MockitoExtension.class)
|
||||||
@@ -25,7 +27,7 @@ class GitRepositoryContextResolverTest {
|
|||||||
|
|
||||||
@Mock
|
@Mock
|
||||||
RepositoryManager repositoryManager;
|
RepositoryManager repositoryManager;
|
||||||
@Mock
|
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
|
||||||
RepositoryLocationResolver locationResolver;
|
RepositoryLocationResolver locationResolver;
|
||||||
|
|
||||||
@InjectMocks
|
@InjectMocks
|
||||||
@@ -35,7 +37,7 @@ class GitRepositoryContextResolverTest {
|
|||||||
void shouldResolveCorrectRepository() throws IOException {
|
void shouldResolveCorrectRepository() throws IOException {
|
||||||
when(repositoryManager.get(new NamespaceAndName("space", "X"))).thenReturn(REPOSITORY);
|
when(repositoryManager.get(new NamespaceAndName("space", "X"))).thenReturn(REPOSITORY);
|
||||||
Path repositoryPath = File.createTempFile("test", "scm").toPath();
|
Path repositoryPath = File.createTempFile("test", "scm").toPath();
|
||||||
when(locationResolver.getPath("id")).thenReturn(repositoryPath);
|
when(locationResolver.forClass(any()).getLocation("id")).thenReturn(repositoryPath);
|
||||||
|
|
||||||
RepositoryContext context = resolver.resolve(new String[] {"git", "repo/space/X/something/else"});
|
RepositoryContext context = resolver.resolve(new String[] {"git", "repo/space/X/something/else"});
|
||||||
|
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ package sonia.scm.repository;
|
|||||||
|
|
||||||
import org.junit.Assume;
|
import org.junit.Assume;
|
||||||
import sonia.scm.SCMContext;
|
import sonia.scm.SCMContext;
|
||||||
|
import sonia.scm.TempDirRepositoryLocationResolver;
|
||||||
import sonia.scm.store.InMemoryConfigurationStoreFactory;
|
import sonia.scm.store.InMemoryConfigurationStoreFactory;
|
||||||
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
@@ -101,13 +102,12 @@ public final class HgTestUtil
|
|||||||
|
|
||||||
context.setBaseDirectory(directory);
|
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 =
|
HgRepositoryHandler handler =
|
||||||
new HgRepositoryHandler(new InMemoryConfigurationStoreFactory(), new HgContextProvider(), repositoryLocationResolver, null, null);
|
new HgRepositoryHandler(new InMemoryConfigurationStoreFactory(), new HgContextProvider(), repositoryLocationResolver, null, null);
|
||||||
Path repoDir = directory.toPath();
|
Path repoDir = directory.toPath();
|
||||||
when(repoDao.getPath(any())).thenReturn(repoDir);
|
|
||||||
handler.init(context);
|
handler.init(context);
|
||||||
|
|
||||||
return handler;
|
return handler;
|
||||||
|
|||||||
@@ -91,7 +91,7 @@ public class AbstractTestBase
|
|||||||
contextProvider = MockUtil.getSCMContextProvider(tempDirectory);
|
contextProvider = MockUtil.getSCMContextProvider(tempDirectory);
|
||||||
fileSystem = new DefaultFileSystem();
|
fileSystem = new DefaultFileSystem();
|
||||||
InitialRepositoryLocationResolver initialRepoLocationResolver = new InitialRepositoryLocationResolver();
|
InitialRepositoryLocationResolver initialRepoLocationResolver = new InitialRepositoryLocationResolver();
|
||||||
repositoryLocationResolver = new RepositoryLocationResolver(contextProvider, repositoryDAO, initialRepoLocationResolver);
|
repositoryLocationResolver = new TempDirRepositoryLocationResolver(tempDirectory);
|
||||||
postSetUp();
|
postSetUp();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -254,4 +254,5 @@ public class AbstractTestBase
|
|||||||
subjectThreadState = createThreadState(subject);
|
subjectThreadState = createThreadState(subject);
|
||||||
subjectThreadState.bind();
|
subjectThreadState.bind();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ public abstract class ManagerTestBase<T extends ModelObject>
|
|||||||
contextProvider = MockUtil.getSCMContextProvider(temp);
|
contextProvider = MockUtil.getSCMContextProvider(temp);
|
||||||
InitialRepositoryLocationResolver initialRepositoryLocationResolver = new InitialRepositoryLocationResolver();
|
InitialRepositoryLocationResolver initialRepositoryLocationResolver = new InitialRepositoryLocationResolver();
|
||||||
RepositoryDAO repoDao = mock(RepositoryDAO.class);
|
RepositoryDAO repoDao = mock(RepositoryDAO.class);
|
||||||
locationResolver = new RepositoryLocationResolver(contextProvider, repoDao ,initialRepositoryLocationResolver);
|
locationResolver = new TempDirRepositoryLocationResolver(temp);
|
||||||
manager = createManager();
|
manager = createManager();
|
||||||
manager.init(contextProvider);
|
manager.init(contextProvider);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 java.nio.file.Path;
|
||||||
|
|
||||||
import static org.junit.Assert.assertTrue;
|
import static org.junit.Assert.assertTrue;
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
import static org.mockito.ArgumentMatchers.anyString;
|
import static org.mockito.ArgumentMatchers.anyString;
|
||||||
import static org.mockito.Mockito.mock;
|
import static org.mockito.Mockito.mock;
|
||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
@@ -56,7 +57,7 @@ import static org.mockito.Mockito.when;
|
|||||||
public abstract class SimpleRepositoryHandlerTestBase extends AbstractTestBase {
|
public abstract class SimpleRepositoryHandlerTestBase extends AbstractTestBase {
|
||||||
|
|
||||||
|
|
||||||
protected PathBasedRepositoryDAO repoDao = mock(PathBasedRepositoryDAO.class);
|
protected RepositoryDAO repoDao = mock(RepositoryDAO.class);
|
||||||
protected Path repoPath;
|
protected Path repoPath;
|
||||||
protected Repository repository;
|
protected Repository repository;
|
||||||
|
|
||||||
@@ -78,7 +79,11 @@ public abstract class SimpleRepositoryHandlerTestBase extends AbstractTestBase {
|
|||||||
|
|
||||||
locationResolver = mock(RepositoryLocationResolver.class);
|
locationResolver = mock(RepositoryLocationResolver.class);
|
||||||
|
|
||||||
when(locationResolver.getPath(anyString())).then(ic -> {
|
RepositoryLocationResolver.RepositoryLocationResolverInstance instanceMock = mock(RepositoryLocationResolver.RepositoryLocationResolverInstance.class);
|
||||||
|
when(locationResolver.create(any())).thenReturn(instanceMock);
|
||||||
|
when(locationResolver.supportsLocationType(any())).thenReturn(true);
|
||||||
|
|
||||||
|
when(instanceMock.getLocation(anyString())).then(ic -> {
|
||||||
String id = ic.getArgument(0);
|
String id = ic.getArgument(0);
|
||||||
return baseDirectory.toPath().resolve(id);
|
return baseDirectory.toPath().resolve(id);
|
||||||
});
|
});
|
||||||
@@ -107,7 +112,7 @@ public abstract class SimpleRepositoryHandlerTestBase extends AbstractTestBase {
|
|||||||
repository = RepositoryTestData.createHeartOfGold();
|
repository = RepositoryTestData.createHeartOfGold();
|
||||||
File repoDirectory = new File(baseDirectory, repository.getId());
|
File repoDirectory = new File(baseDirectory, repository.getId());
|
||||||
repoPath = repoDirectory.toPath();
|
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);
|
return new File(repoDirectory, AbstractSimpleRepositoryHandler.REPOSITORIES_NATIVE_DIRECTORY);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -184,6 +184,12 @@
|
|||||||
<version>${guice.version}</version>
|
<version>${guice.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.google.inject.extensions</groupId>
|
||||||
|
<artifactId>guice-assistedinject</artifactId>
|
||||||
|
<version>${guice.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<!-- event bus -->
|
<!-- event bus -->
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ import com.google.common.base.Throwables;
|
|||||||
import com.google.common.collect.Lists;
|
import com.google.common.collect.Lists;
|
||||||
import com.google.inject.Injector;
|
import com.google.inject.Injector;
|
||||||
import com.google.inject.Module;
|
import com.google.inject.Module;
|
||||||
|
import com.google.inject.assistedinject.Assisted;
|
||||||
import org.apache.shiro.guice.web.ShiroWebModule;
|
import org.apache.shiro.guice.web.ShiroWebModule;
|
||||||
import org.jboss.resteasy.plugins.guice.GuiceResteasyBootstrapServletContextListener;
|
import org.jboss.resteasy.plugins.guice.GuiceResteasyBootstrapServletContextListener;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
@@ -55,6 +56,7 @@ import sonia.scm.upgrade.UpgradeManager;
|
|||||||
import sonia.scm.user.UserManager;
|
import sonia.scm.user.UserManager;
|
||||||
import sonia.scm.util.IOUtil;
|
import sonia.scm.util.IOUtil;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
import javax.servlet.ServletContext;
|
import javax.servlet.ServletContext;
|
||||||
import javax.servlet.ServletContextEvent;
|
import javax.servlet.ServletContextEvent;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
@@ -77,9 +79,12 @@ public class ScmContextListener extends GuiceResteasyBootstrapServletContextList
|
|||||||
private final Set<PluginWrapper> plugins;
|
private final Set<PluginWrapper> plugins;
|
||||||
private Injector injector;
|
private Injector injector;
|
||||||
|
|
||||||
//~--- constructors ---------------------------------------------------------
|
public interface Factory {
|
||||||
|
ScmContextListener create(ClassLoader parent, Set<PluginWrapper> plugins);
|
||||||
|
}
|
||||||
|
|
||||||
public ScmContextListener(ClassLoader parent, Set<PluginWrapper> plugins)
|
@Inject
|
||||||
|
public ScmContextListener(@Assisted ClassLoader parent, @Assisted Set<PluginWrapper> plugins)
|
||||||
{
|
{
|
||||||
this.parent = parent;
|
this.parent = parent;
|
||||||
this.plugins = plugins;
|
this.plugins = plugins;
|
||||||
@@ -127,9 +132,6 @@ public class ScmContextListener extends GuiceResteasyBootstrapServletContextList
|
|||||||
List<Module> moduleList = Lists.newArrayList();
|
List<Module> moduleList = Lists.newArrayList();
|
||||||
|
|
||||||
moduleList.add(new ResteasyModule());
|
moduleList.add(new ResteasyModule());
|
||||||
moduleList.add(new ScmInitializerModule());
|
|
||||||
moduleList.add(new ScmEventBusModule());
|
|
||||||
moduleList.add(new EagerSingletonModule());
|
|
||||||
moduleList.add(ShiroWebModule.guiceFilterModule());
|
moduleList.add(ShiroWebModule.guiceFilterModule());
|
||||||
moduleList.add(new WebElementModule(pluginLoader));
|
moduleList.add(new WebElementModule(pluginLoader));
|
||||||
moduleList.add(new ScmServletModule(context, pluginLoader, overrides));
|
moduleList.add(new ScmServletModule(context, pluginLoader, overrides));
|
||||||
|
|||||||
@@ -212,12 +212,7 @@ public class ScmServletModule extends ServletModule
|
|||||||
{
|
{
|
||||||
install(ThrowingProviderBinder.forModule(this));
|
install(ThrowingProviderBinder.forModule(this));
|
||||||
|
|
||||||
SCMContextProvider context = SCMContext.getContext();
|
|
||||||
|
|
||||||
bind(SCMContextProvider.class).toInstance(context);
|
|
||||||
|
|
||||||
ScmConfiguration config = getScmConfiguration();
|
ScmConfiguration config = getScmConfiguration();
|
||||||
CipherUtil cu = CipherUtil.getInstance();
|
|
||||||
|
|
||||||
bind(NamespaceStrategy.class).toProvider(NamespaceStrategyProvider.class);
|
bind(NamespaceStrategy.class).toProvider(NamespaceStrategyProvider.class);
|
||||||
|
|
||||||
@@ -234,22 +229,12 @@ public class ScmServletModule extends ServletModule
|
|||||||
bind(ScmEventBus.class).toInstance(ScmEventBus.getInstance());
|
bind(ScmEventBus.class).toInstance(ScmEventBus.getInstance());
|
||||||
|
|
||||||
// bind core
|
// 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(ScmConfiguration.class).toInstance(config);
|
||||||
bind(PluginLoader.class).toInstance(pluginLoader);
|
|
||||||
bind(PluginManager.class, DefaultPluginManager.class);
|
bind(PluginManager.class, DefaultPluginManager.class);
|
||||||
|
|
||||||
// bind scheduler
|
// bind scheduler
|
||||||
bind(Scheduler.class).to(QuartzScheduler.class);
|
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 health check stuff
|
||||||
bind(HealthCheckContextListener.class);
|
bind(HealthCheckContextListener.class);
|
||||||
|
|
||||||
@@ -327,7 +312,6 @@ public class ScmServletModule extends ServletModule
|
|||||||
bind(ObjectMapper.class).toProvider(ObjectMapperProvider.class);
|
bind(ObjectMapper.class).toProvider(ObjectMapperProvider.class);
|
||||||
|
|
||||||
// bind events
|
// bind events
|
||||||
// bind(LastModifiedUpdateListener.class);
|
|
||||||
|
|
||||||
bind(AccessTokenCookieIssuer.class).to(DefaultAccessTokenCookieIssuer.class);
|
bind(AccessTokenCookieIssuer.class).to(DefaultAccessTokenCookieIssuer.class);
|
||||||
bind(PushStateDispatcher.class).toProvider(PushStateDispatcherProvider.class);
|
bind(PushStateDispatcher.class).toProvider(PushStateDispatcherProvider.class);
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
/**
|
/**
|
||||||
* Copyright (c) 2010, Sebastian Sdorra All rights reserved.
|
* Copyright (c) 2010, Sebastian Sdorra All rights reserved.
|
||||||
*
|
* <p>
|
||||||
* Redistribution and use in source and binary forms, with or without
|
* Redistribution and use in source and binary forms, with or without
|
||||||
* modification, are permitted provided that the following conditions are met:
|
* modification, are permitted provided that the following conditions are met:
|
||||||
*
|
* <p>
|
||||||
* 1. Redistributions of source code must retain the above copyright notice,
|
* 1. Redistributions of source code must retain the above copyright notice,
|
||||||
* this list of conditions and the following disclaimer. 2. Redistributions in
|
* this list of conditions and the following disclaimer. 2. Redistributions in
|
||||||
* binary form must reproduce the above copyright notice, this list of
|
* 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;
|
* 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
|
* nor the names of its contributors may be used to endorse or promote products
|
||||||
* derived from this software without specific prior written permission.
|
* derived from this software without specific prior written permission.
|
||||||
*
|
* <p>
|
||||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
* 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,
|
* 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
|
* 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.
|
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
*
|
* <p>
|
||||||
* http://bitbucket.org/sdorra/scm-manager
|
* http://bitbucket.org/sdorra/scm-manager
|
||||||
*
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
package sonia.scm.boot;
|
package sonia.scm.boot;
|
||||||
|
|
||||||
//~--- non-JDK imports --------------------------------------------------------
|
|
||||||
|
|
||||||
import com.google.common.base.Charsets;
|
import com.google.common.base.Charsets;
|
||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
import com.google.common.io.Files;
|
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.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
import sonia.scm.EagerSingletonModule;
|
||||||
import sonia.scm.SCMContext;
|
import sonia.scm.SCMContext;
|
||||||
import sonia.scm.ScmContextListener;
|
import sonia.scm.ScmContextListener;
|
||||||
|
import sonia.scm.ScmEventBusModule;
|
||||||
|
import sonia.scm.ScmInitializerModule;
|
||||||
import sonia.scm.Stage;
|
import sonia.scm.Stage;
|
||||||
import sonia.scm.event.ScmEventBus;
|
import sonia.scm.event.ScmEventBus;
|
||||||
|
import sonia.scm.plugin.DefaultPluginLoader;
|
||||||
import sonia.scm.plugin.Plugin;
|
import sonia.scm.plugin.Plugin;
|
||||||
import sonia.scm.plugin.PluginException;
|
import sonia.scm.plugin.PluginException;
|
||||||
import sonia.scm.plugin.PluginLoadException;
|
import sonia.scm.plugin.PluginLoadException;
|
||||||
|
import sonia.scm.plugin.PluginLoader;
|
||||||
import sonia.scm.plugin.PluginWrapper;
|
import sonia.scm.plugin.PluginWrapper;
|
||||||
import sonia.scm.plugin.PluginsInternal;
|
import sonia.scm.plugin.PluginsInternal;
|
||||||
import sonia.scm.plugin.SmpArchive;
|
import sonia.scm.plugin.SmpArchive;
|
||||||
|
import sonia.scm.update.UpdateEngine;
|
||||||
import sonia.scm.util.ClassLoaders;
|
import sonia.scm.util.ClassLoaders;
|
||||||
import sonia.scm.util.IOUtil;
|
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.ServletContext;
|
||||||
import javax.servlet.ServletContextEvent;
|
import javax.servlet.ServletContextEvent;
|
||||||
import javax.servlet.ServletContextListener;
|
import javax.servlet.ServletContextListener;
|
||||||
|
|
||||||
import javax.xml.bind.DataBindingException;
|
import javax.xml.bind.DataBindingException;
|
||||||
import javax.xml.bind.JAXB;
|
import javax.xml.bind.JAXB;
|
||||||
import javax.xml.bind.annotation.XmlAccessType;
|
import javax.xml.bind.annotation.XmlAccessType;
|
||||||
import javax.xml.bind.annotation.XmlAccessorType;
|
import javax.xml.bind.annotation.XmlAccessorType;
|
||||||
import javax.xml.bind.annotation.XmlElement;
|
import javax.xml.bind.annotation.XmlElement;
|
||||||
import javax.xml.bind.annotation.XmlRootElement;
|
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
|
* @author Sebastian Sdorra
|
||||||
*/
|
*/
|
||||||
public class BootstrapContextListener implements ServletContextListener
|
public class BootstrapContextListener implements ServletContextListener {
|
||||||
{
|
|
||||||
|
|
||||||
/** Field description */
|
/** Field description */
|
||||||
private static final String DIRECTORY_PLUGINS = "plugins";
|
private static final String DIRECTORY_PLUGINS = "plugins";
|
||||||
@@ -109,22 +107,16 @@ public class BootstrapContextListener implements ServletContextListener
|
|||||||
* @param sce
|
* @param sce
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void contextDestroyed(ServletContextEvent sce)
|
public void contextDestroyed(ServletContextEvent sce) {
|
||||||
{
|
|
||||||
contextListener.contextDestroyed(sce);
|
contextListener.contextDestroyed(sce);
|
||||||
|
|
||||||
for (PluginWrapper plugin : contextListener.getPlugins())
|
for (PluginWrapper plugin : contextListener.getPlugins()) {
|
||||||
{
|
|
||||||
ClassLoader pcl = plugin.getClassLoader();
|
ClassLoader pcl = plugin.getClassLoader();
|
||||||
|
|
||||||
if (pcl instanceof Closeable)
|
if (pcl instanceof Closeable) {
|
||||||
{
|
try {
|
||||||
try
|
|
||||||
{
|
|
||||||
((Closeable) pcl).close();
|
((Closeable) pcl).close();
|
||||||
}
|
} catch (IOException ex) {
|
||||||
catch (IOException ex)
|
|
||||||
{
|
|
||||||
logger.warn("could not close plugin classloader", ex);
|
logger.warn("could not close plugin classloader", ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -141,43 +133,68 @@ public class BootstrapContextListener implements ServletContextListener
|
|||||||
* @param sce
|
* @param sce
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void contextInitialized(ServletContextEvent sce)
|
public void contextInitialized(ServletContextEvent sce) {
|
||||||
{
|
|
||||||
context = sce.getServletContext();
|
context = sce.getServletContext();
|
||||||
|
|
||||||
File pluginDirectory = getPluginDirectory();
|
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()) {
|
if (!isCorePluginExtractionDisabled()) {
|
||||||
extractCorePlugins(context, pluginDirectory);
|
extractCorePlugins(context, pluginDirectory);
|
||||||
} else {
|
} else {
|
||||||
logger.info("core plugin extraction is disabled");
|
logger.info("core plugin extraction is disabled");
|
||||||
}
|
}
|
||||||
|
|
||||||
ClassLoader cl =
|
ClassLoader cl = ClassLoaders.getContextClassLoader(BootstrapContextListener.class);
|
||||||
ClassLoaders.getContextClassLoader(BootstrapContextListener.class);
|
|
||||||
|
|
||||||
Set<PluginWrapper> plugins = PluginsInternal.collectPlugins(cl,
|
Set<PluginWrapper> plugins = PluginsInternal.collectPlugins(cl, pluginDirectory.toPath());
|
||||||
pluginDirectory.toPath());
|
|
||||||
|
|
||||||
contextListener = new ScmContextListener(cl, plugins);
|
PluginLoader pluginLoader = new DefaultPluginLoader(context, cl, plugins);
|
||||||
}
|
|
||||||
catch (IOException ex)
|
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);
|
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() {
|
private boolean isCorePluginExtractionDisabled() {
|
||||||
@@ -196,8 +213,7 @@ public class BootstrapContextListener implements ServletContextListener
|
|||||||
*/
|
*/
|
||||||
private void extractCorePlugin(ServletContext context, File pluginDirectory,
|
private void extractCorePlugin(ServletContext context, File pluginDirectory,
|
||||||
PluginIndexEntry entry)
|
PluginIndexEntry entry)
|
||||||
throws IOException
|
throws IOException {
|
||||||
{
|
|
||||||
URL url = context.getResource(PLUGIN_DIRECTORY.concat(entry.getName()));
|
URL url = context.getResource(PLUGIN_DIRECTORY.concat(entry.getName()));
|
||||||
SmpArchive archive = SmpArchive.create(url);
|
SmpArchive archive = SmpArchive.create(url);
|
||||||
Plugin plugin = archive.getPlugin();
|
Plugin plugin = archive.getPlugin();
|
||||||
@@ -206,30 +222,22 @@ public class BootstrapContextListener implements ServletContextListener
|
|||||||
plugin);
|
plugin);
|
||||||
File checksumFile = PluginsInternal.getChecksumFile(directory);
|
File checksumFile = PluginsInternal.getChecksumFile(directory);
|
||||||
|
|
||||||
if (!directory.exists())
|
if (!directory.exists()) {
|
||||||
{
|
|
||||||
logger.warn("install plugin {}", plugin.getInformation().getId());
|
logger.warn("install plugin {}", plugin.getInformation().getId());
|
||||||
PluginsInternal.extract(archive, entry.getChecksum(), directory,
|
PluginsInternal.extract(archive, entry.getChecksum(), directory,
|
||||||
checksumFile, true);
|
checksumFile, true);
|
||||||
}
|
} else if (!checksumFile.exists()) {
|
||||||
else if (!checksumFile.exists())
|
|
||||||
{
|
|
||||||
logger.warn("plugin directory {} exists without checksum file.",
|
logger.warn("plugin directory {} exists without checksum file.",
|
||||||
directory);
|
directory);
|
||||||
PluginsInternal.extract(archive, entry.getChecksum(), directory,
|
PluginsInternal.extract(archive, entry.getChecksum(), directory,
|
||||||
checksumFile, true);
|
checksumFile, true);
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
String checksum = Files.toString(checksumFile, Charsets.UTF_8).trim();
|
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",
|
logger.debug("plugin {} is up to date",
|
||||||
plugin.getInformation().getId());
|
plugin.getInformation().getId());
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
logger.warn("checksum mismatch of pluing {}, start update",
|
logger.warn("checksum mismatch of pluing {}, start update",
|
||||||
plugin.getInformation().getId());
|
plugin.getInformation().getId());
|
||||||
PluginsInternal.extract(archive, entry.getChecksum(), directory,
|
PluginsInternal.extract(archive, entry.getChecksum(), directory,
|
||||||
@@ -247,14 +255,12 @@ public class BootstrapContextListener implements ServletContextListener
|
|||||||
*
|
*
|
||||||
* @throws IOException
|
* @throws IOException
|
||||||
*/
|
*/
|
||||||
private void extractCorePlugins(ServletContext context, File pluginDirectory) throws IOException
|
private void extractCorePlugins(ServletContext context, File pluginDirectory) throws IOException {
|
||||||
{
|
|
||||||
IOUtil.mkdirs(pluginDirectory);
|
IOUtil.mkdirs(pluginDirectory);
|
||||||
|
|
||||||
PluginIndex index = readCorePluginIndex(context);
|
PluginIndex index = readCorePluginIndex(context);
|
||||||
|
|
||||||
for (PluginIndexEntry entry : index)
|
for (PluginIndexEntry entry : index) {
|
||||||
{
|
|
||||||
extractCorePlugin(context, pluginDirectory, entry);
|
extractCorePlugin(context, pluginDirectory, entry);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -267,27 +273,20 @@ public class BootstrapContextListener implements ServletContextListener
|
|||||||
*
|
*
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
private PluginIndex readCorePluginIndex(ServletContext context)
|
private PluginIndex readCorePluginIndex(ServletContext context) {
|
||||||
{
|
|
||||||
PluginIndex index = null;
|
PluginIndex index = null;
|
||||||
|
|
||||||
try
|
try {
|
||||||
{
|
|
||||||
URL indexUrl = context.getResource(PLUGIN_COREINDEX);
|
URL indexUrl = context.getResource(PLUGIN_COREINDEX);
|
||||||
|
|
||||||
if (indexUrl == null)
|
if (indexUrl == null) {
|
||||||
{
|
|
||||||
throw new PluginException("no core plugin index found");
|
throw new PluginException("no core plugin index found");
|
||||||
}
|
}
|
||||||
|
|
||||||
index = JAXB.unmarshal(indexUrl, PluginIndex.class);
|
index = JAXB.unmarshal(indexUrl, PluginIndex.class);
|
||||||
}
|
} catch (MalformedURLException ex) {
|
||||||
catch (MalformedURLException ex)
|
|
||||||
{
|
|
||||||
throw new PluginException("could not load core plugin index", 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);
|
throw new PluginException("could not unmarshall core plugin index", ex);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -302,8 +301,7 @@ public class BootstrapContextListener implements ServletContextListener
|
|||||||
*
|
*
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
private File getPluginDirectory()
|
private File getPluginDirectory() {
|
||||||
{
|
|
||||||
File baseDirectory = SCMContext.getContext().getBaseDirectory();
|
File baseDirectory = SCMContext.getContext().getBaseDirectory();
|
||||||
|
|
||||||
return new File(baseDirectory, DIRECTORY_PLUGINS);
|
return new File(baseDirectory, DIRECTORY_PLUGINS);
|
||||||
@@ -320,8 +318,7 @@ public class BootstrapContextListener implements ServletContextListener
|
|||||||
*/
|
*/
|
||||||
@XmlAccessorType(XmlAccessType.FIELD)
|
@XmlAccessorType(XmlAccessType.FIELD)
|
||||||
@XmlRootElement(name = "plugin-index")
|
@XmlRootElement(name = "plugin-index")
|
||||||
private static class PluginIndex implements Iterable<PluginIndexEntry>
|
private static class PluginIndex implements Iterable<PluginIndexEntry> {
|
||||||
{
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Method description
|
* Method description
|
||||||
@@ -330,8 +327,7 @@ public class BootstrapContextListener implements ServletContextListener
|
|||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public Iterator<PluginIndexEntry> iterator()
|
public Iterator<PluginIndexEntry> iterator() {
|
||||||
{
|
|
||||||
return getPlugins().iterator();
|
return getPlugins().iterator();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -343,10 +339,8 @@ public class BootstrapContextListener implements ServletContextListener
|
|||||||
*
|
*
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
public List<PluginIndexEntry> getPlugins()
|
public List<PluginIndexEntry> getPlugins() {
|
||||||
{
|
if (plugins == null) {
|
||||||
if (plugins == null)
|
|
||||||
{
|
|
||||||
plugins = ImmutableList.of();
|
plugins = ImmutableList.of();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -370,8 +364,7 @@ public class BootstrapContextListener implements ServletContextListener
|
|||||||
*/
|
*/
|
||||||
@XmlRootElement(name = "plugins")
|
@XmlRootElement(name = "plugins")
|
||||||
@XmlAccessorType(XmlAccessType.FIELD)
|
@XmlAccessorType(XmlAccessType.FIELD)
|
||||||
private static class PluginIndexEntry
|
private static class PluginIndexEntry {
|
||||||
{
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Method description
|
* Method description
|
||||||
@@ -379,8 +372,7 @@ public class BootstrapContextListener implements ServletContextListener
|
|||||||
*
|
*
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
public String getChecksum()
|
public String getChecksum() {
|
||||||
{
|
|
||||||
return checksum;
|
return checksum;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -390,8 +382,7 @@ public class BootstrapContextListener implements ServletContextListener
|
|||||||
*
|
*
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
public String getName()
|
public String getName() {
|
||||||
{
|
|
||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -415,4 +406,11 @@ public class BootstrapContextListener implements ServletContextListener
|
|||||||
|
|
||||||
/** Field description */
|
/** Field description */
|
||||||
private boolean registered = false;
|
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.HookContextFactory;
|
||||||
import sonia.scm.repository.api.HookFeature;
|
import sonia.scm.repository.api.HookFeature;
|
||||||
import sonia.scm.repository.spi.HookContextProvider;
|
import sonia.scm.repository.spi.HookContextProvider;
|
||||||
|
import sonia.scm.repository.xml.PathBasedRepositoryLocationResolver;
|
||||||
import sonia.scm.repository.xml.XmlRepositoryDAO;
|
import sonia.scm.repository.xml.XmlRepositoryDAO;
|
||||||
import sonia.scm.security.DefaultKeyGenerator;
|
import sonia.scm.security.DefaultKeyGenerator;
|
||||||
import sonia.scm.security.KeyGenerator;
|
import sonia.scm.security.KeyGenerator;
|
||||||
import sonia.scm.store.ConfigurationStoreFactory;
|
import sonia.scm.store.ConfigurationStoreFactory;
|
||||||
import sonia.scm.store.JAXBConfigurationStoreFactory;
|
import sonia.scm.store.JAXBConfigurationStoreFactory;
|
||||||
|
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.Paths;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
@@ -86,451 +89,447 @@ import static org.mockito.Mockito.*;
|
|||||||
password = "secret",
|
password = "secret",
|
||||||
configuration = "classpath:sonia/scm/repository/shiro.ini"
|
configuration = "classpath:sonia/scm/repository/shiro.ini"
|
||||||
)
|
)
|
||||||
public class DefaultRepositoryManagerTest extends ManagerTestBase<Repository> {
|
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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// {
|
||||||
|
// 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