From 3c94ce91d603ad94cd503e40d0050c8413b6e383 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Thu, 25 Feb 2021 08:12:16 +0100 Subject: [PATCH] Set update step info for new repositories (#1554) Sets versions for repository update steps to newest versions for new repositories to prevent unnecessary updates. --- .../RepositoryUpdateAfterCreationHook.java | 68 ++++++++++++ .../java/sonia/scm/update/UpdateEngine.java | 46 ++------ .../sonia/scm/update/UpdateStepStore.java | 102 ++++++++++++++++++ ...RepositoryUpdateAfterCreationHookTest.java | 93 ++++++++++++++++ .../sonia/scm/update/UpdateEngineTest.java | 23 ++-- 5 files changed, 283 insertions(+), 49 deletions(-) create mode 100644 scm-webapp/src/main/java/sonia/scm/update/RepositoryUpdateAfterCreationHook.java create mode 100644 scm-webapp/src/main/java/sonia/scm/update/UpdateStepStore.java create mode 100644 scm-webapp/src/test/java/sonia/scm/update/RepositoryUpdateAfterCreationHookTest.java diff --git a/scm-webapp/src/main/java/sonia/scm/update/RepositoryUpdateAfterCreationHook.java b/scm-webapp/src/main/java/sonia/scm/update/RepositoryUpdateAfterCreationHook.java new file mode 100644 index 0000000000..ddf033586e --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/update/RepositoryUpdateAfterCreationHook.java @@ -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.update; + +import com.github.legman.Subscribe; +import sonia.scm.EagerSingleton; +import sonia.scm.HandlerEventType; +import sonia.scm.migration.RepositoryUpdateStep; +import sonia.scm.plugin.Extension; +import sonia.scm.repository.RepositoryEvent; + +import javax.inject.Inject; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +@Extension @EagerSingleton +class RepositoryUpdateAfterCreationHook { + + private final List repositorySteps; + private final UpdateStepStore updateStepStore; + + @Inject + RepositoryUpdateAfterCreationHook(Set repositorySteps, UpdateStepStore updateStepStore) { + this.repositorySteps = determineStepsWithHighestVersion(repositorySteps); + this.updateStepStore = updateStepStore; + } + + private ArrayList determineStepsWithHighestVersion(Set repositorySteps) { + List temporarySteps = new ArrayList<>(repositorySteps); + temporarySteps.sort(Comparator.comparing(RepositoryUpdateStep::getTargetVersion).reversed()); + Map stepsForDataType = new HashMap<>(); + temporarySteps.forEach(step -> stepsForDataType.put(step.getAffectedDataType(), step)); + return new ArrayList<>(stepsForDataType.values()); + } + + @Subscribe + public void updateRepository(RepositoryEvent event) { + if (event.getEventType() == HandlerEventType.CREATE) { + repositorySteps.forEach(updateStep -> updateStepStore.storeExecutedUpdate(event.getItem().getId(), updateStep)); + } + } +} diff --git a/scm-webapp/src/main/java/sonia/scm/update/UpdateEngine.java b/scm-webapp/src/main/java/sonia/scm/update/UpdateEngine.java index c59f7c92da..8adda1ff26 100644 --- a/scm-webapp/src/main/java/sonia/scm/update/UpdateEngine.java +++ b/scm-webapp/src/main/java/sonia/scm/update/UpdateEngine.java @@ -31,8 +31,6 @@ import sonia.scm.migration.RepositoryUpdateStep; import sonia.scm.migration.UpdateException; import sonia.scm.migration.UpdateStep; import sonia.scm.migration.UpdateStepTarget; -import sonia.scm.store.ConfigurationEntryStore; -import sonia.scm.store.ConfigurationEntryStoreFactory; import sonia.scm.version.Version; import javax.inject.Inject; @@ -48,23 +46,19 @@ public class UpdateEngine { public static final Logger LOG = LoggerFactory.getLogger(UpdateEngine.class); - private static final String STORE_NAME = "executedUpdates"; private final List steps; - private final ConfigurationEntryStore store; - private final ConfigurationEntryStoreFactory storeFactory; private final RepositoryUpdateIterator repositoryUpdateIterator; + private final UpdateStepStore updateStepStore; @Inject public UpdateEngine( Set globalSteps, Set repositorySteps, - ConfigurationEntryStoreFactory storeFactory, - RepositoryUpdateIterator repositoryUpdateIterator - ) { - this.storeFactory = storeFactory; + RepositoryUpdateIterator repositoryUpdateIterator, + UpdateStepStore updateStepStore) { this.repositoryUpdateIterator = repositoryUpdateIterator; - this.store = storeFactory.withType(UpdateVersionInfo.class).withName(STORE_NAME).build(); + this.updateStepStore = updateStepStore; this.steps = sortSteps(globalSteps, repositorySteps); } @@ -127,32 +121,12 @@ public class UpdateEngine { } } - private void storeNewVersion(ConfigurationEntryStore store, UpdateStepTarget updateStep) { - UpdateVersionInfo newVersionInfo = new UpdateVersionInfo(updateStep.getTargetVersion().getParsedVersion()); - store.put(updateStep.getAffectedDataType(), newVersionInfo); - } - private boolean notRunYet(UpdateStep updateStep) { LOG.trace("checking whether to run update step for type {} and version {}", updateStep.getAffectedDataType(), updateStep.getTargetVersion() ); - return notRunYet(this.store, updateStep); - } - - private boolean notRunYet(ConfigurationEntryStore store, UpdateStepTarget updateStep) { - UpdateVersionInfo updateVersionInfo = store.get(updateStep.getAffectedDataType()); - if (updateVersionInfo == null) { - LOG.trace("no updates for type {} run yet; step will be executed", updateStep.getAffectedDataType()); - return true; - } - boolean result = updateStep.getTargetVersion().isNewer(updateVersionInfo.getLatestVersion()); - LOG.trace("latest version for type {}: {}; step will be executed: {}", - updateStep.getAffectedDataType(), - updateVersionInfo.getLatestVersion(), - result - ); - return result; + return updateStepStore.notRunYet(updateStep); } private abstract static class UpdateStepWrapper implements UpdateStepTarget { @@ -219,7 +193,7 @@ public class UpdateEngine { delegate.getClass().getName() ); delegate.doUpdate(); - storeNewVersion(store, delegate); + updateStepStore.storeExecutedUpdate(delegate); } void doUpdate(String repositoryId) { @@ -261,7 +235,7 @@ public class UpdateEngine { repositoryId ); delegate.doUpdate(new RepositoryUpdateContext(repositoryId)); - storeNewVersion(storeForRepository(repositoryId), delegate); + updateStepStore.storeExecutedUpdate(repositoryId, delegate); } } @@ -271,11 +245,7 @@ public class UpdateEngine { delegate.getTargetVersion(), repositoryId ); - return UpdateEngine.this.notRunYet(storeForRepository(repositoryId), delegate); - } - - private ConfigurationEntryStore storeForRepository(String repositoryId) { - return storeFactory.withType(UpdateVersionInfo.class).withName(STORE_NAME).forRepository(repositoryId).build(); + return updateStepStore.notRunYet(repositoryId, delegate); } } } diff --git a/scm-webapp/src/main/java/sonia/scm/update/UpdateStepStore.java b/scm-webapp/src/main/java/sonia/scm/update/UpdateStepStore.java new file mode 100644 index 0000000000..a44354912b --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/update/UpdateStepStore.java @@ -0,0 +1,102 @@ +/* + * 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 org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import sonia.scm.migration.RepositoryUpdateStep; +import sonia.scm.migration.UpdateStep; +import sonia.scm.migration.UpdateStepTarget; +import sonia.scm.store.ConfigurationEntryStore; +import sonia.scm.store.ConfigurationEntryStoreFactory; + +import javax.inject.Inject; + +class UpdateStepStore { + + private static final String STORE_NAME = "executedUpdates"; + + private static final Logger LOG = LoggerFactory.getLogger(UpdateStepStore.class); + + private final ConfigurationEntryStoreFactory storeFactory; + + @Inject + UpdateStepStore(ConfigurationEntryStoreFactory storeFactory) { + this.storeFactory = storeFactory; + } + + void storeExecutedUpdate(String repositoryId, RepositoryUpdateStep updateStep) { + ConfigurationEntryStore store = createRepositoryStore(repositoryId); + storeNewVersion(store, updateStep); + } + + void storeExecutedUpdate(UpdateStep updateStep) { + ConfigurationEntryStore store = createGlobalStore(); + storeNewVersion(store, updateStep); + } + + boolean notRunYet(UpdateStep updateStep) { + return notRunYet(createGlobalStore(), updateStep); + } + + boolean notRunYet(String repositoryId, RepositoryUpdateStep updateStep) { + return notRunYet(createRepositoryStore(repositoryId), updateStep); + } + + private void storeNewVersion(ConfigurationEntryStore store, UpdateStepTarget updateStep) { + UpdateVersionInfo newVersionInfo = new UpdateVersionInfo(updateStep.getTargetVersion().getParsedVersion()); + store.put(updateStep.getAffectedDataType(), newVersionInfo); + } + + private boolean notRunYet(ConfigurationEntryStore store, UpdateStepTarget updateStep) { + UpdateVersionInfo updateVersionInfo = store.get(updateStep.getAffectedDataType()); + if (updateVersionInfo == null) { + LOG.trace("no updates for type {} run yet; step will be executed", updateStep.getAffectedDataType()); + return true; + } + boolean result = updateStep.getTargetVersion().isNewer(updateVersionInfo.getLatestVersion()); + LOG.trace("latest version for type {}: {}; step will be executed: {}", + updateStep.getAffectedDataType(), + updateVersionInfo.getLatestVersion(), + result + ); + return result; + } + + private ConfigurationEntryStore createGlobalStore() { + return storeFactory + .withType(UpdateVersionInfo.class) + .withName(STORE_NAME) + .build(); + } + + private ConfigurationEntryStore createRepositoryStore(String repositoryId) { + return storeFactory + .withType(UpdateVersionInfo.class) + .withName(STORE_NAME) + .forRepository(repositoryId) + .build(); + } +} diff --git a/scm-webapp/src/test/java/sonia/scm/update/RepositoryUpdateAfterCreationHookTest.java b/scm-webapp/src/test/java/sonia/scm/update/RepositoryUpdateAfterCreationHookTest.java new file mode 100644 index 0000000000..8333450779 --- /dev/null +++ b/scm-webapp/src/test/java/sonia/scm/update/RepositoryUpdateAfterCreationHookTest.java @@ -0,0 +1,93 @@ +/* + * 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 com.google.common.collect.ImmutableSet; +import org.junit.jupiter.api.Test; +import sonia.scm.HandlerEventType; +import sonia.scm.migration.RepositoryUpdateContext; +import sonia.scm.migration.RepositoryUpdateStep; +import sonia.scm.repository.Repository; +import sonia.scm.repository.RepositoryEvent; +import sonia.scm.version.Version; + +import static org.mockito.ArgumentMatchers.argThat; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +class RepositoryUpdateAfterCreationHookTest { + + private static final Repository REPOSITORY = new Repository("1", "git", "space", "X"); + + @Test + void shouldSetHighestVersion() { + UpdateStepStore updateStepStore = mock(UpdateStepStore.class); + + RepositoryUpdateAfterCreationHook hook = new RepositoryUpdateAfterCreationHook( + ImmutableSet.of( + createStep("1.1.0", "test.data"), + createStep("1.0.0", "test.data"), + createStep("2.0.0", "more.data"), + createStep("3.0.0", "more.data") + ), + updateStepStore); + + hook.updateRepository(new RepositoryEvent(HandlerEventType.CREATE, REPOSITORY)); + + verify(updateStepStore).storeExecutedUpdate(eq("1"), updateStepWith("1.1.0", "test.data")); + verify(updateStepStore).storeExecutedUpdate(eq("1"), updateStepWith("3.0", "more.data")); + } + + private RepositoryUpdateStep updateStepWith(String version, String dataType) { + return argThat( + argument -> argument.getTargetVersion().equals(Version.parse(version)) + && argument.getAffectedDataType().equals(dataType) + ); + } + + private RepositoryUpdateStep createStep(String versionString, String dataType) { + return new RepositoryUpdateStep() { + @Override + public void doUpdate(RepositoryUpdateContext repositoryUpdateContext) { + } + + @Override + public Version getTargetVersion() { + return Version.parse(versionString); + } + + @Override + public String getAffectedDataType() { + return dataType; + } + + @Override + public String toString() { + return String.format("Test update step with type %s for version %s", dataType, versionString); + } + }; + } +} diff --git a/scm-webapp/src/test/java/sonia/scm/update/UpdateEngineTest.java b/scm-webapp/src/test/java/sonia/scm/update/UpdateEngineTest.java index 831c774aec..e57ad9737b 100644 --- a/scm-webapp/src/test/java/sonia/scm/update/UpdateEngineTest.java +++ b/scm-webapp/src/test/java/sonia/scm/update/UpdateEngineTest.java @@ -52,6 +52,7 @@ class UpdateEngineTest { ConfigurationEntryStoreFactory storeFactory = new InMemoryConfigurationEntryStoreFactory(); RepositoryUpdateIterator repositoryUpdateIterator = mock(RepositoryUpdateIterator.class, CALLS_REAL_METHODS); + UpdateStepStore updateStepStore = new UpdateStepStore(storeFactory); List processedUpdates = new ArrayList<>(); @@ -73,7 +74,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(), storeFactory, repositoryUpdateIterator); + UpdateEngine updateEngine = new UpdateEngine(updateSteps, emptySet(), repositoryUpdateIterator, updateStepStore); updateEngine.update(); assertThat(processedUpdates) @@ -89,7 +90,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, storeFactory, repositoryUpdateIterator); + UpdateEngine updateEngine = new UpdateEngine(updateSteps, repositoryUpdateSteps, repositoryUpdateIterator, updateStepStore); updateEngine.update(); assertThat(processedUpdates) @@ -105,7 +106,7 @@ class UpdateEngineTest { repositoryUpdateSteps.add(new FixedVersionUpdateStep("test", "1.1.1")); repositoryUpdateSteps.add(new FixedVersionUpdateStep("test", "1.1.0")); - UpdateEngine updateEngine = new UpdateEngine(updateSteps, repositoryUpdateSteps, storeFactory, repositoryUpdateIterator); + UpdateEngine updateEngine = new UpdateEngine(updateSteps, repositoryUpdateSteps, repositoryUpdateIterator, updateStepStore); updateEngine.update("1337"); assertThat(processedUpdates) @@ -119,7 +120,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(), storeFactory, repositoryUpdateIterator); + UpdateEngine updateEngine = new UpdateEngine(updateSteps, emptySet(), repositoryUpdateIterator, updateStepStore); updateEngine.update(); assertThat(processedUpdates) @@ -131,7 +132,7 @@ class UpdateEngineTest { Set updateSteps = singleton(new FixedVersionUpdateStep("test", "1.2.0")); Set repositoryUpdateSteps = singleton(new FixedVersionUpdateStep("test", "1.2.0")); - UpdateEngine updateEngine = new UpdateEngine(updateSteps, repositoryUpdateSteps, storeFactory, repositoryUpdateIterator); + UpdateEngine updateEngine = new UpdateEngine(updateSteps, repositoryUpdateSteps, repositoryUpdateIterator, updateStepStore); updateEngine.update(); assertThat(processedUpdates) @@ -144,12 +145,12 @@ class UpdateEngineTest { updateSteps.add(new FixedVersionUpdateStep("test", "1.1.1")); - UpdateEngine firstUpdateEngine = new UpdateEngine(updateSteps, emptySet(), storeFactory, repositoryUpdateIterator); + UpdateEngine firstUpdateEngine = new UpdateEngine(updateSteps, emptySet(), repositoryUpdateIterator, updateStepStore); firstUpdateEngine.update(); processedUpdates.clear(); - UpdateEngine secondUpdateEngine = new UpdateEngine(updateSteps, emptySet(), storeFactory, repositoryUpdateIterator); + UpdateEngine secondUpdateEngine = new UpdateEngine(updateSteps, emptySet(), repositoryUpdateIterator, updateStepStore); secondUpdateEngine.update(); assertThat(processedUpdates).isEmpty(); @@ -161,13 +162,13 @@ class UpdateEngineTest { repositoryUpdateSteps.add(new FixedVersionUpdateStep("test", "1.1.1")); - UpdateEngine firstUpdateEngine = new UpdateEngine(emptySet(), repositoryUpdateSteps, storeFactory, repositoryUpdateIterator); + UpdateEngine firstUpdateEngine = new UpdateEngine(emptySet(), repositoryUpdateSteps, repositoryUpdateIterator, updateStepStore); firstUpdateEngine.update(); processedUpdates.clear(); repositoryUpdateSteps.add(new FixedVersionUpdateStep("test", "1.2.0")); - UpdateEngine secondUpdateEngine = new UpdateEngine(emptySet(), repositoryUpdateSteps, storeFactory, repositoryUpdateIterator); + UpdateEngine secondUpdateEngine = new UpdateEngine(emptySet(), repositoryUpdateSteps, repositoryUpdateIterator, updateStepStore); secondUpdateEngine.update(); assertThat(processedUpdates) @@ -180,14 +181,14 @@ class UpdateEngineTest { updateSteps.add(new FixedVersionUpdateStep("test", "1.1.1")); - UpdateEngine updateEngine = new UpdateEngine(updateSteps, emptySet(), storeFactory, repositoryUpdateIterator); + UpdateEngine updateEngine = new UpdateEngine(updateSteps, emptySet(), repositoryUpdateIterator, updateStepStore); updateEngine.update(); processedUpdates.clear(); updateSteps.add(new FixedVersionUpdateStep("other", "1.1.1")); - updateEngine = new UpdateEngine(updateSteps, emptySet(), storeFactory, repositoryUpdateIterator); + updateEngine = new UpdateEngine(updateSteps, emptySet(), repositoryUpdateIterator, updateStepStore); updateEngine.update(); assertThat(processedUpdates).containsExactly("other:1.1.1");