mirror of
https://github.com/scm-manager/scm-manager.git
synced 2025-11-14 09:25:43 +01:00
Merged in feature/updatestep_api_for_plugins (pull request #273)
Feature/updatestep api for plugins
This commit is contained in:
@@ -82,7 +82,7 @@ public interface BlobStoreFactory {
|
||||
final class FloatingStoreParameters implements StoreParameters {
|
||||
|
||||
private String name;
|
||||
private Repository repository;
|
||||
private String repositoryId;
|
||||
|
||||
private final BlobStoreFactory factory;
|
||||
|
||||
@@ -96,8 +96,8 @@ final class FloatingStoreParameters implements StoreParameters {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Repository getRepository() {
|
||||
return repository;
|
||||
public String getRepositoryId() {
|
||||
return repositoryId;
|
||||
}
|
||||
|
||||
public class Builder {
|
||||
@@ -113,7 +113,18 @@ final class FloatingStoreParameters implements StoreParameters {
|
||||
* @return Floating API to finish the call.
|
||||
*/
|
||||
public FloatingStoreParameters.Builder forRepository(Repository repository) {
|
||||
FloatingStoreParameters.this.repository = repository;
|
||||
FloatingStoreParameters.this.repositoryId = repository.getId();
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Use this to create or get a {@link BlobStore} for a specific repository. This step is optional. If you want to
|
||||
* have a global {@link BlobStore}, omit this.
|
||||
* @param repositoryId The id of the optional repository for the {@link BlobStore}.
|
||||
* @return Floating API to finish the call.
|
||||
*/
|
||||
public FloatingStoreParameters.Builder forRepository(String repositoryId) {
|
||||
FloatingStoreParameters.this.repositoryId = repositoryId;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
@@ -120,7 +120,18 @@ final class TypedFloatingConfigurationEntryStoreParameters<T> {
|
||||
* @return Floating API to finish the call.
|
||||
*/
|
||||
public OptionalRepositoryBuilder forRepository(Repository repository) {
|
||||
parameters.setRepository(repository);
|
||||
parameters.setRepositoryId(repository.getId());
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Use this to create or get a {@link ConfigurationEntryStore} for a specific repository. This step is optional. If
|
||||
* you want to have a global {@link ConfigurationEntryStore}, omit this.
|
||||
* @param repositoryId The id of the optional repository for the {@link ConfigurationEntryStore}.
|
||||
* @return Floating API to finish the call.
|
||||
*/
|
||||
public OptionalRepositoryBuilder forRepository(String repositoryId) {
|
||||
parameters.setRepositoryId(repositoryId);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
@@ -120,7 +120,18 @@ final class TypedFloatingConfigurationStoreParameters<T> {
|
||||
* @return Floating API to finish the call.
|
||||
*/
|
||||
public OptionalRepositoryBuilder forRepository(Repository repository) {
|
||||
parameters.setRepository(repository);
|
||||
parameters.setRepositoryId(repository.getId());
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Use this to create or get a {@link ConfigurationStore} for a specific repository. This step is optional. If you
|
||||
* want to have a global {@link ConfigurationStore}, omit this.
|
||||
* @param repositoryId The id of the optional repository for the {@link ConfigurationStore}.
|
||||
* @return Floating API to finish the call.
|
||||
*/
|
||||
public OptionalRepositoryBuilder forRepository(String repositoryId) {
|
||||
parameters.setRepositoryId(repositoryId);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
@@ -117,7 +117,18 @@ final class TypedFloatingDataStoreParameters<T> {
|
||||
* @return Floating API to finish the call.
|
||||
*/
|
||||
public OptionalRepositoryBuilder forRepository(Repository repository) {
|
||||
parameters.setRepository(repository);
|
||||
parameters.setRepositoryId(repository.getId());
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Use this to create or get a {@link DataStore} for a specific repository. This step is optional. If you
|
||||
* want to have a global {@link DataStore}, omit this.
|
||||
* @param repositoryId The id of the optional repository for the {@link DataStore}.
|
||||
* @return Floating API to finish the call.
|
||||
*/
|
||||
public OptionalRepositoryBuilder forRepository(String repositoryId) {
|
||||
parameters.setRepositoryId(repositoryId);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
@@ -12,5 +12,5 @@ public interface StoreParameters {
|
||||
|
||||
String getName();
|
||||
|
||||
Repository getRepository();
|
||||
String getRepositoryId();
|
||||
}
|
||||
|
||||
@@ -15,5 +15,5 @@ public interface TypedStoreParameters<T> {
|
||||
|
||||
String getName();
|
||||
|
||||
Repository getRepository();
|
||||
String getRepositoryId();
|
||||
}
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
package sonia.scm.store;
|
||||
|
||||
import sonia.scm.repository.Repository;
|
||||
|
||||
class TypedStoreParametersImpl<T> implements TypedStoreParameters<T> {
|
||||
private Class<T> type;
|
||||
private String name;
|
||||
private Repository repository;
|
||||
private String repositoryId;
|
||||
|
||||
@Override
|
||||
public Class<T> getType() {
|
||||
@@ -26,11 +24,11 @@ class TypedStoreParametersImpl<T> implements TypedStoreParameters<T> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Repository getRepository() {
|
||||
return repository;
|
||||
public String getRepositoryId() {
|
||||
return repositoryId;
|
||||
}
|
||||
|
||||
void setRepository(Repository repository) {
|
||||
this.repository = repository;
|
||||
void setRepositoryId(String repositoryId) {
|
||||
this.repositoryId = repositoryId;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
package sonia.scm.update;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
public class GroupV1PropertyReader implements V1PropertyReader {
|
||||
@Override
|
||||
public String getStoreName() {
|
||||
return "group-properties-v1";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Instance createInstance(Map<String, V1Properties> all) {
|
||||
return new MapBasedPropertyReaderInstance(all);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package sonia.scm.update;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public class MapBasedPropertyReaderInstance implements V1PropertyReader.Instance {
|
||||
private final Stream<Map.Entry<String, V1Properties>> all;
|
||||
|
||||
public MapBasedPropertyReaderInstance(Map<String, V1Properties> all) {
|
||||
this(all.entrySet().stream());
|
||||
}
|
||||
|
||||
private MapBasedPropertyReaderInstance(Stream<Map.Entry<String, V1Properties>> all) {
|
||||
this.all = all;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void forEachEntry(BiConsumer<String, V1Properties> propertiesForNameConsumer) {
|
||||
all.forEach(e -> call(e.getKey(), e.getValue(), propertiesForNameConsumer));
|
||||
}
|
||||
|
||||
@Override
|
||||
public V1PropertyReader.Instance havingAnyOf(String... keys) {
|
||||
return new MapBasedPropertyReaderInstance(all.filter(e -> e.getValue().hasAny(keys)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public V1PropertyReader.Instance havingAllOf(String... keys) {
|
||||
return new MapBasedPropertyReaderInstance(all.filter(e -> e.getValue().hasAll(keys)));
|
||||
}
|
||||
|
||||
private void call(String repositoryId, V1Properties properties, BiConsumer<String, V1Properties> propertiesForNameConsumer) {
|
||||
propertiesForNameConsumer.accept(repositoryId, properties);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package sonia.scm.update;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
|
||||
public interface PropertyFileAccess {
|
||||
|
||||
/**
|
||||
* Use this to rename a configuration file.
|
||||
* @param oldName The old file name.
|
||||
* @return Object to specify the new file name.
|
||||
*/
|
||||
Target renameGlobalConfigurationFrom(String oldName);
|
||||
|
||||
interface Target {
|
||||
/**
|
||||
* Renames a file to the new name given here.
|
||||
* @param newName The new file name.
|
||||
* @throws IOException If the file could not be renamed.
|
||||
*/
|
||||
void to(String newName) throws IOException;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a tool object for store migration from v1 to v2.
|
||||
* @param name The name of the store to be handled.
|
||||
* @return The tool object for the named store.
|
||||
*/
|
||||
StoreFileTools forStoreName(String name);
|
||||
|
||||
interface StoreFileTools {
|
||||
/**
|
||||
* Iterates over all found store files (that is, files ending with ".xml") and calls the
|
||||
* given consumer for each file, giving the file and the name of the data file.
|
||||
* @param storeFileConsumer This consumer will be called for each file found.
|
||||
* @throws IOException May be thrown when an exception occurs.
|
||||
*/
|
||||
void forStoreFiles(FileConsumer storeFileConsumer) throws IOException;
|
||||
|
||||
/**
|
||||
* Moves a data file to the new location for a repository with the given id. If there
|
||||
* is no directory for the given repository id, nothing will be done.
|
||||
* @param storeFile The name of the store file.
|
||||
* @param repositoryId The id of the repository as the new target for the store file.
|
||||
* @throws IOException When the file could not be moved.
|
||||
*/
|
||||
void moveAsRepositoryStore(Path storeFile, String repositoryId) throws IOException;
|
||||
}
|
||||
|
||||
interface FileConsumer {
|
||||
void accept(Path file, String storeName) throws IOException;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package sonia.scm.update;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
public class RepositoryV1PropertyReader implements V1PropertyReader {
|
||||
@Override
|
||||
public String getStoreName() {
|
||||
return "repository-properties-v1";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Instance createInstance(Map<String, V1Properties> all) {
|
||||
return new MapBasedPropertyReaderInstance(all);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package sonia.scm.update;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
public class UserV1PropertyReader implements V1PropertyReader {
|
||||
@Override
|
||||
public String getStoreName() {
|
||||
return "user-properties-v1";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Instance createInstance(Map<String, V1Properties> all) {
|
||||
return new MapBasedPropertyReaderInstance(all);
|
||||
}
|
||||
}
|
||||
59
scm-core/src/main/java/sonia/scm/update/V1Properties.java
Normal file
59
scm-core/src/main/java/sonia/scm/update/V1Properties.java
Normal file
@@ -0,0 +1,59 @@
|
||||
package sonia.scm.update;
|
||||
|
||||
import javax.xml.bind.annotation.XmlAccessType;
|
||||
import javax.xml.bind.annotation.XmlAccessorType;
|
||||
import javax.xml.bind.annotation.XmlElement;
|
||||
import javax.xml.bind.annotation.XmlRootElement;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static java.util.Arrays.asList;
|
||||
import static java.util.Arrays.stream;
|
||||
import static java.util.stream.Stream.empty;
|
||||
|
||||
@XmlAccessorType(XmlAccessType.FIELD)
|
||||
@XmlRootElement(name = "properties")
|
||||
public class V1Properties {
|
||||
@XmlElement(name = "item")
|
||||
private List<V1Property> properties;
|
||||
|
||||
public V1Properties() {
|
||||
}
|
||||
|
||||
public V1Properties(V1Property... properties) {
|
||||
this(asList(properties));
|
||||
}
|
||||
|
||||
public V1Properties(List<V1Property> properties) {
|
||||
this.properties = properties;
|
||||
}
|
||||
|
||||
public String get(String key) {
|
||||
return getOptional(key).orElse(null);
|
||||
}
|
||||
|
||||
public Optional<String> getOptional(String key) {
|
||||
return streamProps().filter(p -> key.equals(p.getKey())).map(V1Property::getValue).findFirst();
|
||||
}
|
||||
|
||||
public Optional<Boolean> getBoolean(String key) {
|
||||
return getOptional(key).map(Boolean::valueOf);
|
||||
}
|
||||
|
||||
public <T extends Enum<T>> Optional<T> getEnum(String key, Class<T> enumType) {
|
||||
return getOptional(key).map(name -> Enum.valueOf(enumType, name));
|
||||
}
|
||||
|
||||
public boolean hasAny(String[] keys) {
|
||||
return streamProps().anyMatch(p -> stream(keys).anyMatch(k -> k.equals(p.getKey())));
|
||||
}
|
||||
|
||||
public boolean hasAll(String[] keys) {
|
||||
return stream(keys).allMatch(k -> streamProps().anyMatch(p -> k.equals(p.getKey())));
|
||||
}
|
||||
|
||||
private Stream<V1Property> streamProps() {
|
||||
return properties == null? empty(): properties.stream();
|
||||
}
|
||||
}
|
||||
49
scm-core/src/main/java/sonia/scm/update/V1Property.java
Normal file
49
scm-core/src/main/java/sonia/scm/update/V1Property.java
Normal file
@@ -0,0 +1,49 @@
|
||||
package sonia.scm.update;
|
||||
|
||||
import javax.xml.bind.annotation.XmlAccessType;
|
||||
import javax.xml.bind.annotation.XmlAccessorType;
|
||||
import java.util.Objects;
|
||||
|
||||
@XmlAccessorType(XmlAccessType.FIELD)
|
||||
public class V1Property {
|
||||
private String key;
|
||||
private String value;
|
||||
|
||||
public V1Property() {
|
||||
}
|
||||
|
||||
public V1Property(String key, String value) {
|
||||
this.key = key;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public String getKey() {
|
||||
return key;
|
||||
}
|
||||
|
||||
public String getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
V1Property that = (V1Property) o;
|
||||
return Objects.equals(key, that.key) &&
|
||||
Objects.equals(value, that.value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(key, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "V1Property{" +
|
||||
"key='" + key + '\'' +
|
||||
", value='" + value + '\'' +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
14
scm-core/src/main/java/sonia/scm/update/V1PropertyDAO.java
Normal file
14
scm-core/src/main/java/sonia/scm/update/V1PropertyDAO.java
Normal file
@@ -0,0 +1,14 @@
|
||||
package sonia.scm.update;
|
||||
|
||||
/**
|
||||
* Use this to access old properties from an instance of SCM-Manager v1.
|
||||
*/
|
||||
public interface V1PropertyDAO {
|
||||
/**
|
||||
* Creates an instance of a property reader to process old properties.
|
||||
* @param reader The reader for the origin of the properties (for example
|
||||
* {@link V1PropertyReader#REPOSITORY_PROPERTY_READER} for properties of repositories).
|
||||
* @return The reader instance.
|
||||
*/
|
||||
V1PropertyReader.Instance getProperties(V1PropertyReader reader);
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package sonia.scm.update;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.function.BiConsumer;
|
||||
|
||||
public interface V1PropertyReader {
|
||||
|
||||
V1PropertyReader REPOSITORY_PROPERTY_READER = new RepositoryV1PropertyReader();
|
||||
V1PropertyReader USER_PROPERTY_READER = new UserV1PropertyReader();
|
||||
V1PropertyReader GROUP_PROPERTY_READER = new GroupV1PropertyReader();
|
||||
|
||||
String getStoreName();
|
||||
|
||||
Instance createInstance(Map<String, V1Properties> all);
|
||||
|
||||
interface Instance {
|
||||
/**
|
||||
* Will call the given consumer for each id of the corresponding entity with its list of
|
||||
* properties converted from v1.
|
||||
* For example for repositories this will call the consumer with the id of each repository
|
||||
* that had properties attached in v1.
|
||||
*/
|
||||
void forEachEntry(BiConsumer<String, V1Properties> propertiesForNameConsumer);
|
||||
|
||||
/**
|
||||
* Filters for entities only having at least one property with a given key name.
|
||||
*/
|
||||
Instance havingAnyOf(String... keys);
|
||||
|
||||
/**
|
||||
* Filters for entities only having properties for all given key name.
|
||||
*/
|
||||
Instance havingAllOf(String... keys);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
package sonia.scm.update;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import org.assertj.core.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.function.BiConsumer;
|
||||
|
||||
class MapBasedPropertyReaderInstanceTest {
|
||||
|
||||
Map<String, V1Properties> executedCalls = new HashMap<>();
|
||||
|
||||
BiConsumer<String, V1Properties> consumer = (key, properties) -> executedCalls.put(key, properties);
|
||||
|
||||
MapBasedPropertyReaderInstance instance = new MapBasedPropertyReaderInstance(
|
||||
ImmutableMap.of(
|
||||
"o1", new V1Properties(
|
||||
new V1Property("k1", "v1-1"),
|
||||
new V1Property("k2", "v1-2"),
|
||||
new V1Property("k3", "v1-3")
|
||||
),
|
||||
"o2", new V1Properties(
|
||||
new V1Property("k1", "v2-1"),
|
||||
new V1Property("k2", "v2-2")
|
||||
),
|
||||
"o3", new V1Properties(
|
||||
new V1Property("k1", "v3-1")
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
@Test
|
||||
void shouldCallBackForEachObjectIfNotFiltered() {
|
||||
instance.forEachEntry(consumer);
|
||||
|
||||
Assertions.assertThat(executedCalls).hasSize(3);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldCallBackOnlyObjectsHavingAtLeastOneOfGivenKey() {
|
||||
instance.havingAnyOf("k2", "k3").forEachEntry(consumer);
|
||||
|
||||
Assertions.assertThat(executedCalls).hasSize(2).containsKeys("o1", "o2");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldCallBackOnlyObjectsHavingAllOfGivenKey() {
|
||||
instance.havingAllOf("k2", "k3").forEachEntry(consumer);
|
||||
|
||||
Assertions.assertThat(executedCalls).hasSize(1).containsKeys("o1");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldCombineFilters() {
|
||||
instance.havingAnyOf("k2", "k3").havingAllOf("k3").forEachEntry(consumer);
|
||||
|
||||
Assertions.assertThat(executedCalls).hasSize(1).containsKeys("o1");
|
||||
}
|
||||
}
|
||||
@@ -35,7 +35,6 @@ package sonia.scm.store;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import sonia.scm.SCMContextProvider;
|
||||
import sonia.scm.repository.Repository;
|
||||
import sonia.scm.repository.RepositoryLocationResolver;
|
||||
import sonia.scm.util.IOUtil;
|
||||
|
||||
@@ -66,18 +65,18 @@ public abstract class FileBasedStoreFactory {
|
||||
}
|
||||
|
||||
protected File getStoreLocation(StoreParameters storeParameters) {
|
||||
return getStoreLocation(storeParameters.getName(), null, storeParameters.getRepository());
|
||||
return getStoreLocation(storeParameters.getName(), null, storeParameters.getRepositoryId());
|
||||
}
|
||||
|
||||
protected File getStoreLocation(TypedStoreParameters storeParameters) {
|
||||
return getStoreLocation(storeParameters.getName(), storeParameters.getType(), storeParameters.getRepository());
|
||||
return getStoreLocation(storeParameters.getName(), storeParameters.getType(), storeParameters.getRepositoryId());
|
||||
}
|
||||
|
||||
protected File getStoreLocation(String name, Class type, Repository repository) {
|
||||
protected File getStoreLocation(String name, Class type, String repositoryId) {
|
||||
File storeDirectory;
|
||||
if (repository != null) {
|
||||
LOG.debug("create store with type: {}, name: {} and repository: {}", type, name, repository.getNamespaceAndName());
|
||||
storeDirectory = this.getStoreDirectory(store, repository);
|
||||
if (repositoryId != null) {
|
||||
LOG.debug("create store with type: {}, name: {} and repository id: {}", type, name, repositoryId);
|
||||
storeDirectory = this.getStoreDirectory(store, repositoryId);
|
||||
} else {
|
||||
LOG.debug("create store with type: {} and name: {} ", type, name);
|
||||
storeDirectory = this.getStoreDirectory(store);
|
||||
@@ -89,11 +88,11 @@ public abstract class FileBasedStoreFactory {
|
||||
/**
|
||||
* Get the store directory of a specific repository
|
||||
* @param store the type of the store
|
||||
* @param repository the repo
|
||||
* @param repositoryId the id of the repossitory
|
||||
* @return the store directory of a specific repository
|
||||
*/
|
||||
private File getStoreDirectory(Store store, Repository repository) {
|
||||
return new File(repositoryLocationResolver.forClass(Path.class).getLocation(repository.getId()).toFile(), store.getRepositoryStoreDirectory());
|
||||
private File getStoreDirectory(Store store, String repositoryId) {
|
||||
return new File(repositoryLocationResolver.forClass(Path.class).getLocation(repositoryId).toFile(), store.getRepositoryStoreDirectory());
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -59,7 +59,9 @@ public class JAXBConfigurationEntryStoreFactory extends FileBasedStoreFactory
|
||||
|
||||
@Override
|
||||
public <T> ConfigurationEntryStore<T> getStore(TypedStoreParameters<T> storeParameters) {
|
||||
return new JAXBConfigurationEntryStore<>(getStoreLocation(storeParameters.getName().concat(StoreConstants.FILE_EXTENSION), storeParameters.getType(), storeParameters.getRepository()), keyGenerator, storeParameters.getType());
|
||||
return new JAXBConfigurationEntryStore<>(
|
||||
getStoreLocation(storeParameters.getName().concat(StoreConstants.FILE_EXTENSION), storeParameters.getType(), storeParameters.getRepositoryId()),
|
||||
keyGenerator,
|
||||
storeParameters.getType());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -55,6 +55,10 @@ public class JAXBConfigurationStoreFactory extends FileBasedStoreFactory impleme
|
||||
|
||||
@Override
|
||||
public <T> JAXBConfigurationStore<T> getStore(TypedStoreParameters<T> storeParameters) {
|
||||
return new JAXBConfigurationStore<>(storeParameters.getType(), getStoreLocation(storeParameters.getName().concat(StoreConstants.FILE_EXTENSION), storeParameters.getType(), storeParameters.getRepository()));
|
||||
return new JAXBConfigurationStore<>(
|
||||
storeParameters.getType(),
|
||||
getStoreLocation(storeParameters.getName().concat(StoreConstants.FILE_EXTENSION),
|
||||
storeParameters.getType(),
|
||||
storeParameters.getRepositoryId()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
package sonia.scm.store;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import sonia.scm.SCMContextProvider;
|
||||
import sonia.scm.repository.RepositoryLocationResolver;
|
||||
import sonia.scm.update.PropertyFileAccess;
|
||||
import sonia.scm.util.IOUtil;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public class JAXBPropertyFileAccess implements PropertyFileAccess {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(JAXBPropertyFileAccess.class);
|
||||
|
||||
public static final String XML_FILENAME_SUFFIX = ".xml";
|
||||
private final SCMContextProvider contextProvider;
|
||||
private final RepositoryLocationResolver locationResolver;
|
||||
|
||||
@Inject
|
||||
public JAXBPropertyFileAccess(SCMContextProvider contextProvider, RepositoryLocationResolver locationResolver) {
|
||||
this.contextProvider = contextProvider;
|
||||
this.locationResolver = locationResolver;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Target renameGlobalConfigurationFrom(String oldName) {
|
||||
return newName -> {
|
||||
Path configDir = contextProvider.getBaseDirectory().toPath().resolve(StoreConstants.CONFIG_DIRECTORY_NAME);
|
||||
Path oldConfigFile = configDir.resolve(oldName + XML_FILENAME_SUFFIX);
|
||||
Path newConfigFile = configDir.resolve(newName + XML_FILENAME_SUFFIX);
|
||||
Files.move(oldConfigFile, newConfigFile);
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public StoreFileTools forStoreName(String storeName) {
|
||||
return new StoreFileTools() {
|
||||
@Override
|
||||
public void forStoreFiles(FileConsumer storeFileConsumer) throws IOException {
|
||||
Path v1storeDir = computeV1StoreDir();
|
||||
if (Files.exists(v1storeDir) && Files.isDirectory(v1storeDir)) {
|
||||
try (Stream<Path> fileStream = Files.list(v1storeDir)) {
|
||||
fileStream.filter(p -> p.toString().endsWith(XML_FILENAME_SUFFIX)).forEach(p -> {
|
||||
try {
|
||||
String storeName = extractStoreName(p);
|
||||
storeFileConsumer.accept(p, storeName);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("could not call consumer for store file " + p + " with name " + storeName, e);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void moveAsRepositoryStore(Path storeFile, String repositoryId) throws IOException {
|
||||
Path repositoryLocation;
|
||||
try {
|
||||
repositoryLocation = locationResolver
|
||||
.forClass(Path.class)
|
||||
.getLocation(repositoryId);
|
||||
} catch (IllegalStateException e) {
|
||||
LOG.info("ignoring store file {} because there is no repository location for repository id {}", storeFile, repositoryId);
|
||||
return;
|
||||
}
|
||||
Path target = repositoryLocation
|
||||
.resolve(Store.DATA.getRepositoryStoreDirectory())
|
||||
.resolve(storeName);
|
||||
IOUtil.mkdirs(target.toFile());
|
||||
Path resolvedSourceFile = computeV1StoreDir().resolve(storeFile);
|
||||
Path resolvedTargetFile = target.resolve(storeFile.getFileName());
|
||||
LOG.trace("moving file {} to {}", resolvedSourceFile, resolvedTargetFile);
|
||||
Files.move(resolvedSourceFile, resolvedTargetFile);
|
||||
}
|
||||
|
||||
private Path computeV1StoreDir() {
|
||||
return contextProvider.getBaseDirectory().toPath().resolve("var").resolve("data").resolve(storeName);
|
||||
}
|
||||
|
||||
private String extractStoreName(Path p) {
|
||||
String fileName = p.getFileName().toString();
|
||||
return fileName.substring(0, fileName.length() - XML_FILENAME_SUFFIX.length());
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package sonia.scm.update.xml;
|
||||
|
||||
import sonia.scm.store.ConfigurationEntryStore;
|
||||
import sonia.scm.store.ConfigurationEntryStoreFactory;
|
||||
import sonia.scm.update.V1Properties;
|
||||
import sonia.scm.update.V1PropertyDAO;
|
||||
import sonia.scm.update.V1PropertyReader;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import java.util.Map;
|
||||
|
||||
public class XmlV1PropertyDAO implements V1PropertyDAO {
|
||||
|
||||
private final ConfigurationEntryStoreFactory configurationEntryStoreFactory;
|
||||
|
||||
@Inject
|
||||
public XmlV1PropertyDAO(ConfigurationEntryStoreFactory configurationEntryStoreFactory) {
|
||||
this.configurationEntryStoreFactory = configurationEntryStoreFactory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public V1PropertyReader.Instance getProperties(V1PropertyReader reader) {
|
||||
ConfigurationEntryStore<V1Properties> propertyStore = configurationEntryStoreFactory
|
||||
.withType(V1Properties.class)
|
||||
.withName(reader.getStoreName())
|
||||
.build();
|
||||
Map<String, V1Properties> all = propertyStore.getAll();
|
||||
return reader.createInstance(all);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
package sonia.scm.store;
|
||||
|
||||
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 sonia.scm.SCMContextProvider;
|
||||
import sonia.scm.io.DefaultFileSystem;
|
||||
import sonia.scm.repository.InitialRepositoryLocationResolver;
|
||||
import sonia.scm.repository.RepositoryLocationResolver;
|
||||
import sonia.scm.repository.xml.PathBasedRepositoryLocationResolver;
|
||||
import sonia.scm.update.PropertyFileAccess;
|
||||
import sonia.scm.util.IOUtil;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
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.lenient;
|
||||
|
||||
@ExtendWith(TempDirectory.class)
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class JAXBPropertyFileAccessTest {
|
||||
|
||||
public static final String REPOSITORY_ID = "repoId";
|
||||
public static final String STORE_NAME = "test";
|
||||
|
||||
@Mock
|
||||
SCMContextProvider contextProvider;
|
||||
|
||||
RepositoryLocationResolver locationResolver;
|
||||
|
||||
JAXBPropertyFileAccess fileAccess;
|
||||
|
||||
@BeforeEach
|
||||
void initTempDir(@TempDirectory.TempDir Path tempDir) {
|
||||
lenient().when(contextProvider.getBaseDirectory()).thenReturn(tempDir.toFile());
|
||||
lenient().when(contextProvider.resolve(any())).thenAnswer(invocation -> tempDir.resolve(invocation.getArgument(0).toString()));
|
||||
|
||||
locationResolver = new PathBasedRepositoryLocationResolver(contextProvider, new InitialRepositoryLocationResolver(), new DefaultFileSystem());
|
||||
|
||||
fileAccess = new JAXBPropertyFileAccess(contextProvider, locationResolver);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRenameGlobalConfigFile() throws IOException {
|
||||
Path baseDirectory = contextProvider.getBaseDirectory().toPath();
|
||||
Path configDirectory = baseDirectory.resolve(StoreConstants.CONFIG_DIRECTORY_NAME);
|
||||
|
||||
Files.createDirectories(configDirectory);
|
||||
|
||||
Path oldPath = configDirectory.resolve("old" + StoreConstants.FILE_EXTENSION);
|
||||
Files.createFile(oldPath);
|
||||
|
||||
fileAccess.renameGlobalConfigurationFrom("old").to("new");
|
||||
|
||||
Path newPath = configDirectory.resolve("new" + StoreConstants.FILE_EXTENSION);
|
||||
assertThat(oldPath).doesNotExist();
|
||||
assertThat(newPath).exists();
|
||||
}
|
||||
|
||||
@Nested
|
||||
class ForExistingRepository {
|
||||
|
||||
|
||||
@BeforeEach
|
||||
void createRepositoryLocation() {
|
||||
locationResolver.forClass(Path.class).createLocation(REPOSITORY_ID);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldMoveStoreFileToRepositoryBasedLocation(@TempDirectory.TempDir Path tempDir) throws IOException {
|
||||
createV1StoreFile(tempDir, "myStore.xml");
|
||||
|
||||
fileAccess.forStoreName(STORE_NAME).moveAsRepositoryStore(Paths.get("myStore.xml"), REPOSITORY_ID);
|
||||
|
||||
assertThat(tempDir.resolve("repositories").resolve(REPOSITORY_ID).resolve("store").resolve("data").resolve(STORE_NAME).resolve("myStore.xml")).exists();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldMoveAllStoreFilesToRepositoryBasedLocations(@TempDirectory.TempDir Path tempDir) throws IOException {
|
||||
locationResolver.forClass(Path.class).createLocation("repoId2");
|
||||
|
||||
createV1StoreFile(tempDir, REPOSITORY_ID + ".xml");
|
||||
createV1StoreFile(tempDir, "repoId2.xml");
|
||||
|
||||
PropertyFileAccess.StoreFileTools statisticStoreAccess = fileAccess.forStoreName(STORE_NAME);
|
||||
statisticStoreAccess.forStoreFiles(statisticStoreAccess::moveAsRepositoryStore);
|
||||
|
||||
assertThat(tempDir.resolve("repositories").resolve(REPOSITORY_ID).resolve("store").resolve("data").resolve(STORE_NAME).resolve("repoId.xml")).exists();
|
||||
assertThat(tempDir.resolve("repositories").resolve("repoId2").resolve("store").resolve("data").resolve(STORE_NAME).resolve("repoId2.xml")).exists();
|
||||
}
|
||||
}
|
||||
|
||||
private void createV1StoreFile(@TempDirectory.TempDir Path tempDir, String name) throws IOException {
|
||||
Path v1Dir = tempDir.resolve("var").resolve("data").resolve(STORE_NAME);
|
||||
IOUtil.mkdirs(v1Dir.toFile());
|
||||
Files.createFile(v1Dir.resolve(name));
|
||||
}
|
||||
|
||||
@Nested
|
||||
class ForMissingRepository {
|
||||
|
||||
@Test
|
||||
void shouldIgnoreStoreFile(@TempDirectory.TempDir Path tempDir) throws IOException {
|
||||
createV1StoreFile(tempDir, "myStore.xml");
|
||||
|
||||
fileAccess.forStoreName(STORE_NAME).moveAsRepositoryStore(Paths.get("myStore.xml"), REPOSITORY_ID);
|
||||
|
||||
assertThat(tempDir.resolve("repositories").resolve(REPOSITORY_ID).resolve("store").resolve("data").resolve(STORE_NAME).resolve("myStore.xml")).doesNotExist();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -40,6 +40,7 @@ import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
import sonia.scm.api.v2.resources.GitRepositoryConfigStoreProvider;
|
||||
import sonia.scm.repository.ChangesetPagingResult;
|
||||
import sonia.scm.repository.Repository;
|
||||
import sonia.scm.store.InMemoryConfigurationStoreFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
@@ -189,7 +190,7 @@ public class GitIncomingCommandTest
|
||||
*/
|
||||
private GitIncomingCommand createCommand()
|
||||
{
|
||||
return new GitIncomingCommand(handler, new GitContext(incomingDirectory, null, new GitRepositoryConfigStoreProvider(new InMemoryConfigurationStoreFactory())),
|
||||
incomingRepository);
|
||||
return new GitIncomingCommand(handler, new GitContext(incomingDirectory, incomingRepository, new GitRepositoryConfigStoreProvider(new InMemoryConfigurationStoreFactory())),
|
||||
this.incomingRepository);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,6 +40,7 @@ import org.eclipse.jgit.revwalk.RevCommit;
|
||||
import org.junit.Test;
|
||||
import sonia.scm.api.v2.resources.GitRepositoryConfigStoreProvider;
|
||||
import sonia.scm.repository.ChangesetPagingResult;
|
||||
import sonia.scm.repository.Repository;
|
||||
import sonia.scm.store.InMemoryConfigurationStoreFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
@@ -160,7 +161,7 @@ public class GitOutgoingCommandTest extends AbstractRemoteCommandTestBase
|
||||
*/
|
||||
private GitOutgoingCommand createCommand()
|
||||
{
|
||||
return new GitOutgoingCommand(handler, new GitContext(outgoingDirectory, null, new GitRepositoryConfigStoreProvider(new InMemoryConfigurationStoreFactory())),
|
||||
return new GitOutgoingCommand(handler, new GitContext(outgoingDirectory, outgoingRepository, new GitRepositoryConfigStoreProvider(new InMemoryConfigurationStoreFactory())),
|
||||
outgoingRepository);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,7 +71,7 @@ public class LfsBlobStoreFactoryTest {
|
||||
// the return value (and should not be part of this test)
|
||||
verify(blobStoreFactory).getStore(argThat(blobStoreParameters -> {
|
||||
assertThat(blobStoreParameters.getName()).isEqualTo("the-id-git-lfs");
|
||||
assertThat(blobStoreParameters.getRepository()).isEqualTo(repository);
|
||||
assertThat(blobStoreParameters.getRepositoryId()).isEqualTo("the-id");
|
||||
return true;
|
||||
}));
|
||||
|
||||
|
||||
@@ -1,28 +1,23 @@
|
||||
package sonia.scm.store;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class InMemoryConfigurationEntryStoreFactory implements ConfigurationEntryStoreFactory {
|
||||
|
||||
private final Map<String, InMemoryConfigurationEntryStore> stores = new HashMap<>();
|
||||
|
||||
|
||||
|
||||
private ConfigurationEntryStore store;
|
||||
|
||||
public static ConfigurationEntryStoreFactory create() {
|
||||
return new InMemoryConfigurationEntryStoreFactory(new InMemoryConfigurationEntryStore());
|
||||
}
|
||||
|
||||
public InMemoryConfigurationEntryStoreFactory() {
|
||||
}
|
||||
|
||||
public InMemoryConfigurationEntryStoreFactory(ConfigurationEntryStore store) {
|
||||
this.store = store;
|
||||
public static InMemoryConfigurationEntryStoreFactory create() {
|
||||
return new InMemoryConfigurationEntryStoreFactory();
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> ConfigurationEntryStore<T> getStore(TypedStoreParameters<T> storeParameters) {
|
||||
if (store != null) {
|
||||
return store;
|
||||
String name = storeParameters.getName();
|
||||
return get(name);
|
||||
}
|
||||
return new InMemoryConfigurationEntryStore<>();
|
||||
|
||||
public <T> InMemoryConfigurationEntryStore<T> get(String name) {
|
||||
return stores.computeIfAbsent(name, x -> new InMemoryConfigurationEntryStore());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,6 +35,9 @@ package sonia.scm.store;
|
||||
|
||||
//~--- non-JDK imports --------------------------------------------------------
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* In memory configuration store factory for testing purposes.
|
||||
*
|
||||
@@ -44,24 +47,19 @@ package sonia.scm.store;
|
||||
*/
|
||||
public class InMemoryConfigurationStoreFactory implements ConfigurationStoreFactory {
|
||||
|
||||
private ConfigurationStore store;
|
||||
private final Map<String, InMemoryConfigurationStore> stores = new HashMap<>();
|
||||
|
||||
public static ConfigurationStoreFactory create() {
|
||||
return new InMemoryConfigurationStoreFactory(new InMemoryConfigurationStore());
|
||||
}
|
||||
|
||||
public InMemoryConfigurationStoreFactory() {
|
||||
}
|
||||
|
||||
public InMemoryConfigurationStoreFactory(ConfigurationStore store) {
|
||||
this.store = store;
|
||||
public static InMemoryConfigurationStoreFactory create() {
|
||||
return new InMemoryConfigurationStoreFactory();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ConfigurationStore getStore(TypedStoreParameters storeParameters) {
|
||||
if (store != null) {
|
||||
return store;
|
||||
String name = storeParameters.getName();
|
||||
return get(name);
|
||||
}
|
||||
return new InMemoryConfigurationStore<>();
|
||||
|
||||
public ConfigurationStore get(String name) {
|
||||
return stores.computeIfAbsent(name, x -> new InMemoryConfigurationStore());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,11 +3,6 @@ package sonia.scm.update;
|
||||
import com.google.common.io.Resources;
|
||||
import org.mockito.Mockito;
|
||||
import sonia.scm.SCMContextProvider;
|
||||
import sonia.scm.security.AssignedPermission;
|
||||
import sonia.scm.security.DefaultKeyGenerator;
|
||||
import sonia.scm.store.ConfigurationEntryStore;
|
||||
import sonia.scm.store.ConfigurationEntryStoreFactory;
|
||||
import sonia.scm.store.JAXBConfigurationEntryStoreFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
@@ -23,12 +18,10 @@ public class UpdateStepTestUtil {
|
||||
private final SCMContextProvider contextProvider;
|
||||
|
||||
private final Path tempDir;
|
||||
private final ConfigurationEntryStoreFactory storeFactory;
|
||||
|
||||
public UpdateStepTestUtil(Path tempDir) {
|
||||
this.tempDir = tempDir;
|
||||
contextProvider = Mockito.mock(SCMContextProvider.class);
|
||||
storeFactory = new JAXBConfigurationEntryStoreFactory(contextProvider, null, new DefaultKeyGenerator());
|
||||
lenient().when(contextProvider.getBaseDirectory()).thenReturn(tempDir.toFile());
|
||||
lenient().when(contextProvider.resolve(any())).thenAnswer(invocation -> tempDir.resolve(invocation.getArgument(0).toString()));
|
||||
}
|
||||
@@ -37,10 +30,6 @@ private final SCMContextProvider contextProvider;
|
||||
return contextProvider;
|
||||
}
|
||||
|
||||
public ConfigurationEntryStoreFactory getStoreFactory() {
|
||||
return storeFactory;
|
||||
}
|
||||
|
||||
public void copyConfigFile(String fileName) throws IOException {
|
||||
Path configDir = tempDir.resolve("config");
|
||||
Files.createDirectories(configDir);
|
||||
@@ -53,17 +42,6 @@ private final SCMContextProvider contextProvider;
|
||||
copyTestDatabaseFile(configDir, fileName, targetFileName);
|
||||
}
|
||||
|
||||
public ConfigurationEntryStore<AssignedPermission> getStoreForConfigFile(String name) {
|
||||
return storeFactory
|
||||
.withType(AssignedPermission.class)
|
||||
.withName(name)
|
||||
.build();
|
||||
}
|
||||
|
||||
public Path getFile(String name) {
|
||||
return tempDir.resolve("config").resolve(name);
|
||||
}
|
||||
|
||||
private void copyTestDatabaseFile(Path configDir, String fileName) throws IOException {
|
||||
Path targetFileName = Paths.get(fileName).getFileName();
|
||||
copyTestDatabaseFile(configDir, fileName, targetFileName.toString());
|
||||
@@ -0,0 +1,40 @@
|
||||
package sonia.scm.update;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static java.util.Arrays.stream;
|
||||
import static org.mockito.ArgumentMatchers.argThat;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
public class V1PropertyDaoTestUtil {
|
||||
|
||||
private final V1PropertyDAO propertyDAO = mock(V1PropertyDAO.class);
|
||||
|
||||
public V1PropertyDAO getPropertyDAO() {
|
||||
return propertyDAO;
|
||||
}
|
||||
|
||||
public void mockRepositoryProperties(PropertiesForRepository... mockedPropertiesForRepositories) {
|
||||
Map<String, V1Properties> map = new HashMap<>();
|
||||
stream(mockedPropertiesForRepositories).forEach(p -> map.put(p.repositoryId, p.asProperties()));
|
||||
V1PropertyReader.Instance v1PropertyReader = new MapBasedPropertyReaderInstance(map);
|
||||
when(propertyDAO.getProperties(argThat(argument -> argument instanceof RepositoryV1PropertyReader))).thenReturn(v1PropertyReader);
|
||||
}
|
||||
|
||||
public static class PropertiesForRepository {
|
||||
private final String repositoryId;
|
||||
private final Map<String, String> properties;
|
||||
|
||||
public PropertiesForRepository(String repositoryId, Map<String, String> properties) {
|
||||
this.repositoryId = repositoryId;
|
||||
this.properties = properties;
|
||||
}
|
||||
|
||||
V1Properties asProperties() {
|
||||
return new V1Properties(properties.entrySet().stream().map(e -> new V1Property(e.getKey(), e.getValue())).collect(Collectors.toList()));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -24,6 +24,10 @@ import sonia.scm.store.FileBlobStoreFactory;
|
||||
import sonia.scm.store.JAXBConfigurationEntryStoreFactory;
|
||||
import sonia.scm.store.JAXBConfigurationStoreFactory;
|
||||
import sonia.scm.store.JAXBDataStoreFactory;
|
||||
import sonia.scm.store.JAXBPropertyFileAccess;
|
||||
import sonia.scm.update.PropertyFileAccess;
|
||||
import sonia.scm.update.V1PropertyDAO;
|
||||
import sonia.scm.update.xml.XmlV1PropertyDAO;
|
||||
|
||||
public class BootstrapModule extends AbstractModule {
|
||||
|
||||
@@ -60,6 +64,8 @@ public class BootstrapModule extends AbstractModule {
|
||||
bind(DataStoreFactory.class, JAXBDataStoreFactory.class);
|
||||
bind(BlobStoreFactory.class, FileBlobStoreFactory.class);
|
||||
bind(PluginLoader.class).toInstance(pluginLoader);
|
||||
bind(V1PropertyDAO.class, XmlV1PropertyDAO.class);
|
||||
bind(PropertyFileAccess.class, JAXBPropertyFileAccess.class);
|
||||
}
|
||||
|
||||
private <T> void bind(Class<T> clazz, Class<? extends T> defaultImplementation) {
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
package sonia.scm.update;
|
||||
|
||||
import sonia.scm.migration.UpdateStep;
|
||||
|
||||
public interface CoreUpdateStep extends UpdateStep {
|
||||
}
|
||||
@@ -32,12 +32,20 @@ public class UpdateEngine {
|
||||
private List<UpdateStep> sortSteps(Set<UpdateStep> steps) {
|
||||
LOG.trace("sorting available update steps:");
|
||||
List<UpdateStep> sortedSteps = steps.stream()
|
||||
.sorted(Comparator.comparing(UpdateStep::getTargetVersion).reversed())
|
||||
.sorted(
|
||||
Comparator
|
||||
.comparing(UpdateStep::getTargetVersion)
|
||||
.thenComparing(this::isCoreUpdateStep)
|
||||
.reversed())
|
||||
.collect(toList());
|
||||
sortedSteps.forEach(step -> LOG.trace("{} for version {}", step.getAffectedDataType(), step.getTargetVersion()));
|
||||
return sortedSteps;
|
||||
}
|
||||
|
||||
private boolean isCoreUpdateStep(UpdateStep updateStep) {
|
||||
return updateStep instanceof CoreUpdateStep;
|
||||
}
|
||||
|
||||
public void update() {
|
||||
steps
|
||||
.stream()
|
||||
|
||||
@@ -11,7 +11,7 @@ import sonia.scm.plugin.Extension;
|
||||
import sonia.scm.store.ConfigurationEntryStore;
|
||||
import sonia.scm.store.ConfigurationEntryStoreFactory;
|
||||
import sonia.scm.store.StoreConstants;
|
||||
import sonia.scm.update.properties.V1Properties;
|
||||
import sonia.scm.update.V1Properties;
|
||||
import sonia.scm.version.Version;
|
||||
|
||||
import javax.inject.Inject;
|
||||
@@ -30,6 +30,7 @@ import java.util.Optional;
|
||||
|
||||
import static java.util.Optional.empty;
|
||||
import static java.util.Optional.of;
|
||||
import static sonia.scm.update.V1PropertyReader.GROUP_PROPERTY_READER;
|
||||
import static sonia.scm.version.Version.parse;
|
||||
|
||||
@Extension
|
||||
@@ -51,7 +52,7 @@ public class XmlGroupV1UpdateStep implements UpdateStep {
|
||||
this.groupDAO = groupDAO;
|
||||
this.propertyStore = configurationEntryStoreFactory
|
||||
.withType(V1Properties.class)
|
||||
.withName("group-properties-v1")
|
||||
.withName(GROUP_PROPERTY_READER.getStoreName())
|
||||
.build();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
package sonia.scm.update.properties;
|
||||
|
||||
import javax.xml.bind.annotation.XmlAccessType;
|
||||
import javax.xml.bind.annotation.XmlAccessorType;
|
||||
import javax.xml.bind.annotation.XmlElement;
|
||||
import javax.xml.bind.annotation.XmlRootElement;
|
||||
import java.util.List;
|
||||
|
||||
@XmlAccessorType(XmlAccessType.FIELD)
|
||||
@XmlRootElement(name = "properties")
|
||||
public class V1Properties {
|
||||
@XmlElement(name = "item")
|
||||
private List<V1Property> properties;
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
package sonia.scm.update.properties;
|
||||
|
||||
import javax.xml.bind.annotation.XmlAccessType;
|
||||
import javax.xml.bind.annotation.XmlAccessorType;
|
||||
|
||||
@XmlAccessorType(XmlAccessType.FIELD)
|
||||
public class V1Property {
|
||||
private String key;
|
||||
private String value;
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
package sonia.scm.update.repository;
|
||||
|
||||
import sonia.scm.update.properties.V1Properties;
|
||||
import sonia.scm.update.V1Properties;
|
||||
|
||||
import javax.xml.bind.annotation.XmlAccessType;
|
||||
import javax.xml.bind.annotation.XmlAccessorType;
|
||||
|
||||
@@ -5,7 +5,6 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import sonia.scm.SCMContextProvider;
|
||||
import sonia.scm.migration.UpdateException;
|
||||
import sonia.scm.migration.UpdateStep;
|
||||
import sonia.scm.plugin.Extension;
|
||||
import sonia.scm.repository.Repository;
|
||||
import sonia.scm.repository.RepositoryPermission;
|
||||
@@ -13,7 +12,8 @@ import sonia.scm.repository.xml.XmlRepositoryDAO;
|
||||
import sonia.scm.store.ConfigurationEntryStore;
|
||||
import sonia.scm.store.ConfigurationEntryStoreFactory;
|
||||
import sonia.scm.store.StoreConstants;
|
||||
import sonia.scm.update.properties.V1Properties;
|
||||
import sonia.scm.update.CoreUpdateStep;
|
||||
import sonia.scm.update.V1Properties;
|
||||
import sonia.scm.version.Version;
|
||||
|
||||
import javax.inject.Inject;
|
||||
@@ -36,6 +36,7 @@ import java.util.stream.Stream;
|
||||
import static java.util.Collections.emptyList;
|
||||
import static java.util.Optional.empty;
|
||||
import static java.util.Optional.of;
|
||||
import static sonia.scm.update.V1PropertyReader.REPOSITORY_PROPERTY_READER;
|
||||
import static sonia.scm.version.Version.parse;
|
||||
|
||||
/**
|
||||
@@ -56,7 +57,7 @@ import static sonia.scm.version.Version.parse;
|
||||
* </ul>
|
||||
*/
|
||||
@Extension
|
||||
public class XmlRepositoryV1UpdateStep implements UpdateStep {
|
||||
public class XmlRepositoryV1UpdateStep implements CoreUpdateStep {
|
||||
|
||||
private static Logger LOG = LoggerFactory.getLogger(XmlRepositoryV1UpdateStep.class);
|
||||
|
||||
@@ -80,7 +81,7 @@ public class XmlRepositoryV1UpdateStep implements UpdateStep {
|
||||
this.injector = injector;
|
||||
this.propertyStore = configurationEntryStoreFactory
|
||||
.withType(V1Properties.class)
|
||||
.withName("repository-properties-v1")
|
||||
.withName(REPOSITORY_PROPERTY_READER.getStoreName())
|
||||
.build();
|
||||
}
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ import sonia.scm.security.AssignedPermission;
|
||||
import sonia.scm.store.ConfigurationEntryStore;
|
||||
import sonia.scm.store.ConfigurationEntryStoreFactory;
|
||||
import sonia.scm.store.StoreConstants;
|
||||
import sonia.scm.update.properties.V1Properties;
|
||||
import sonia.scm.update.V1Properties;
|
||||
import sonia.scm.user.User;
|
||||
import sonia.scm.user.xml.XmlUserDAO;
|
||||
import sonia.scm.version.Version;
|
||||
@@ -31,6 +31,7 @@ import java.util.Optional;
|
||||
|
||||
import static java.util.Optional.empty;
|
||||
import static java.util.Optional.of;
|
||||
import static sonia.scm.update.V1PropertyReader.USER_PROPERTY_READER;
|
||||
import static sonia.scm.version.Version.parse;
|
||||
|
||||
@Extension
|
||||
@@ -50,7 +51,7 @@ public class XmlUserV1UpdateStep implements UpdateStep {
|
||||
this.configurationEntryStoreFactory = configurationEntryStoreFactory;
|
||||
this.propertyStore = configurationEntryStoreFactory
|
||||
.withType(V1Properties.class)
|
||||
.withName("user-properties-v1")
|
||||
.withName(USER_PROPERTY_READER.getStoreName())
|
||||
.build();
|
||||
}
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ import static sonia.scm.version.Version.parse;
|
||||
|
||||
class UpdateEngineTest {
|
||||
|
||||
ConfigurationEntryStoreFactory storeFactory = new InMemoryConfigurationEntryStoreFactory(new InMemoryConfigurationEntryStore());
|
||||
ConfigurationEntryStoreFactory storeFactory = new InMemoryConfigurationEntryStoreFactory();
|
||||
|
||||
List<String> processedUpdates = new ArrayList<>();
|
||||
|
||||
@@ -32,7 +32,21 @@ class UpdateEngineTest {
|
||||
updateEngine.update();
|
||||
|
||||
assertThat(processedUpdates)
|
||||
.containsExactly("1.1.0", "1.1.1", "1.2.0");
|
||||
.containsExactly("test:1.1.0", "test:1.1.1", "test:1.2.0");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldProcessCoreStepsBeforeOther() {
|
||||
LinkedHashSet<UpdateStep> updateSteps = new LinkedHashSet<>();
|
||||
|
||||
updateSteps.add(new FixedVersionUpdateStep("test", "1.2.0"));
|
||||
updateSteps.add(new CoreFixedVersionUpdateStep("core", "1.2.0"));
|
||||
|
||||
UpdateEngine updateEngine = new UpdateEngine(updateSteps, storeFactory);
|
||||
updateEngine.update();
|
||||
|
||||
assertThat(processedUpdates)
|
||||
.containsExactly("core:1.2.0", "test:1.2.0");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -67,7 +81,7 @@ class UpdateEngineTest {
|
||||
updateEngine = new UpdateEngine(updateSteps, storeFactory);
|
||||
updateEngine.update();
|
||||
|
||||
assertThat(processedUpdates).containsExactly("1.1.1");
|
||||
assertThat(processedUpdates).containsExactly("other:1.1.1");
|
||||
}
|
||||
|
||||
class FixedVersionUpdateStep implements UpdateStep {
|
||||
@@ -91,7 +105,13 @@ class UpdateEngineTest {
|
||||
|
||||
@Override
|
||||
public void doUpdate() {
|
||||
processedUpdates.add(version);
|
||||
processedUpdates.add(type + ":" + version);
|
||||
}
|
||||
}
|
||||
|
||||
class CoreFixedVersionUpdateStep extends FixedVersionUpdateStep implements CoreUpdateStep {
|
||||
CoreFixedVersionUpdateStep(String type, String version) {
|
||||
super(type, version);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,11 @@ import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import sonia.scm.group.Group;
|
||||
import sonia.scm.group.xml.XmlGroupDAO;
|
||||
import sonia.scm.store.ConfigurationEntryStore;
|
||||
import sonia.scm.store.InMemoryConfigurationEntryStoreFactory;
|
||||
import sonia.scm.update.UpdateStepTestUtil;
|
||||
import sonia.scm.update.V1Properties;
|
||||
import sonia.scm.update.V1Property;
|
||||
|
||||
import javax.xml.bind.JAXBException;
|
||||
import java.io.IOException;
|
||||
@@ -20,11 +24,11 @@ import java.util.Optional;
|
||||
|
||||
import static java.util.Arrays.asList;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.linesOf;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.doNothing;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static sonia.scm.store.InMemoryConfigurationEntryStoreFactory.create;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
@ExtendWith(TempDirectory.class)
|
||||
@@ -36,6 +40,8 @@ class XmlGroupV1UpdateStepTest {
|
||||
@Captor
|
||||
ArgumentCaptor<Group> groupCaptor;
|
||||
|
||||
InMemoryConfigurationEntryStoreFactory storeFactory = create();
|
||||
|
||||
XmlGroupV1UpdateStep updateStep;
|
||||
|
||||
private UpdateStepTestUtil testUtil;
|
||||
@@ -44,7 +50,7 @@ class XmlGroupV1UpdateStepTest {
|
||||
@BeforeEach
|
||||
void mockScmHome(@TempDirectory.TempDir Path tempDir) {
|
||||
testUtil = new UpdateStepTestUtil(tempDir);
|
||||
updateStep = new XmlGroupV1UpdateStep(testUtil.getContextProvider(), groupDAO, testUtil.getStoreFactory());
|
||||
updateStep = new XmlGroupV1UpdateStep(testUtil.getContextProvider(), groupDAO, storeFactory);
|
||||
}
|
||||
|
||||
@Nested
|
||||
@@ -83,19 +89,10 @@ class XmlGroupV1UpdateStepTest {
|
||||
@Test
|
||||
void shouldExtractProperties() throws JAXBException {
|
||||
updateStep.doUpdate();
|
||||
Path propertiesFile = testUtil.getFile("group-properties-v1.xml");
|
||||
assertThat(propertiesFile)
|
||||
.exists();
|
||||
assertThat(linesOf(propertiesFile.toFile()))
|
||||
.extracting(String::trim)
|
||||
.containsSequence(
|
||||
"<key>normals</key>",
|
||||
"<value>",
|
||||
"<item>",
|
||||
"<key>mostly</key>",
|
||||
"<value>humans</value>",
|
||||
"</item>",
|
||||
"</value>");
|
||||
ConfigurationEntryStore<V1Properties> propertiesStore = storeFactory.get("group-properties-v1");
|
||||
V1Properties properties = propertiesStore.get("normals");
|
||||
assertThat(properties).isNotNull();
|
||||
assertThat(properties.get("mostly")).isEqualTo("humans");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -51,7 +51,7 @@ class XmlRepositoryV1UpdateStepTest {
|
||||
@Mock
|
||||
MigrationStrategyDao migrationStrategyDao;
|
||||
|
||||
ConfigurationEntryStoreFactory configurationEntryStoreFactory = new InMemoryConfigurationEntryStoreFactory(new InMemoryConfigurationEntryStore());
|
||||
InMemoryConfigurationEntryStoreFactory configurationEntryStoreFactory = new InMemoryConfigurationEntryStoreFactory();
|
||||
|
||||
@Captor
|
||||
ArgumentCaptor<Repository> storeCaptor;
|
||||
@@ -137,7 +137,7 @@ class XmlRepositoryV1UpdateStepTest {
|
||||
void shouldExtractPropertiesFromRepositories() throws JAXBException {
|
||||
updateStep.doUpdate();
|
||||
|
||||
ConfigurationEntryStore<Object> store = configurationEntryStoreFactory.withType(null).withName("").build();
|
||||
ConfigurationEntryStore store = configurationEntryStoreFactory.get("repository-properties-v1");
|
||||
assertThat(store.getAll())
|
||||
.hasSize(3);
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ import java.util.List;
|
||||
import static java.util.stream.Collectors.toList;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.when;
|
||||
import static sonia.scm.store.InMemoryConfigurationEntryStoreFactory.create;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
@ExtendWith(TempDirectory.class)
|
||||
@@ -40,8 +41,8 @@ class XmlSecurityV1UpdateStepTest {
|
||||
@BeforeEach
|
||||
void mockScmHome(@TempDirectory.TempDir Path tempDir) {
|
||||
when(contextProvider.getBaseDirectory()).thenReturn(tempDir.toFile());
|
||||
assignedPermissionStore = new InMemoryConfigurationEntryStore<>();
|
||||
ConfigurationEntryStoreFactory inMemoryConfigurationEntryStoreFactory = new InMemoryConfigurationEntryStoreFactory(assignedPermissionStore);
|
||||
InMemoryConfigurationEntryStoreFactory inMemoryConfigurationEntryStoreFactory = create();
|
||||
assignedPermissionStore = inMemoryConfigurationEntryStoreFactory.get("security");
|
||||
updateStep = new XmlSecurityV1UpdateStep(contextProvider, inMemoryConfigurationEntryStoreFactory);
|
||||
}
|
||||
|
||||
|
||||
@@ -10,7 +10,11 @@ import org.mockito.Captor;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import sonia.scm.security.AssignedPermission;
|
||||
import sonia.scm.store.ConfigurationEntryStore;
|
||||
import sonia.scm.store.InMemoryConfigurationEntryStoreFactory;
|
||||
import sonia.scm.update.UpdateStepTestUtil;
|
||||
import sonia.scm.update.V1Properties;
|
||||
import sonia.scm.update.V1Property;
|
||||
import sonia.scm.user.User;
|
||||
import sonia.scm.user.xml.XmlUserDAO;
|
||||
|
||||
@@ -20,11 +24,11 @@ import java.nio.file.Path;
|
||||
import java.util.Optional;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.linesOf;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.doNothing;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static sonia.scm.store.InMemoryConfigurationEntryStoreFactory.create;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
@ExtendWith(TempDirectory.class)
|
||||
@@ -36,6 +40,8 @@ class XmlUserV1UpdateStepTest {
|
||||
@Captor
|
||||
ArgumentCaptor<User> userCaptor;
|
||||
|
||||
InMemoryConfigurationEntryStoreFactory storeFactory = create();
|
||||
|
||||
XmlUserV1UpdateStep updateStep;
|
||||
|
||||
private UpdateStepTestUtil testUtil;
|
||||
@@ -43,7 +49,7 @@ class XmlUserV1UpdateStepTest {
|
||||
@BeforeEach
|
||||
void mockScmHome(@TempDirectory.TempDir Path tempDir) {
|
||||
testUtil = new UpdateStepTestUtil(tempDir);
|
||||
updateStep = new XmlUserV1UpdateStep(testUtil.getContextProvider(), userDAO, testUtil.getStoreFactory());
|
||||
updateStep = new XmlUserV1UpdateStep(testUtil.getContextProvider(), userDAO, storeFactory);
|
||||
}
|
||||
|
||||
@Nested
|
||||
@@ -63,7 +69,7 @@ class XmlUserV1UpdateStepTest {
|
||||
void shouldCreateNewPermissionsForV1AdminUser() throws JAXBException {
|
||||
updateStep.doUpdate();
|
||||
Optional<AssignedPermission> assignedPermission =
|
||||
testUtil.getStoreForConfigFile("security")
|
||||
storeFactory.<AssignedPermission>get("security")
|
||||
.getAll()
|
||||
.values()
|
||||
.stream()
|
||||
@@ -98,23 +104,11 @@ class XmlUserV1UpdateStepTest {
|
||||
@Test
|
||||
void shouldExtractProperties() throws JAXBException {
|
||||
updateStep.doUpdate();
|
||||
Path propertiesFile = testUtil.getFile("user-properties-v1.xml");
|
||||
assertThat(propertiesFile)
|
||||
.exists();
|
||||
assertThat(linesOf(propertiesFile.toFile()))
|
||||
.extracting(String::trim)
|
||||
.containsSequence(
|
||||
"<key>dent</key>",
|
||||
"<value>",
|
||||
"<item>",
|
||||
"<key>born.on</key>",
|
||||
"<value>earth</value>",
|
||||
"</item>",
|
||||
"<item>",
|
||||
"<key>last.seen</key>",
|
||||
"<value>end of the universe</value>",
|
||||
"</item>",
|
||||
"</value>");
|
||||
ConfigurationEntryStore<V1Properties> propertiesStore = storeFactory.<V1Properties>get("user-properties-v1");
|
||||
V1Properties properties = propertiesStore.get("dent");
|
||||
assertThat(properties).isNotNull();
|
||||
assertThat(properties.get("born.on")).isEqualTo("earth");
|
||||
assertThat(properties.get("last.seen")).isEqualTo("end of the universe");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user