Enable plugins to create config stores for repository config

Therefore we have to
- add an API to create stores for repository ids, not only for
  repositories,
- make v1 properties available in scm-core
- make sure that properties are extracted from repositories before the
  update step of a plugin runs (this is done by sorting the update steps
  in a way so that "core" update steps are executed before plugin update
  steps with the same version)
This commit is contained in:
René Pfeuffer
2019-06-20 16:12:16 +02:00
parent d3b65ac3bd
commit 9581bf946b
29 changed files with 220 additions and 66 deletions

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -12,5 +12,5 @@ public interface StoreParameters {
String getName();
Repository getRepository();
String getRepositoryId();
}

View File

@@ -15,5 +15,5 @@ public interface TypedStoreParameters<T> {
String getName();
Repository getRepository();
String getRepositoryId();
}

View File

@@ -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;
}
}

View File

@@ -0,0 +1,23 @@
package sonia.scm.update;
import java.util.Map;
import java.util.function.BiConsumer;
public class RepositoryV1PropertyReader implements V1PropertyReader {
@Override
public String getStoreName() {
return "repository-properties-v1";
}
@Override
public Instance createInstance(Map<String, V1Properties> all) {
return propertiesForNameConsumer ->
all
.entrySet()
.forEach(e -> call(e.getKey(), e.getValue(), propertiesForNameConsumer));
}
private void call(String repositoryId, V1Properties properties, BiConsumer<String, V1Properties> propertiesForNameConsumer) {
propertiesForNameConsumer.accept(repositoryId, properties);
}
}

View File

@@ -1,4 +1,4 @@
package sonia.scm.update.properties;
package sonia.scm.update;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
@@ -14,7 +14,7 @@ public class V1Properties {
@XmlElement(name = "item")
private List<V1Property> properties;
public List<V1Property> getProperties() {
return unmodifiableList(properties);
public String get(String key) {
return properties.stream().filter(p -> key.equals(p.getKey())).map(V1Property::getValue).findFirst().orElse(null);
}
}

View File

@@ -1,4 +1,4 @@
package sonia.scm.update.properties;
package sonia.scm.update;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;

View File

@@ -0,0 +1,5 @@
package sonia.scm.update;
public interface V1PropertyDAO {
V1PropertyReader.Instance getProperties(V1PropertyReader reader);
}

View File

@@ -0,0 +1,15 @@
package sonia.scm.update;
import java.util.Map;
import java.util.function.BiConsumer;
public interface V1PropertyReader {
String getStoreName();
Instance createInstance(Map<String, V1Properties> all);
interface Instance {
void forEachEntry(BiConsumer<String, V1Properties> propertiesForNameConsumer);
}
}

View File

@@ -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());
}
/**

View File

@@ -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());
}
}

View File

@@ -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()));
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}));

View File

@@ -24,6 +24,8 @@ import sonia.scm.store.FileBlobStoreFactory;
import sonia.scm.store.JAXBConfigurationEntryStoreFactory;
import sonia.scm.store.JAXBConfigurationStoreFactory;
import sonia.scm.store.JAXBDataStoreFactory;
import sonia.scm.update.V1PropertyDAO;
import sonia.scm.update.xml.XmlV1PropertyDAO;
public class BootstrapModule extends AbstractModule {
@@ -60,6 +62,7 @@ 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);
}
private <T> void bind(Class<T> clazz, Class<? extends T> defaultImplementation) {

View File

@@ -0,0 +1,6 @@
package sonia.scm.update;
import sonia.scm.migration.UpdateStep;
public interface CoreUpdateStep extends UpdateStep {
}

View File

@@ -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()

View File

@@ -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;

View File

@@ -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;

View File

@@ -13,7 +13,9 @@ 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.update.RepositoryV1PropertyReader;
import sonia.scm.version.Version;
import javax.inject.Inject;
@@ -56,7 +58,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 +82,7 @@ public class XmlRepositoryV1UpdateStep implements UpdateStep {
this.injector = injector;
this.propertyStore = configurationEntryStoreFactory
.withType(V1Properties.class)
.withName("repository-properties-v1")
.withName(new RepositoryV1PropertyReader().getStoreName())
.build();
}

View File

@@ -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;

View File

@@ -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);
}
}
}

View File

@@ -14,8 +14,8 @@ 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.properties.V1Properties;
import sonia.scm.update.properties.V1Property;
import sonia.scm.update.V1Properties;
import sonia.scm.update.V1Property;
import javax.xml.bind.JAXBException;
import java.io.IOException;
@@ -89,13 +89,10 @@ class XmlGroupV1UpdateStepTest {
@Test
void shouldExtractProperties() throws JAXBException {
updateStep.doUpdate();
ConfigurationEntryStore<V1Properties> propertiesStore = storeFactory.<V1Properties>get("group-properties-v1");
assertThat(propertiesStore.get("normals"))
.isNotNull()
.extracting(V1Properties::getProperties)
.first()
.asList()
.contains(new V1Property("mostly", "humans"));
ConfigurationEntryStore<V1Properties> propertiesStore = storeFactory.get("group-properties-v1");
V1Properties properties = propertiesStore.get("normals");
assertThat(properties).isNotNull();
assertThat(properties.get("mostly")).isEqualTo("humans");
}
}

View File

@@ -13,8 +13,8 @@ import sonia.scm.security.AssignedPermission;
import sonia.scm.store.ConfigurationEntryStore;
import sonia.scm.store.InMemoryConfigurationEntryStoreFactory;
import sonia.scm.update.UpdateStepTestUtil;
import sonia.scm.update.properties.V1Properties;
import sonia.scm.update.properties.V1Property;
import sonia.scm.update.V1Properties;
import sonia.scm.update.V1Property;
import sonia.scm.user.User;
import sonia.scm.user.xml.XmlUserDAO;
@@ -105,14 +105,10 @@ class XmlUserV1UpdateStepTest {
void shouldExtractProperties() throws JAXBException {
updateStep.doUpdate();
ConfigurationEntryStore<V1Properties> propertiesStore = storeFactory.<V1Properties>get("user-properties-v1");
assertThat(propertiesStore.get("dent"))
.isNotNull()
.extracting(V1Properties::getProperties)
.first()
.asList()
.contains(
new V1Property("born.on", "earth"),
new V1Property("last.seen", "end of the universe"));
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");
}
}