diff --git a/src/main/java/run/halo/app/extension/DefaultExtensionClient.java b/src/main/java/run/halo/app/extension/DefaultExtensionClient.java deleted file mode 100644 index 9def90158..000000000 --- a/src/main/java/run/halo/app/extension/DefaultExtensionClient.java +++ /dev/null @@ -1,128 +0,0 @@ -package run.halo.app.extension; - -import java.time.Instant; -import java.util.Comparator; -import java.util.List; -import java.util.Optional; -import java.util.function.Predicate; -import org.springframework.stereotype.Component; -import org.springframework.util.Assert; -import run.halo.app.extension.store.ExtensionStoreClient; - -/** - * DefaultExtensionClient is default implementation of ExtensionClient. - * - * @author johnniang - */ -@Component -public class DefaultExtensionClient implements ExtensionClient { - - private final ExtensionStoreClient storeClient; - private final ExtensionConverter converter; - - private final SchemeManager schemeManager; - - private final Watcher.WatcherComposite watchers; - - private final ReactiveExtensionClient reactiveClient; - - public DefaultExtensionClient(ExtensionStoreClient storeClient, - ExtensionConverter converter, - SchemeManager schemeManager, ReactiveExtensionClient reactiveClient) { - this.storeClient = storeClient; - this.converter = converter; - this.schemeManager = schemeManager; - this.reactiveClient = reactiveClient; - - watchers = new Watcher.WatcherComposite(); - } - - @Override - public List list(Class type, Predicate predicate, - Comparator comparator) { - var scheme = schemeManager.get(type); - var storeNamePrefix = ExtensionUtil.buildStoreNamePrefix(scheme); - - var storesStream = storeClient.listByNamePrefix(storeNamePrefix).stream() - .map(extensionStore -> converter.convertFrom(type, extensionStore)); - if (predicate != null) { - storesStream = storesStream.filter(predicate); - } - if (comparator != null) { - storesStream = storesStream.sorted(comparator); - } - return storesStream.toList(); - } - - @Override - public ListResult list(Class type, Predicate predicate, - Comparator comparators, int page, int size) { - var extensions = list(type, predicate, comparators); - var extensionStream = extensions.stream(); - if (page > 0) { - extensionStream = extensionStream.skip(((long) (page - 1)) * (long) size); - } - if (size > 0) { - extensionStream = extensionStream.limit(size); - } - var content = extensionStream.toList(); - - return new ListResult<>(page, size, extensions.size(), content); - } - - @Override - public Optional fetch(Class type, String name) { - return fetch(schemeManager.get(type), name, type); - } - - @Override - public Optional fetch(GroupVersionKind gvk, String name) { - return fetch(schemeManager.get(gvk), name, Unstructured.class); - } - - private Optional fetch(Scheme scheme, String name, Class type) { - var storeName = ExtensionUtil.buildStoreName(scheme, name); - return storeClient.fetchByName(storeName) - .map(extensionStore -> converter.convertFrom(type, extensionStore)); - } - - @Override - public void create(E extension) { - var metadata = extension.getMetadata(); - metadata.setCreationTimestamp(Instant.now()); - // extension.setMetadata(metadata); - var extensionStore = converter.convertTo(extension); - var createdStore = storeClient.create(extensionStore.getName(), extensionStore.getData()); - var createdExt = converter.convertFrom(extension.getClass(), createdStore); - watchers.onAdd(createdExt); - } - - @Override - public void update(E extension) { - var extensionStore = converter.convertTo(extension); - Assert.notNull(extension.getMetadata().getVersion(), - "Extension version must not be null when updating"); - var updatedStore = storeClient.update(extensionStore.getName(), extensionStore.getVersion(), - extensionStore.getData()); - var updatedExt = converter.convertFrom(extension.getClass(), updatedStore); - watchers.onUpdate(extension, updatedExt); - } - - @Override - public void delete(E extension) { - extension.getMetadata().setDeletionTimestamp(Instant.now()); - var extensionStore = converter.convertTo(extension); - var deleteStore = storeClient.update(extensionStore.getName(), extensionStore.getVersion(), - extensionStore.getData()); - Extension deleteExt = converter.convertFrom(extension.getClass(), deleteStore); - watchers.onDelete(deleteExt); - } - - @Override - public void watch(Watcher watcher) { - this.watchers.addWatcher(watcher); - // TODO Refactor the watch process. At present, we have to ensure the compatibility. - reactiveClient.watch(watcher); - } - -} diff --git a/src/main/java/run/halo/app/extension/DelegateExtensionClient.java b/src/main/java/run/halo/app/extension/DelegateExtensionClient.java new file mode 100644 index 000000000..571d6519f --- /dev/null +++ b/src/main/java/run/halo/app/extension/DelegateExtensionClient.java @@ -0,0 +1,64 @@ +package run.halo.app.extension; + +import java.util.Comparator; +import java.util.List; +import java.util.Optional; +import java.util.function.Predicate; +import org.springframework.stereotype.Component; + +/** + * DelegateExtensionClient fully delegates ReactiveExtensionClient. + * + * @author johnniang + */ +@Component +public class DelegateExtensionClient implements ExtensionClient { + + private final ReactiveExtensionClient client; + + public DelegateExtensionClient(ReactiveExtensionClient client) { + this.client = client; + } + + @Override + public List list(Class type, Predicate predicate, + Comparator comparator) { + return client.list(type, predicate, comparator).collectList().block(); + } + + @Override + public ListResult list(Class type, Predicate predicate, + Comparator comparator, int page, int size) { + return client.list(type, predicate, comparator, page, size).block(); + } + + @Override + public Optional fetch(Class type, String name) { + return client.fetch(type, name).blockOptional(); + } + + @Override + public Optional fetch(GroupVersionKind gvk, String name) { + return client.fetch(gvk, name).blockOptional(); + } + + @Override + public void create(E extension) { + client.create(extension).block(); + } + + @Override + public void update(E extension) { + client.update(extension).block(); + } + + @Override + public void delete(E extension) { + client.delete(extension).block(); + } + + @Override + public void watch(Watcher watcher) { + client.watch(watcher); + } +} diff --git a/src/main/java/run/halo/app/extension/ExtensionClient.java b/src/main/java/run/halo/app/extension/ExtensionClient.java index a2f37278e..ff4753fcf 100644 --- a/src/main/java/run/halo/app/extension/ExtensionClient.java +++ b/src/main/java/run/halo/app/extension/ExtensionClient.java @@ -10,9 +10,9 @@ import java.util.function.Predicate; * ExtensionStore. * * @author johnniang - * @deprecated Use {@link ReactiveExtensionClient} instead. + * @apiNote Please note that this client can only use in non-reactive environment. If you want to + * use Extension client in reactive environment, please use {@link ReactiveExtensionClient} instead. */ -@Deprecated(forRemoval = true, since = "2.0") public interface ExtensionClient { /** diff --git a/src/test/java/run/halo/app/extension/DefaultExtensionClientTest.java b/src/test/java/run/halo/app/extension/DefaultExtensionClientTest.java deleted file mode 100644 index de3b8350a..000000000 --- a/src/test/java/run/halo/app/extension/DefaultExtensionClientTest.java +++ /dev/null @@ -1,395 +0,0 @@ -package run.halo.app.extension; - -import static java.util.Collections.emptyList; -import static java.util.Collections.reverseOrder; -import static java.util.Comparator.comparing; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.ArgumentMatchers.isA; -import static org.mockito.Mockito.doNothing; -import static org.mockito.Mockito.lenient; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; -import static run.halo.app.extension.GroupVersionKind.fromAPIVersionAndKind; -import static run.halo.app.extension.Scheme.buildFromType; - -import com.fasterxml.jackson.core.JsonProcessingException; -import java.util.List; -import java.util.Optional; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Nested; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; -import run.halo.app.extension.exception.SchemeNotFoundException; -import run.halo.app.extension.store.ExtensionStore; -import run.halo.app.extension.store.ExtensionStoreClient; - -@ExtendWith(MockitoExtension.class) -class DefaultExtensionClientTest { - - static final Scheme fakeScheme = Scheme.buildFromType(FakeExtension.class); - - @Mock - ExtensionStoreClient storeClient; - - @Mock - ExtensionConverter converter; - - @Mock - SchemeManager schemeManager; - - @Mock - ReactiveExtensionClient reactiveClient; - - @InjectMocks - DefaultExtensionClient client; - - @BeforeEach - void setUp() { - lenient().when(schemeManager.get(eq(FakeExtension.class))) - .thenReturn(buildFromType(FakeExtension.class)); - } - - FakeExtension createFakeExtension(String name, Long version) { - var fake = new FakeExtension(); - var metadata = new Metadata(); - metadata.setName(name); - metadata.setVersion(version); - - fake.setMetadata(metadata); - fake.setApiVersion("fake.halo.run/v1alpha1"); - fake.setKind("Fake"); - - return fake; - } - - ExtensionStore createExtensionStore(String name) { - return createExtensionStore(name, null); - } - - ExtensionStore createExtensionStore(String name, Long version) { - var extensionStore = new ExtensionStore(); - extensionStore.setName(name); - extensionStore.setVersion(version); - return extensionStore; - } - - Unstructured createUnstructured() throws JsonProcessingException { - String extensionJson = """ - { - "apiVersion": "fake.halo.run/v1alpha1", - "kind": "Fake", - "metadata": { - "labels": { - "category": "fake", - "default": "true" - }, - "name": "fake", - "creationTimestamp": "2011-12-03T10:15:30Z", - "version": 12345 - } - } - """; - return Unstructured.OBJECT_MAPPER.readValue(extensionJson, Unstructured.class); - } - - @Test - void shouldThrowSchemeNotFoundExceptionWhenSchemeNotRegistered() { - class UnRegisteredExtension extends AbstractExtension { - } - - when(schemeManager.get(eq(UnRegisteredExtension.class))) - .thenThrow(SchemeNotFoundException.class); - when(schemeManager.get(isA(GroupVersionKind.class))) - .thenThrow(SchemeNotFoundException.class); - - assertThrows(SchemeNotFoundException.class, - () -> client.list(UnRegisteredExtension.class, null, null)); - assertThrows(SchemeNotFoundException.class, - () -> client.list(UnRegisteredExtension.class, null, null, 0, 10)); - assertThrows(SchemeNotFoundException.class, - () -> client.fetch(UnRegisteredExtension.class, "fake")); - assertThrows(SchemeNotFoundException.class, () -> - client.fetch(fromAPIVersionAndKind("fake.halo.run/v1alpha1", "UnRegistered"), "fake")); - assertThrows(SchemeNotFoundException.class, () -> { - when(converter.convertTo(any())).thenThrow(SchemeNotFoundException.class); - client.create(createFakeExtension("fake", null)); - }); - assertThrows(SchemeNotFoundException.class, () -> { - when(converter.convertTo(any())).thenThrow(SchemeNotFoundException.class); - client.update(createFakeExtension("fake", 1L)); - }); - assertThrows(SchemeNotFoundException.class, () -> { - when(converter.convertTo(any())).thenThrow(SchemeNotFoundException.class); - client.delete(createFakeExtension("fake", 1L)); - }); - } - - @Test - void shouldReturnEmptyExtensions() { - when(storeClient.listByNamePrefix(anyString())).thenReturn(emptyList()); - var fakes = client.list(FakeExtension.class, null, null); - assertEquals(emptyList(), fakes); - } - - @Test - void shouldReturnExtensionsWithFilterAndSorter() { - var fake1 = createFakeExtension("fake-01", 1L); - var fake2 = createFakeExtension("fake-02", 1L); - - when( - converter.convertFrom(FakeExtension.class, createExtensionStore("fake-01"))).thenReturn( - fake1); - when( - converter.convertFrom(FakeExtension.class, createExtensionStore("fake-02"))).thenReturn( - fake2); - when(storeClient.listByNamePrefix(anyString())).thenReturn( - List.of(createExtensionStore("fake-01"), createExtensionStore("fake-02"))); - - // without filter and sorter - var fakes = client.list(FakeExtension.class, null, null); - assertEquals(List.of(fake1, fake2), fakes); - - // with filter - fakes = client.list(FakeExtension.class, fake -> { - String name = fake.getMetadata().getName(); - return "fake-01".equals(name); - }, null); - assertEquals(List.of(fake1), fakes); - - // with sorter - fakes = client.list(FakeExtension.class, null, - reverseOrder(comparing(fake -> fake.getMetadata().getName()))); - assertEquals(List.of(fake2, fake1), fakes); - } - - @Test - void shouldQueryPageableAndCorrectly() { - var fake1 = createFakeExtension("fake-01", 1L); - var fake2 = createFakeExtension("fake-02", 1L); - var fake3 = createFakeExtension("fake-03", 1L); - - when( - converter.convertFrom(FakeExtension.class, createExtensionStore("fake-01"))).thenReturn( - fake1); - when( - converter.convertFrom(FakeExtension.class, createExtensionStore("fake-02"))).thenReturn( - fake2); - when( - converter.convertFrom(FakeExtension.class, createExtensionStore("fake-03"))).thenReturn( - fake3); - - when(storeClient.listByNamePrefix(anyString())).thenReturn( - List.of(createExtensionStore("fake-01"), createExtensionStore("fake-02"), - createExtensionStore("fake-03"))); - - // without filter and sorter. - var fakes = client.list(FakeExtension.class, null, null, 1, 10); - assertEquals(new ListResult<>(1, 10, 3, List.of(fake1, fake2, fake3)), fakes); - - // out of page range - fakes = client.list(FakeExtension.class, null, null, 100, 10); - assertEquals(new ListResult<>(100, 10, 3, emptyList()), fakes); - - // with filter only - fakes = - client.list(FakeExtension.class, fake -> "fake-03".equals(fake.getMetadata().getName()), - null, 1, 10); - assertEquals(new ListResult<>(1, 10, 1, List.of(fake3)), fakes); - - // with sorter only - fakes = client.list(FakeExtension.class, null, - reverseOrder(comparing(fake -> fake.getMetadata().getName())), 1, 10); - assertEquals(new ListResult<>(1, 10, 3, List.of(fake3, fake2, fake1)), fakes); - - // without page - fakes = client.list(FakeExtension.class, null, null, 0, 0); - assertEquals(new ListResult<>(0, 0, 3, List.of(fake1, fake2, fake3)), fakes); - } - - @Test - void shouldFetchNothing() { - when(storeClient.fetchByName(any())).thenReturn(Optional.empty()); - - Optional fake = client.fetch(FakeExtension.class, "fake"); - - assertEquals(Optional.empty(), fake); - verify(converter, times(0)).convertFrom(any(), any()); - verify(storeClient, times(1)).fetchByName(any()); - } - - @Test - void shouldNotFetchUnstructured() { - when(schemeManager.get(isA(GroupVersionKind.class))) - .thenReturn(fakeScheme); - when(storeClient.fetchByName(any())).thenReturn(Optional.empty()); - var unstructuredFake = client.fetch(fakeScheme.groupVersionKind(), "fake"); - - assertEquals(Optional.empty(), unstructuredFake); - verify(converter, times(0)).convertFrom(any(), any()); - verify(schemeManager, times(1)).get(isA(GroupVersionKind.class)); - verify(storeClient, times(1)).fetchByName(any()); - } - - @Test - void shouldFetchAnExtension() { - var storeName = "/registry/fake.halo.run/fakes/fake"; - when(storeClient.fetchByName(storeName)).thenReturn( - Optional.of(createExtensionStore(storeName))); - - when( - converter.convertFrom(FakeExtension.class, createExtensionStore(storeName))).thenReturn( - createFakeExtension("fake", 1L)); - - Optional fake = client.fetch(FakeExtension.class, "fake"); - assertEquals(Optional.of(createFakeExtension("fake", 1L)), fake); - - verify(storeClient, times(1)).fetchByName(eq(storeName)); - verify(converter, times(1)).convertFrom(eq(FakeExtension.class), - eq(createExtensionStore(storeName))); - } - - @Test - void shouldFetchUnstructuredExtension() throws JsonProcessingException { - var storeName = "/registry/fake.halo.run/fakes/fake"; - when(storeClient.fetchByName(storeName)).thenReturn( - Optional.of(createExtensionStore(storeName))); - when(schemeManager.get(isA(GroupVersionKind.class))) - .thenReturn(fakeScheme); - when(converter.convertFrom(Unstructured.class, createExtensionStore(storeName))) - .thenReturn(createUnstructured()); - - var fake = client.fetch(fakeScheme.groupVersionKind(), "fake"); - - assertEquals(Optional.of(createUnstructured()), fake); - verify(storeClient, times(1)).fetchByName(eq(storeName)); - verify(schemeManager, times(1)).get(isA(GroupVersionKind.class)); - verify(converter, times(1)).convertFrom(eq(Unstructured.class), - eq(createExtensionStore(storeName))); - } - - @Test - void shouldCreateSuccessfully() { - var fake = createFakeExtension("fake", null); - when(converter.convertTo(any())).thenReturn( - createExtensionStore("/registry/fake.halo.run/fakes/fake")); - when(storeClient.create(any(), any())).thenReturn( - createExtensionStore("/registry/fake.halo.run/fakes/fake")); - - client.create(fake); - - verify(converter, times(1)).convertTo(eq(fake)); - verify(storeClient, times(1)).create(eq("/registry/fake.halo.run/fakes/fake"), any()); - assertNotNull(fake.getMetadata().getCreationTimestamp()); - } - - @Test - void shouldCreateUsingUnstructuredSuccessfully() throws JsonProcessingException { - var fake = createUnstructured(); - - when(converter.convertTo(any())).thenReturn( - createExtensionStore("/registry/fake.halo.run/fakes/fake")); - when(storeClient.create(any(), any())).thenReturn( - createExtensionStore("/registry/fake.halo.run/fakes/fake")); - - client.create(fake); - - verify(converter, times(1)).convertTo(eq(fake)); - verify(storeClient, times(1)).create(eq("/registry/fake.halo.run/fakes/fake"), any()); - assertNotNull(fake.getMetadata().getCreationTimestamp()); - } - - @Test - void shouldUpdateSuccessfully() { - var fake = createFakeExtension("fake", 2L); - when(converter.convertTo(any())).thenReturn( - createExtensionStore("/registry/fake.halo.run/fakes/fake", 2L)); - when(storeClient.update(any(), any(), any())).thenReturn( - createExtensionStore("/registry/fake.halo.run/fakes/fake", 2L)); - - client.update(fake); - - verify(converter, times(1)).convertTo(eq(fake)); - verify(storeClient, times(1)) - .update(eq("/registry/fake.halo.run/fakes/fake"), eq(2L), any()); - } - - @Test - void shouldUpdateUnstructuredSuccessfully() throws JsonProcessingException { - var fake = createUnstructured(); - when(converter.convertTo(any())).thenReturn( - createExtensionStore("/registry/fake.halo.run/fakes/fake", 12345L)); - when(storeClient.update(any(), any(), any())).thenReturn( - createExtensionStore("/registry/fake.halo.run/fakes/fake", 12345L)); - - client.update(fake); - - verify(converter, times(1)).convertTo(eq(fake)); - verify(storeClient, times(1)) - .update(eq("/registry/fake.halo.run/fakes/fake"), eq(12345L), any()); - } - - @Test - void shouldDeleteSuccessfully() { - var fake = createFakeExtension("fake", 2L); - when(converter.convertTo(any())).thenReturn( - createExtensionStore("/registry/fake.halo.run/fakes/fake")); - when(storeClient.update(any(), any(), any())).thenReturn( - createExtensionStore("/registry/fake.halo.run/fakes/fake")); - - client.delete(fake); - - verify(converter, times(1)).convertTo(any()); - verify(storeClient, times(1)).update(any(), any(), any()); - verify(storeClient, never()).delete(any(), any()); - } - - @Nested - @DisplayName("Extension watcher test") - class WatcherTest { - - @Mock - Watcher watcher; - - @BeforeEach - void setUp() { - client.watch(watcher); - verify(reactiveClient).watch(watcher); - } - - @Test - void shouldWatchOnAddSuccessfully() { - doNothing().when(watcher).onAdd(any()); - shouldCreateSuccessfully(); - - verify(watcher, times(1)).onAdd(any()); - } - - @Test - void shouldWatchOnUpdateSuccessfully() { - doNothing().when(watcher).onUpdate(any(), any()); - shouldUpdateSuccessfully(); - - verify(watcher, times(1)).onUpdate(any(), any()); - } - - @Test - void shouldWatchOnDeleteSuccessfully() { - doNothing().when(watcher).onDelete(any()); - shouldDeleteSuccessfully(); - - verify(watcher, times(1)).onDelete(any()); - } - } - -} \ No newline at end of file