mirror of
https://github.com/scm-manager/scm-manager.git
synced 2026-01-06 07:39:48 +01:00
Add update steps for namespaces
This adds a new update step API dedicated to handle namespace related data. Pushed-by: Rene Pfeuffer<rene.pfeuffer@cloudogu.com> Co-authored-by: René Pfeuffer<rene.pfeuffer@cloudogu.com> Committed-by: René Pfeuffer<rene.pfeuffer@cloudogu.com>
This commit is contained in:
2
gradle/changelog/namespace_update_steps.yaml
Normal file
2
gradle/changelog/namespace_update_steps.yaml
Normal file
@@ -0,0 +1,2 @@
|
||||
- type: added
|
||||
description: Update steps for namespaces
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
package sonia.scm.migration;
|
||||
|
||||
/**
|
||||
* Data for the namespace, whose data that should be migrated.
|
||||
*
|
||||
* @since 2.47.0
|
||||
*/
|
||||
public final class NamespaceUpdateContext {
|
||||
|
||||
private final String namespace;
|
||||
|
||||
public NamespaceUpdateContext(String namespace) {
|
||||
this.namespace = namespace;
|
||||
}
|
||||
|
||||
/**
|
||||
* The name of the namespace, whose data should be migrated.
|
||||
*/
|
||||
public String getNamespace() {
|
||||
return namespace;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
package sonia.scm.migration;
|
||||
|
||||
import sonia.scm.plugin.ExtensionPoint;
|
||||
|
||||
/**
|
||||
* This is the main interface for "namespace specific" data migration/update. Using this interface, SCM-Manager
|
||||
* provides the possibility to change data structures between versions for a given type of data. This class should be
|
||||
* used only for namespace specific data (eg. the store is created with a call of <code>forNamespace</code> in a store
|
||||
* factory. To migrate global data, use a {@link UpdateStep}.
|
||||
* <p>For information about {@link #getAffectedDataType()} and {@link #getTargetVersion()}, see the package
|
||||
* documentation.</p>
|
||||
*
|
||||
* @see sonia.scm.migration
|
||||
*
|
||||
* @since 2.47.0
|
||||
*/
|
||||
@ExtensionPoint
|
||||
public interface NamespaceUpdateStep extends UpdateStepTarget {
|
||||
/**
|
||||
* Implement this to update the data to the new version for a specific namespace. If any {@link Exception} is thrown,
|
||||
* SCM-Manager will not start up.
|
||||
*
|
||||
* @param namespaceUpdateContext A context providing specifics about the namespace, whose data should be migrated
|
||||
* (eg. its name).
|
||||
*/
|
||||
@SuppressWarnings("java:S112") // we suppress this one, because an implementation should feel free to throw any exception it deems necessary
|
||||
void doUpdate(NamespaceUpdateContext namespaceUpdateContext) throws Exception;
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
package sonia.scm.update;
|
||||
|
||||
import sonia.scm.migration.UpdateException;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* Implementations of this interface can be used to iterate all namespaces in update steps.
|
||||
*
|
||||
* @since 2.47.0
|
||||
*/
|
||||
public interface NamespaceUpdateIterator {
|
||||
|
||||
/**
|
||||
* Calls the given consumer with each namespace.
|
||||
*
|
||||
* @since 2.47.0
|
||||
*/
|
||||
void forEachNamespace(Consumer<String> namespace);
|
||||
|
||||
/**
|
||||
* Equivalent to {@link #forEachNamespace(Consumer)} with the difference, that you can throw exceptions in the given
|
||||
* update code, that will then be wrapped in a {@link UpdateException}.
|
||||
*
|
||||
* @since 2.47.0
|
||||
*/
|
||||
default void updateEachNamespace(Updater updater) {
|
||||
forEachNamespace(
|
||||
namespace -> {
|
||||
try {
|
||||
updater.update(namespace);
|
||||
} catch (Exception e) {
|
||||
throw new UpdateException("failed to update namespace " + namespace, e);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple callback with the name of an existing namespace with the possibility to throw exceptions.
|
||||
*
|
||||
* @since 2.47.0
|
||||
*/
|
||||
interface Updater {
|
||||
/**
|
||||
* Implements the update logic for a single namespace, denoted by its name.
|
||||
*/
|
||||
@SuppressWarnings("java:S112") // We explicitly want to allow arbitrary exceptions here
|
||||
void update(String namespace) throws Exception;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
package sonia.scm.store;
|
||||
|
||||
import sonia.scm.migration.UpdateException;
|
||||
import sonia.scm.repository.Repository;
|
||||
import sonia.scm.repository.RepositoryLocationResolver;
|
||||
import sonia.scm.update.NamespaceUpdateIterator;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.xml.bind.JAXBContext;
|
||||
import javax.xml.bind.JAXBException;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public class FileNamespaceUpdateIterator implements NamespaceUpdateIterator {
|
||||
|
||||
private final RepositoryLocationResolver locationResolver;
|
||||
private final JAXBContext jaxbContext;
|
||||
|
||||
@Inject
|
||||
public FileNamespaceUpdateIterator(RepositoryLocationResolver locationResolver) {
|
||||
this.locationResolver = locationResolver;
|
||||
try {
|
||||
jaxbContext = JAXBContext.newInstance(Repository.class);
|
||||
} catch (JAXBException ex) {
|
||||
throw new IllegalStateException("failed to create jaxb context for repository", ex);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void forEachNamespace(Consumer<String> namespaceConsumer) {
|
||||
Collection<String> namespaces = new HashSet<>();
|
||||
locationResolver
|
||||
.forClass(Path.class)
|
||||
.forAllLocations((repositoryId, path) -> {
|
||||
try {
|
||||
Repository metadata = (Repository) jaxbContext.createUnmarshaller().unmarshal(path.resolve("metadata.xml").toFile());
|
||||
namespaces.add(metadata.getNamespace());
|
||||
} catch (JAXBException e) {
|
||||
throw new UpdateException("Failed to read metadata for repository " + repositoryId, e);
|
||||
}
|
||||
});
|
||||
namespaces.forEach(namespaceConsumer);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
package sonia.scm.store;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import sonia.scm.TempDirRepositoryLocationResolver;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
|
||||
import static java.util.Arrays.asList;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class FileNamespaceUpdateIteratorTest {
|
||||
|
||||
private TempDirRepositoryLocationResolver locationResolver;
|
||||
|
||||
@BeforeEach
|
||||
void initLocationResolver(@TempDir Path tempDir) throws IOException {
|
||||
locationResolver = new TempDirRepositoryLocationResolver(tempDir.toFile());
|
||||
Files.write(tempDir.resolve("metadata.xml"), asList(
|
||||
"<repositories>",
|
||||
" <namespace>hitchhike</namespace>",
|
||||
"</repositories>"
|
||||
));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldFindNamespaces() {
|
||||
Collection<String> foundNamespaces = new ArrayList<>();
|
||||
new FileNamespaceUpdateIterator(locationResolver)
|
||||
.forEachNamespace(foundNamespaces::add);
|
||||
|
||||
assertThat(foundNamespaces).containsExactly("hitchhike");
|
||||
}
|
||||
}
|
||||
@@ -37,7 +37,7 @@ import java.util.Map;
|
||||
|
||||
public class InMemoryByteConfigurationEntryStore<V> implements ConfigurationEntryStore<V> {
|
||||
|
||||
private final Class<V> type;
|
||||
private Class<V> type;
|
||||
private final KeyGenerator generator = new UUIDKeyGenerator();
|
||||
private final Map<String, byte[]> store = new HashMap<>();
|
||||
|
||||
@@ -104,4 +104,8 @@ public class InMemoryByteConfigurationEntryStore<V> implements ConfigurationEntr
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
void overrideType(Class<V> type) {
|
||||
this.type = type;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,6 +51,8 @@ public class InMemoryByteConfigurationEntryStoreFactory implements Configuration
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> ConfigurationEntryStore<T> get(Class<T> type, String name) {
|
||||
return stores.computeIfAbsent(name, n -> new InMemoryByteConfigurationEntryStore<>(type));
|
||||
InMemoryByteConfigurationEntryStore<T> store = stores.computeIfAbsent(name, n -> new InMemoryByteConfigurationEntryStore<>(type));
|
||||
store.overrideType(type);
|
||||
return store;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ import java.nio.charset.StandardCharsets;
|
||||
*/
|
||||
public class InMemoryByteConfigurationStore<T> implements ConfigurationStore<T> {
|
||||
|
||||
private final Class<T> type;
|
||||
private Class<T> type;
|
||||
byte[] store;
|
||||
|
||||
public InMemoryByteConfigurationStore(Class<T> type) {
|
||||
@@ -73,4 +73,8 @@ public class InMemoryByteConfigurationStore<T> implements ConfigurationStore<T>
|
||||
public void setRawXml(String xml) {
|
||||
store = xml.getBytes(StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
void overrideType(Class<T> type) {
|
||||
this.type = type;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,6 +46,8 @@ public class InMemoryByteConfigurationStoreFactory implements ConfigurationStore
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> ConfigurationStore<T> getStore(Class<T> type, String name) {
|
||||
return stores.computeIfAbsent(name, n -> new InMemoryByteConfigurationStore<>(type));
|
||||
InMemoryByteConfigurationStore<T> store = stores.computeIfAbsent(name, n -> new InMemoryByteConfigurationStore<>(type));
|
||||
store.overrideType(type);
|
||||
return store;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ import java.util.Map;
|
||||
|
||||
public class InMemoryByteDataStore<T> implements DataStore<T> {
|
||||
|
||||
private final Class<T> type;
|
||||
private Class<T> type;
|
||||
private final KeyGenerator generator = new UUIDKeyGenerator();
|
||||
private final Map<String, byte[]> store = new HashMap<>();
|
||||
|
||||
@@ -104,4 +104,8 @@ public class InMemoryByteDataStore<T> implements DataStore<T> {
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
void overrideType(Class<T> type) {
|
||||
this.type = type;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,6 +47,8 @@ public class InMemoryByteDataStoreFactory implements DataStoreFactory, InMemoryS
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> DataStore<T> getStore(Class<T> type, String name) {
|
||||
return stores.computeIfAbsent(name, n -> new InMemoryByteDataStore<>(type));
|
||||
InMemoryByteDataStore<T> store = stores.computeIfAbsent(name, n -> new InMemoryByteDataStore<>(type));
|
||||
store.overrideType(type);
|
||||
return store;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,6 +61,7 @@ import sonia.scm.store.ConfigurationStoreFactory;
|
||||
import sonia.scm.store.DataStoreFactory;
|
||||
import sonia.scm.store.DefaultBlobDirectoryAccess;
|
||||
import sonia.scm.store.FileBlobStoreFactory;
|
||||
import sonia.scm.store.FileNamespaceUpdateIterator;
|
||||
import sonia.scm.store.FileRepositoryUpdateIterator;
|
||||
import sonia.scm.store.FileStoreUpdateStepUtilFactory;
|
||||
import sonia.scm.store.JAXBConfigurationEntryStoreFactory;
|
||||
@@ -68,6 +69,7 @@ import sonia.scm.store.JAXBConfigurationStoreFactory;
|
||||
import sonia.scm.store.JAXBDataStoreFactory;
|
||||
import sonia.scm.store.JAXBPropertyFileAccess;
|
||||
import sonia.scm.update.BlobDirectoryAccess;
|
||||
import sonia.scm.update.NamespaceUpdateIterator;
|
||||
import sonia.scm.update.PropertyFileAccess;
|
||||
import sonia.scm.update.RepositoryUpdateIterator;
|
||||
import sonia.scm.update.StoreUpdateStepUtilFactory;
|
||||
@@ -124,6 +126,7 @@ public class BootstrapModule extends AbstractModule {
|
||||
bind(PropertyFileAccess.class, JAXBPropertyFileAccess.class);
|
||||
bind(BlobDirectoryAccess.class, DefaultBlobDirectoryAccess.class);
|
||||
bind(RepositoryUpdateIterator.class, FileRepositoryUpdateIterator.class);
|
||||
bind(NamespaceUpdateIterator.class, FileNamespaceUpdateIterator.class);
|
||||
bind(StoreUpdateStepUtilFactory.class, FileStoreUpdateStepUtilFactory.class);
|
||||
bind(new TypeLiteral<UpdateStepRepositoryMetadataAccess<Path>>() {}).to(new TypeLiteral<MetadataStore>() {});
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@ package sonia.scm.lifecycle.modules;
|
||||
|
||||
import com.google.inject.AbstractModule;
|
||||
import com.google.inject.multibindings.Multibinder;
|
||||
import sonia.scm.migration.NamespaceUpdateStep;
|
||||
import sonia.scm.migration.RepositoryUpdateStep;
|
||||
import sonia.scm.migration.UpdateStep;
|
||||
import sonia.scm.plugin.PluginLoader;
|
||||
@@ -41,7 +42,8 @@ public class UpdateStepModule extends AbstractModule {
|
||||
@Override
|
||||
protected void configure() {
|
||||
Multibinder<UpdateStep> updateStepBinder = Multibinder.newSetBinder(binder(), UpdateStep.class);
|
||||
Multibinder<RepositoryUpdateStep> repositoryUdateStepBinder = Multibinder.newSetBinder(binder(), RepositoryUpdateStep.class);
|
||||
Multibinder<RepositoryUpdateStep> repositoryUpdateStepBinder = Multibinder.newSetBinder(binder(), RepositoryUpdateStep.class);
|
||||
Multibinder<NamespaceUpdateStep> namespaceUpdateStepBinder = Multibinder.newSetBinder(binder(), NamespaceUpdateStep.class);
|
||||
pluginLoader
|
||||
.getExtensionProcessor()
|
||||
.byExtensionPoint(UpdateStep.class)
|
||||
@@ -49,6 +51,10 @@ public class UpdateStepModule extends AbstractModule {
|
||||
pluginLoader
|
||||
.getExtensionProcessor()
|
||||
.byExtensionPoint(RepositoryUpdateStep.class)
|
||||
.forEach(stepClass -> repositoryUdateStepBinder.addBinding().to(stepClass));
|
||||
.forEach(stepClass -> repositoryUpdateStepBinder.addBinding().to(stepClass));
|
||||
pluginLoader
|
||||
.getExtensionProcessor()
|
||||
.byExtensionPoint(NamespaceUpdateStep.class)
|
||||
.forEach(stepClass -> namespaceUpdateStepBinder.addBinding().to(stepClass));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,6 +26,8 @@ package sonia.scm.update;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import sonia.scm.migration.NamespaceUpdateContext;
|
||||
import sonia.scm.migration.NamespaceUpdateStep;
|
||||
import sonia.scm.migration.RepositoryUpdateContext;
|
||||
import sonia.scm.migration.RepositoryUpdateStep;
|
||||
import sonia.scm.migration.UpdateException;
|
||||
@@ -46,28 +48,33 @@ public class UpdateEngine {
|
||||
|
||||
public static final Logger LOG = LoggerFactory.getLogger(UpdateEngine.class);
|
||||
|
||||
|
||||
private final List<UpdateStepWrapper> steps;
|
||||
private final RepositoryUpdateIterator repositoryUpdateIterator;
|
||||
private final NamespaceUpdateIterator namespaceUpdateIterator;
|
||||
private final UpdateStepStore updateStepStore;
|
||||
|
||||
@Inject
|
||||
public UpdateEngine(
|
||||
Set<UpdateStep> globalSteps,
|
||||
Set<RepositoryUpdateStep> repositorySteps,
|
||||
Set<NamespaceUpdateStep> namespaceSteps,
|
||||
RepositoryUpdateIterator repositoryUpdateIterator,
|
||||
UpdateStepStore updateStepStore) {
|
||||
NamespaceUpdateIterator namespaceUpdateIterator, UpdateStepStore updateStepStore) {
|
||||
this.repositoryUpdateIterator = repositoryUpdateIterator;
|
||||
this.namespaceUpdateIterator = namespaceUpdateIterator;
|
||||
this.updateStepStore = updateStepStore;
|
||||
this.steps = sortSteps(globalSteps, repositorySteps);
|
||||
this.steps = sortSteps(globalSteps, repositorySteps, namespaceSteps);
|
||||
}
|
||||
|
||||
private List<UpdateStepWrapper> sortSteps(Set<UpdateStep> globalSteps, Set<RepositoryUpdateStep> repositorySteps) {
|
||||
private List<UpdateStepWrapper> sortSteps(Set<UpdateStep> globalSteps, Set<RepositoryUpdateStep> repositorySteps, Set<NamespaceUpdateStep> namespaceSteps) {
|
||||
LOG.trace("sorting available update steps:");
|
||||
List<UpdateStepWrapper> sortedSteps =
|
||||
concat(
|
||||
globalSteps.stream().filter(this::notRunYet).map(GlobalUpdateStepWrapper::new),
|
||||
repositorySteps.stream().map(RepositoryUpdateStepWrapper::new))
|
||||
concat(
|
||||
globalSteps.stream().filter(this::notRunYet).map(GlobalUpdateStepWrapper::new),
|
||||
repositorySteps.stream().map(RepositoryUpdateStepWrapper::new)),
|
||||
namespaceSteps.stream().map(NamespaceUpdateStepWrapper::new)
|
||||
)
|
||||
.sorted(
|
||||
Comparator
|
||||
.comparing(UpdateStepWrapper::getTargetVersion)
|
||||
@@ -248,4 +255,52 @@ public class UpdateEngine {
|
||||
return updateStepStore.notRunYet(repositoryId, delegate);
|
||||
}
|
||||
}
|
||||
|
||||
private class NamespaceUpdateStepWrapper extends UpdateStepWrapper {
|
||||
|
||||
private final NamespaceUpdateStep delegate;
|
||||
|
||||
public NamespaceUpdateStepWrapper(NamespaceUpdateStep delegate) {
|
||||
super(delegate);
|
||||
this.delegate = delegate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isGlobalUpdateStep() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean isCoreUpdate() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
void doUpdate() {
|
||||
namespaceUpdateIterator.updateEachNamespace(this::doUpdate);
|
||||
}
|
||||
|
||||
@Override
|
||||
void doUpdate(String namespace) throws Exception {
|
||||
if (notRunYet(namespace)) {
|
||||
LOG.info("running update step for type {} and version {} (class {}) for namespace {}",
|
||||
delegate.getAffectedDataType(),
|
||||
delegate.getTargetVersion(),
|
||||
delegate.getClass().getName(),
|
||||
namespace
|
||||
);
|
||||
delegate.doUpdate(new NamespaceUpdateContext(namespace));
|
||||
updateStepStore.storeExecutedUpdate(namespace, delegate);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean notRunYet(String namespace) {
|
||||
LOG.trace("checking whether to run update step for type {} and version {} on namespace {}",
|
||||
delegate.getAffectedDataType(),
|
||||
delegate.getTargetVersion(),
|
||||
namespace
|
||||
);
|
||||
return updateStepStore.notRunYet(namespace, delegate);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ package sonia.scm.update;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import sonia.scm.migration.NamespaceUpdateStep;
|
||||
import sonia.scm.migration.RepositoryUpdateStep;
|
||||
import sonia.scm.migration.UpdateStep;
|
||||
import sonia.scm.migration.UpdateStepTarget;
|
||||
@@ -52,6 +53,11 @@ class UpdateStepStore {
|
||||
storeNewVersion(store, updateStep);
|
||||
}
|
||||
|
||||
void storeExecutedUpdate(String namespace, NamespaceUpdateStep updateStep) {
|
||||
ConfigurationEntryStore<UpdateVersionInfo> store = createNamespaceStore(namespace);
|
||||
storeNewVersion(store, updateStep);
|
||||
}
|
||||
|
||||
void storeExecutedUpdate(UpdateStep updateStep) {
|
||||
ConfigurationEntryStore<UpdateVersionInfo> store = createGlobalStore();
|
||||
storeNewVersion(store, updateStep);
|
||||
@@ -65,6 +71,10 @@ class UpdateStepStore {
|
||||
return notRunYet(createRepositoryStore(repositoryId), updateStep);
|
||||
}
|
||||
|
||||
boolean notRunYet(String namespace, NamespaceUpdateStep updateStep) {
|
||||
return notRunYet(createNamespaceStore(namespace), updateStep);
|
||||
}
|
||||
|
||||
private void storeNewVersion(ConfigurationEntryStore<UpdateVersionInfo> store, UpdateStepTarget updateStep) {
|
||||
UpdateVersionInfo newVersionInfo = new UpdateVersionInfo(updateStep.getTargetVersion().getParsedVersion());
|
||||
store.put(updateStep.getAffectedDataType(), newVersionInfo);
|
||||
@@ -99,4 +109,12 @@ class UpdateStepStore {
|
||||
.forRepository(repositoryId)
|
||||
.build();
|
||||
}
|
||||
|
||||
private ConfigurationEntryStore<UpdateVersionInfo> createNamespaceStore(String namespace) {
|
||||
return storeFactory
|
||||
.withType(UpdateVersionInfo.class)
|
||||
.withName(STORE_NAME)
|
||||
.forNamespace(namespace)
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,6 +26,8 @@ package sonia.scm.update;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import sonia.scm.migration.NamespaceUpdateContext;
|
||||
import sonia.scm.migration.NamespaceUpdateStep;
|
||||
import sonia.scm.migration.RepositoryUpdateContext;
|
||||
import sonia.scm.migration.RepositoryUpdateStep;
|
||||
import sonia.scm.migration.UpdateStep;
|
||||
@@ -52,6 +54,7 @@ class UpdateEngineTest {
|
||||
|
||||
ConfigurationEntryStoreFactory storeFactory = new InMemoryByteConfigurationEntryStoreFactory();
|
||||
RepositoryUpdateIterator repositoryUpdateIterator = mock(RepositoryUpdateIterator.class, CALLS_REAL_METHODS);
|
||||
NamespaceUpdateIterator namespaceUpdateIterator = mock(NamespaceUpdateIterator.class, CALLS_REAL_METHODS);
|
||||
UpdateStepStore updateStepStore = new UpdateStepStore(storeFactory);
|
||||
|
||||
List<String> processedUpdates = new ArrayList<>();
|
||||
@@ -66,6 +69,16 @@ class UpdateEngineTest {
|
||||
}).when(repositoryUpdateIterator).forEachRepository(any());
|
||||
}
|
||||
|
||||
@BeforeEach
|
||||
void mockNamespaces() {
|
||||
doAnswer(invocation -> {
|
||||
Consumer<String> consumer = invocation.getArgument(0, Consumer.class);
|
||||
consumer.accept("hitchhikers");
|
||||
consumer.accept("vogons");
|
||||
return null;
|
||||
}).when(namespaceUpdateIterator).forEachNamespace(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldProcessStepsInCorrectOrder() {
|
||||
LinkedHashSet<UpdateStep> updateSteps = new LinkedHashSet<>();
|
||||
@@ -74,7 +87,7 @@ class UpdateEngineTest {
|
||||
updateSteps.add(new FixedVersionUpdateStep("test", "1.2.0"));
|
||||
updateSteps.add(new FixedVersionUpdateStep("test", "1.1.0"));
|
||||
|
||||
UpdateEngine updateEngine = new UpdateEngine(updateSteps, emptySet(), repositoryUpdateIterator, updateStepStore);
|
||||
UpdateEngine updateEngine = new UpdateEngine(updateSteps, emptySet(), emptySet(), repositoryUpdateIterator, namespaceUpdateIterator, updateStepStore);
|
||||
updateEngine.update();
|
||||
|
||||
assertThat(processedUpdates)
|
||||
@@ -90,7 +103,7 @@ class UpdateEngineTest {
|
||||
updateSteps.add(new FixedVersionUpdateStep("test", "1.2.0"));
|
||||
repositoryUpdateSteps.add(new FixedVersionUpdateStep("test", "1.1.0"));
|
||||
|
||||
UpdateEngine updateEngine = new UpdateEngine(updateSteps, repositoryUpdateSteps, repositoryUpdateIterator, updateStepStore);
|
||||
UpdateEngine updateEngine = new UpdateEngine(updateSteps, repositoryUpdateSteps, emptySet(), repositoryUpdateIterator, namespaceUpdateIterator, updateStepStore);
|
||||
updateEngine.update();
|
||||
|
||||
assertThat(processedUpdates)
|
||||
@@ -106,13 +119,29 @@ class UpdateEngineTest {
|
||||
repositoryUpdateSteps.add(new FixedVersionUpdateStep("test", "1.1.1"));
|
||||
repositoryUpdateSteps.add(new FixedVersionUpdateStep("test", "1.1.0"));
|
||||
|
||||
UpdateEngine updateEngine = new UpdateEngine(updateSteps, repositoryUpdateSteps, repositoryUpdateIterator, updateStepStore);
|
||||
UpdateEngine updateEngine = new UpdateEngine(updateSteps, repositoryUpdateSteps, emptySet(), repositoryUpdateIterator, namespaceUpdateIterator, updateStepStore);
|
||||
updateEngine.update("1337");
|
||||
|
||||
assertThat(processedUpdates)
|
||||
.containsExactly("test:1.1.0-1337", "test:1.1.1-1337");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldProcessStepsForSingleNamespace() {
|
||||
LinkedHashSet<UpdateStep> updateSteps = new LinkedHashSet<>();
|
||||
LinkedHashSet<NamespaceUpdateStep> namespaceUpdateSteps = new LinkedHashSet<>();
|
||||
|
||||
updateSteps.add(new FixedVersionUpdateStep("test", "1.2.0"));
|
||||
namespaceUpdateSteps.add(new FixedVersionUpdateStep("test", "1.1.1"));
|
||||
namespaceUpdateSteps.add(new FixedVersionUpdateStep("test", "1.1.0"));
|
||||
|
||||
UpdateEngine updateEngine = new UpdateEngine(updateSteps, emptySet(), namespaceUpdateSteps, repositoryUpdateIterator, namespaceUpdateIterator, updateStepStore);
|
||||
updateEngine.update("hitchhikers");
|
||||
|
||||
assertThat(processedUpdates)
|
||||
.containsExactly("test:1.1.0/hitchhikers", "test:1.1.1/hitchhikers");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldProcessCoreStepsBeforeOther() {
|
||||
LinkedHashSet<UpdateStep> updateSteps = new LinkedHashSet<>();
|
||||
@@ -120,7 +149,7 @@ class UpdateEngineTest {
|
||||
updateSteps.add(new FixedVersionUpdateStep("test", "1.2.0"));
|
||||
updateSteps.add(new CoreFixedVersionUpdateStep("core", "1.2.0"));
|
||||
|
||||
UpdateEngine updateEngine = new UpdateEngine(updateSteps, emptySet(), repositoryUpdateIterator, updateStepStore);
|
||||
UpdateEngine updateEngine = new UpdateEngine(updateSteps, emptySet(), emptySet(), repositoryUpdateIterator, namespaceUpdateIterator, updateStepStore);
|
||||
updateEngine.update();
|
||||
|
||||
assertThat(processedUpdates)
|
||||
@@ -132,7 +161,7 @@ class UpdateEngineTest {
|
||||
Set<UpdateStep> updateSteps = singleton(new FixedVersionUpdateStep("test", "1.2.0"));
|
||||
Set<RepositoryUpdateStep> repositoryUpdateSteps = singleton(new FixedVersionUpdateStep("test", "1.2.0"));
|
||||
|
||||
UpdateEngine updateEngine = new UpdateEngine(updateSteps, repositoryUpdateSteps, repositoryUpdateIterator, updateStepStore);
|
||||
UpdateEngine updateEngine = new UpdateEngine(updateSteps, repositoryUpdateSteps, emptySet(), repositoryUpdateIterator, namespaceUpdateIterator, updateStepStore);
|
||||
updateEngine.update();
|
||||
|
||||
assertThat(processedUpdates)
|
||||
@@ -145,12 +174,12 @@ class UpdateEngineTest {
|
||||
|
||||
updateSteps.add(new FixedVersionUpdateStep("test", "1.1.1"));
|
||||
|
||||
UpdateEngine firstUpdateEngine = new UpdateEngine(updateSteps, emptySet(), repositoryUpdateIterator, updateStepStore);
|
||||
UpdateEngine firstUpdateEngine = new UpdateEngine(updateSteps, emptySet(), emptySet(), repositoryUpdateIterator, namespaceUpdateIterator, updateStepStore);
|
||||
firstUpdateEngine.update();
|
||||
|
||||
processedUpdates.clear();
|
||||
|
||||
UpdateEngine secondUpdateEngine = new UpdateEngine(updateSteps, emptySet(), repositoryUpdateIterator, updateStepStore);
|
||||
UpdateEngine secondUpdateEngine = new UpdateEngine(updateSteps, emptySet(), emptySet(), repositoryUpdateIterator, namespaceUpdateIterator, updateStepStore);
|
||||
secondUpdateEngine.update();
|
||||
|
||||
assertThat(processedUpdates).isEmpty();
|
||||
@@ -162,39 +191,58 @@ class UpdateEngineTest {
|
||||
|
||||
repositoryUpdateSteps.add(new FixedVersionUpdateStep("test", "1.1.1"));
|
||||
|
||||
UpdateEngine firstUpdateEngine = new UpdateEngine(emptySet(), repositoryUpdateSteps, repositoryUpdateIterator, updateStepStore);
|
||||
UpdateEngine firstUpdateEngine = new UpdateEngine(emptySet(), repositoryUpdateSteps, emptySet(), repositoryUpdateIterator, namespaceUpdateIterator, updateStepStore);
|
||||
firstUpdateEngine.update();
|
||||
|
||||
processedUpdates.clear();
|
||||
|
||||
repositoryUpdateSteps.add(new FixedVersionUpdateStep("test", "1.2.0"));
|
||||
UpdateEngine secondUpdateEngine = new UpdateEngine(emptySet(), repositoryUpdateSteps, repositoryUpdateIterator, updateStepStore);
|
||||
UpdateEngine secondUpdateEngine = new UpdateEngine(emptySet(), repositoryUpdateSteps, emptySet(), repositoryUpdateIterator, namespaceUpdateIterator, updateStepStore);
|
||||
secondUpdateEngine.update();
|
||||
|
||||
assertThat(processedUpdates)
|
||||
.containsExactly("test:1.2.0-42", "test:1.2.0-1337");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRunNamespaceStepsOnlyOnce() {
|
||||
LinkedHashSet<NamespaceUpdateStep> namespaceUpdateSteps = new LinkedHashSet<>();
|
||||
|
||||
namespaceUpdateSteps.add(new FixedVersionUpdateStep("test", "1.1.1"));
|
||||
|
||||
UpdateEngine firstUpdateEngine = new UpdateEngine(emptySet(), emptySet(), namespaceUpdateSteps, repositoryUpdateIterator, namespaceUpdateIterator, updateStepStore);
|
||||
firstUpdateEngine.update();
|
||||
|
||||
processedUpdates.clear();
|
||||
|
||||
namespaceUpdateSteps.add(new FixedVersionUpdateStep("test", "1.2.0"));
|
||||
UpdateEngine secondUpdateEngine = new UpdateEngine(emptySet(), emptySet(), namespaceUpdateSteps, repositoryUpdateIterator, namespaceUpdateIterator, updateStepStore);
|
||||
secondUpdateEngine.update();
|
||||
|
||||
assertThat(processedUpdates)
|
||||
.containsExactly("test:1.2.0/hitchhikers", "test:1.2.0/vogons");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRunStepsForDifferentTypesIndependently() {
|
||||
LinkedHashSet<UpdateStep> updateSteps = new LinkedHashSet<>();
|
||||
|
||||
updateSteps.add(new FixedVersionUpdateStep("test", "1.1.1"));
|
||||
|
||||
UpdateEngine updateEngine = new UpdateEngine(updateSteps, emptySet(), repositoryUpdateIterator, updateStepStore);
|
||||
UpdateEngine updateEngine = new UpdateEngine(updateSteps, emptySet(), emptySet(), repositoryUpdateIterator, namespaceUpdateIterator, updateStepStore);
|
||||
updateEngine.update();
|
||||
|
||||
processedUpdates.clear();
|
||||
|
||||
updateSteps.add(new FixedVersionUpdateStep("other", "1.1.1"));
|
||||
|
||||
updateEngine = new UpdateEngine(updateSteps, emptySet(), repositoryUpdateIterator, updateStepStore);
|
||||
updateEngine = new UpdateEngine(updateSteps, emptySet(), emptySet(), repositoryUpdateIterator, namespaceUpdateIterator, updateStepStore);
|
||||
updateEngine.update();
|
||||
|
||||
assertThat(processedUpdates).containsExactly("other:1.1.1");
|
||||
}
|
||||
|
||||
class FixedVersionUpdateStep implements UpdateStep, RepositoryUpdateStep {
|
||||
class FixedVersionUpdateStep implements UpdateStep, RepositoryUpdateStep, NamespaceUpdateStep {
|
||||
private final String type;
|
||||
private final String version;
|
||||
|
||||
@@ -222,6 +270,11 @@ class UpdateEngineTest {
|
||||
public void doUpdate(RepositoryUpdateContext repositoryUpdateContext) throws Exception {
|
||||
processedUpdates.add(type + ":" + version + "-" + repositoryUpdateContext.getRepositoryId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doUpdate(NamespaceUpdateContext namespaceUpdateContext) throws Exception {
|
||||
processedUpdates.add(type + ":" + version + "/" + namespaceUpdateContext.getNamespace());
|
||||
}
|
||||
}
|
||||
|
||||
class CoreFixedVersionUpdateStep extends FixedVersionUpdateStep implements CoreUpdateStep {
|
||||
|
||||
Reference in New Issue
Block a user