From fc1856ab6356aefd14d44189ec82a4eab5813917 Mon Sep 17 00:00:00 2001 From: guqing Date: Wed, 15 Nov 2023 18:15:18 +0800 Subject: [PATCH 1/2] feat: add supports for provide theme templates in plugin class path --- .../halo/app/theme/TemplateNameResolver.java | 45 ++++++++ .../plugin/PluginApplicationInitializer.java | 7 ++ .../theme/DefaultTemplateNameResolver.java | 56 ++++++++++ ...lver.java => DefaultViewNameResolver.java} | 19 +++- .../halo/app/theme/TemplateEngineManager.java | 23 +++- .../run/halo/app/theme/ViewNameResolver.java | 20 ++++ .../PluginClassloaderTemplateResolver.java | 105 ++++++++++++++++++ .../theme/router/PreviewRouterFunction.java | 1 + .../app/theme/router/SinglePageRoute.java | 1 + .../factories/CategoryPostRouteFactory.java | 2 +- .../router/factories/PostRouteFactory.java | 2 +- .../{router => }/ViewNameResolverTest.java | 8 +- ...PluginClassloaderTemplateResolverTest.java | 53 +++++++++ .../router/PreviewRouterFunctionTest.java | 9 +- .../app/theme/router/SinglePageRouteTest.java | 3 +- .../factories/PostRouteFactoryTest.java | 4 +- 16 files changed, 337 insertions(+), 21 deletions(-) create mode 100644 api/src/main/java/run/halo/app/theme/TemplateNameResolver.java create mode 100644 application/src/main/java/run/halo/app/theme/DefaultTemplateNameResolver.java rename application/src/main/java/run/halo/app/theme/{router/ViewNameResolver.java => DefaultViewNameResolver.java} (76%) create mode 100644 application/src/main/java/run/halo/app/theme/ViewNameResolver.java create mode 100644 application/src/main/java/run/halo/app/theme/engine/PluginClassloaderTemplateResolver.java rename application/src/test/java/run/halo/app/theme/{router => }/ViewNameResolverTest.java (94%) create mode 100644 application/src/test/java/run/halo/app/theme/engine/PluginClassloaderTemplateResolverTest.java diff --git a/api/src/main/java/run/halo/app/theme/TemplateNameResolver.java b/api/src/main/java/run/halo/app/theme/TemplateNameResolver.java new file mode 100644 index 0000000000..96ebf6722b --- /dev/null +++ b/api/src/main/java/run/halo/app/theme/TemplateNameResolver.java @@ -0,0 +1,45 @@ +package run.halo.app.theme; + +import org.springframework.web.server.ServerWebExchange; +import reactor.core.publisher.Mono; + +/** + *

The {@link TemplateNameResolver} is used to resolve template name.

+ * Halo has a theme mechanism, template files are provided by different themes, so + * we need a method to determine whether the template file exists in the activated theme and if + * it does not exist, provide a default template name. + * + * @author guqing + * @since 2.11.0 + */ +public interface TemplateNameResolver { + + /** + * Resolve template name if exists or default template name in classpath. + * + * @param exchange exchange to resolve theme to use + * @param name template + * @return template name if exists or default template name in classpath + */ + Mono resolveTemplateNameOrDefault(ServerWebExchange exchange, String name); + + /** + * Resolve template name if exists or default template given. + * + * @param exchange exchange to resolve theme to use + * @param name template name + * @param defaultName default template name to use if given template name not exists + * @return template name if exists or default template name given + */ + Mono resolveTemplateNameOrDefault(ServerWebExchange exchange, String name, + String defaultName); + + /** + * Determine whether the template file exists in the current theme. + * + * @param exchange exchange to resolve theme to use + * @param name template name + * @return true if the template file exists in the current theme, false otherwise + */ + Mono isTemplateAvailableInTheme(ServerWebExchange exchange, String name); +} diff --git a/application/src/main/java/run/halo/app/plugin/PluginApplicationInitializer.java b/application/src/main/java/run/halo/app/plugin/PluginApplicationInitializer.java index 45c9d367f6..9fe5ad8c0e 100644 --- a/application/src/main/java/run/halo/app/plugin/PluginApplicationInitializer.java +++ b/application/src/main/java/run/halo/app/plugin/PluginApplicationInitializer.java @@ -26,6 +26,8 @@ import reactor.core.Exceptions; import run.halo.app.extension.ReactiveExtensionClient; import run.halo.app.infra.properties.HaloProperties; +import run.halo.app.theme.DefaultTemplateNameResolver; +import run.halo.app.theme.DefaultViewNameResolver; /** * Plugin application initializer will create plugin application context by plugin id and @@ -90,9 +92,14 @@ private PluginApplicationContext createPluginApplicationContext(String pluginId) stopWatch.stop(); beanFactory.registerSingleton("pluginContext", createPluginContext(plugin)); + // TODO deprecated beanFactory.registerSingleton("pluginWrapper", haloPluginManager.getPlugin(pluginId)); + beanFactory.registerSingleton("templateNameResolver", + new DefaultTemplateNameResolver( + rootApplicationContext.getBean(DefaultViewNameResolver.class), + pluginApplicationContext)); populateSettingFetcher(pluginId, beanFactory); log.debug("Total millis: {} ms -> {}", stopWatch.getTotalTimeMillis(), diff --git a/application/src/main/java/run/halo/app/theme/DefaultTemplateNameResolver.java b/application/src/main/java/run/halo/app/theme/DefaultTemplateNameResolver.java new file mode 100644 index 0000000000..5ecd1a4ac4 --- /dev/null +++ b/application/src/main/java/run/halo/app/theme/DefaultTemplateNameResolver.java @@ -0,0 +1,56 @@ +package run.halo.app.theme; + +import static run.halo.app.plugin.PluginConst.SYSTEM_PLUGIN_NAME; + +import org.apache.commons.lang3.StringUtils; +import org.springframework.context.ApplicationContext; +import org.springframework.web.server.ServerWebExchange; +import reactor.core.publisher.Mono; +import run.halo.app.plugin.PluginApplicationContext; + +/** + * A default implementation of {@link TemplateNameResolver}, It will be provided for plugins to + * resolve template name. + * + * @author guqing + * @since 2.11.0 + */ +public class DefaultTemplateNameResolver implements TemplateNameResolver { + + private final ApplicationContext applicationContext; + private final ViewNameResolver viewNameResolver; + + public DefaultTemplateNameResolver(ViewNameResolver viewNameResolver, + ApplicationContext applicationContext) { + this.applicationContext = applicationContext; + this.viewNameResolver = viewNameResolver; + } + + @Override + public Mono resolveTemplateNameOrDefault(ServerWebExchange exchange, String name) { + if (applicationContext instanceof PluginApplicationContext pluginApplicationContext) { + var pluginName = pluginApplicationContext.getPluginId(); + return this.resolveTemplateNameOrDefault(exchange, name, + pluginClassPathTemplate(pluginName, name)); + } + return resolveTemplateNameOrDefault(exchange, name, + pluginClassPathTemplate(SYSTEM_PLUGIN_NAME, name)); + } + + @Override + public Mono resolveTemplateNameOrDefault(ServerWebExchange exchange, String name, + String defaultName) { + return viewNameResolver.resolveViewNameOrDefault(exchange, name, defaultName); + } + + @Override + public Mono isTemplateAvailableInTheme(ServerWebExchange exchange, String name) { + return this.resolveTemplateNameOrDefault(exchange, name, "") + .filter(StringUtils::isNotBlank) + .hasElement(); + } + + String pluginClassPathTemplate(String pluginName, String templateName) { + return "plugin:" + pluginName + ":" + templateName; + } +} diff --git a/application/src/main/java/run/halo/app/theme/router/ViewNameResolver.java b/application/src/main/java/run/halo/app/theme/DefaultViewNameResolver.java similarity index 76% rename from application/src/main/java/run/halo/app/theme/router/ViewNameResolver.java rename to application/src/main/java/run/halo/app/theme/DefaultViewNameResolver.java index de53492ca3..9c6e56ae53 100644 --- a/application/src/main/java/run/halo/app/theme/router/ViewNameResolver.java +++ b/application/src/main/java/run/halo/app/theme/DefaultViewNameResolver.java @@ -1,4 +1,4 @@ -package run.halo.app.theme.router; +package run.halo.app.theme; import java.nio.file.Files; import lombok.AllArgsConstructor; @@ -7,18 +7,18 @@ import org.springframework.stereotype.Component; import org.springframework.util.Assert; import org.springframework.web.reactive.function.server.ServerRequest; +import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Mono; -import run.halo.app.theme.ThemeResolver; /** - * The {@link ViewNameResolver} is used to resolve view name. + * The {@link DefaultViewNameResolver} is used to resolve view name. * * @author guqing * @since 2.0.0 */ @Component @AllArgsConstructor -public class ViewNameResolver { +public class DefaultViewNameResolver implements ViewNameResolver { private static final String TEMPLATES = "templates"; private final ThemeResolver themeResolver; private final ThymeleafProperties thymeleafProperties; @@ -27,12 +27,13 @@ public class ViewNameResolver { * Resolves view name. * If the {@param #name} cannot be resolved to the view, the {@param #defaultName} is returned. */ - public Mono resolveViewNameOrDefault(ServerRequest request, String name, + @Override + public Mono resolveViewNameOrDefault(ServerWebExchange exchange, String name, String defaultName) { if (StringUtils.isBlank(name)) { return Mono.justOrEmpty(defaultName); } - return themeResolver.getTheme(request.exchange()) + return themeResolver.getTheme(exchange) .mapNotNull(themeContext -> { String templateResourceName = computeResourceName(name); var resourcePath = themeContext.getPath() @@ -43,6 +44,12 @@ public Mono resolveViewNameOrDefault(ServerRequest request, String name, .switchIfEmpty(Mono.justOrEmpty(defaultName)); } + @Override + public Mono resolveViewNameOrDefault(ServerRequest request, String name, + String defaultName) { + return resolveViewNameOrDefault(request.exchange(), name, defaultName); + } + String computeResourceName(String name) { Assert.notNull(name, "Name must not be null"); return StringUtils.endsWith(name, thymeleafProperties.getSuffix()) diff --git a/application/src/main/java/run/halo/app/theme/TemplateEngineManager.java b/application/src/main/java/run/halo/app/theme/TemplateEngineManager.java index cf01ec870d..c8d492237f 100644 --- a/application/src/main/java/run/halo/app/theme/TemplateEngineManager.java +++ b/application/src/main/java/run/halo/app/theme/TemplateEngineManager.java @@ -2,6 +2,7 @@ import java.io.FileNotFoundException; import java.nio.file.Path; +import lombok.NonNull; import org.springframework.beans.factory.ObjectProvider; import org.springframework.boot.autoconfigure.thymeleaf.ThymeleafProperties; import org.springframework.stereotype.Component; @@ -17,8 +18,10 @@ import reactor.core.publisher.Mono; import run.halo.app.infra.ExternalUrlSupplier; import run.halo.app.infra.exception.NotFoundException; +import run.halo.app.plugin.HaloPluginManager; import run.halo.app.theme.dialect.HaloProcessorDialect; import run.halo.app.theme.engine.HaloTemplateEngine; +import run.halo.app.theme.engine.PluginClassloaderTemplateResolver; import run.halo.app.theme.message.ThemeMessageResolver; /** @@ -45,6 +48,8 @@ public class TemplateEngineManager { private final ExternalUrlSupplier externalUrlSupplier; + private final HaloPluginManager haloPluginManager; + private final ObjectProvider templateResolvers; private final ObjectProvider dialects; @@ -53,10 +58,11 @@ public class TemplateEngineManager { public TemplateEngineManager(ThymeleafProperties thymeleafProperties, ExternalUrlSupplier externalUrlSupplier, - ObjectProvider templateResolvers, + HaloPluginManager haloPluginManager, ObjectProvider templateResolvers, ObjectProvider dialects, ThemeResolver themeResolver) { this.thymeleafProperties = thymeleafProperties; this.externalUrlSupplier = externalUrlSupplier; + this.haloPluginManager = haloPluginManager; this.templateResolvers = templateResolvers; this.dialects = dialects; this.themeResolver = themeResolver; @@ -119,6 +125,8 @@ private ISpringWebFluxTemplateEngine templateEngineGenerator(CacheKey cacheKey) var mainResolver = haloTemplateResolver(); mainResolver.setPrefix(cacheKey.context().getPath().resolve("templates") + "/"); engine.addTemplateResolver(mainResolver); + var pluginTemplateResolver = createPluginClassloaderTemplateResolver(); + engine.addTemplateResolver(pluginTemplateResolver); // replace StandardDialect with SpringStandardDialect engine.setDialect(new SpringStandardDialect() { @Override @@ -134,6 +142,19 @@ public IStandardVariableExpressionEvaluator getVariableExpressionEvaluator() { return engine; } + @NonNull + private PluginClassloaderTemplateResolver createPluginClassloaderTemplateResolver() { + var pluginTemplateResolver = new PluginClassloaderTemplateResolver(haloPluginManager); + pluginTemplateResolver.setPrefix(thymeleafProperties.getPrefix()); + pluginTemplateResolver.setSuffix(thymeleafProperties.getSuffix()); + pluginTemplateResolver.setTemplateMode(thymeleafProperties.getMode()); + pluginTemplateResolver.setOrder(1); + if (thymeleafProperties.getEncoding() != null) { + pluginTemplateResolver.setCharacterEncoding(thymeleafProperties.getEncoding().name()); + } + return pluginTemplateResolver; + } + FileTemplateResolver haloTemplateResolver() { final var resolver = new FileTemplateResolver(); resolver.setTemplateMode(thymeleafProperties.getMode()); diff --git a/application/src/main/java/run/halo/app/theme/ViewNameResolver.java b/application/src/main/java/run/halo/app/theme/ViewNameResolver.java new file mode 100644 index 0000000000..873b4a4bb0 --- /dev/null +++ b/application/src/main/java/run/halo/app/theme/ViewNameResolver.java @@ -0,0 +1,20 @@ +package run.halo.app.theme; + +import org.springframework.web.reactive.function.server.ServerRequest; +import org.springframework.web.server.ServerWebExchange; +import reactor.core.publisher.Mono; + +/** + * The {@link ViewNameResolver} is used to resolve view name if the view name cannot be resolved + * to the view, the default view name is returned. + * + * @author guqing + * @since 2.10.2 + */ +public interface ViewNameResolver { + Mono resolveViewNameOrDefault(ServerWebExchange exchange, String name, + String defaultName); + + Mono resolveViewNameOrDefault(ServerRequest request, String name, + String defaultName); +} diff --git a/application/src/main/java/run/halo/app/theme/engine/PluginClassloaderTemplateResolver.java b/application/src/main/java/run/halo/app/theme/engine/PluginClassloaderTemplateResolver.java new file mode 100644 index 0000000000..d77dd5c534 --- /dev/null +++ b/application/src/main/java/run/halo/app/theme/engine/PluginClassloaderTemplateResolver.java @@ -0,0 +1,105 @@ +package run.halo.app.theme.engine; + +import static run.halo.app.plugin.PluginConst.SYSTEM_PLUGIN_NAME; + +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import org.apache.commons.lang3.StringUtils; +import org.pf4j.PluginState; +import org.springframework.core.io.DefaultResourceLoader; +import org.springframework.lang.Nullable; +import org.thymeleaf.IEngineConfiguration; +import org.thymeleaf.spring6.templateresource.SpringResourceTemplateResource; +import org.thymeleaf.templateresolver.AbstractConfigurableTemplateResolver; +import org.thymeleaf.templateresource.ITemplateResource; +import run.halo.app.plugin.HaloPluginManager; + +/** + * Plugin classloader template resolver to resolve template by plugin classloader. + * + * @author guqing + * @since 2.11.0 + */ +public class PluginClassloaderTemplateResolver extends AbstractConfigurableTemplateResolver { + + private final HaloPluginManager haloPluginManager; + static final Pattern PLUGIN_TEMPLATE_PATTERN = + Pattern.compile("plugin:([A-Za-z0-9\\-.]+):(.+)"); + + /** + * Create a new plugin classloader template resolver, not cacheable. + * + * @param haloPluginManager plugin manager must not be null + */ + public PluginClassloaderTemplateResolver(HaloPluginManager haloPluginManager) { + super(); + this.haloPluginManager = haloPluginManager; + setCacheable(false); + } + + @Override + protected ITemplateResource computeTemplateResource( + final IEngineConfiguration configuration, final String ownerTemplate, final String template, + final String resourceName, final String characterEncoding, + final Map templateResolutionAttributes) { + var matchResult = matchPluginTemplate(ownerTemplate, template); + if (!matchResult.matches()) { + return null; + } + String pluginName = matchResult.pluginName(); + var classloader = getClassloaderByPlugin(pluginName); + if (classloader == null) { + return null; + } + + var templateName = matchResult.templateName(); + var ownerTemplateName = matchResult.ownerTemplateName(); + + String handledResourceName = computeResourceName(configuration, ownerTemplateName, + templateName, getPrefix(), getSuffix(), getForceSuffix(), getTemplateAliases(), + templateResolutionAttributes); + + var resource = new DefaultResourceLoader(classloader) + .getResource(handledResourceName); + return new SpringResourceTemplateResource(resource, characterEncoding); + } + + MatchResult matchPluginTemplate(String ownerTemplate, String template) { + boolean matches = false; + String pluginName = null; + String templateName = template; + String ownerTemplateName = ownerTemplate; + if (StringUtils.isNotBlank(ownerTemplate)) { + Matcher ownerTemplateMatcher = PLUGIN_TEMPLATE_PATTERN.matcher(ownerTemplate); + if (ownerTemplateMatcher.matches()) { + matches = true; + pluginName = ownerTemplateMatcher.group(1); + ownerTemplateName = ownerTemplateMatcher.group(2); + } + } + Matcher templateMatcher = PLUGIN_TEMPLATE_PATTERN.matcher(template); + if (templateMatcher.matches()) { + matches = true; + pluginName = templateMatcher.group(1); + templateName = templateMatcher.group(2); + } + return new MatchResult(pluginName, ownerTemplateName, templateName, matches); + } + + record MatchResult(String pluginName, String ownerTemplateName, String templateName, + boolean matches) { + } + + @Nullable + private ClassLoader getClassloaderByPlugin(String pluginName) { + if (SYSTEM_PLUGIN_NAME.equals(pluginName)) { + return this.getClass().getClassLoader(); + } + var pluginWrapper = haloPluginManager.getPlugin(pluginName); + if (pluginWrapper == null || !PluginState.STARTED.equals(pluginWrapper.getPluginState())) { + return null; + } + return pluginWrapper.getPluginClassLoader(); + } +} diff --git a/application/src/main/java/run/halo/app/theme/router/PreviewRouterFunction.java b/application/src/main/java/run/halo/app/theme/router/PreviewRouterFunction.java index 56b149c4c9..eb87d912a9 100644 --- a/application/src/main/java/run/halo/app/theme/router/PreviewRouterFunction.java +++ b/application/src/main/java/run/halo/app/theme/router/PreviewRouterFunction.java @@ -24,6 +24,7 @@ import run.halo.app.infra.AnonymousUserConst; import run.halo.app.infra.exception.NotFoundException; import run.halo.app.theme.DefaultTemplateEnum; +import run.halo.app.theme.ViewNameResolver; import run.halo.app.theme.finders.PostPublicQueryService; import run.halo.app.theme.finders.SinglePageConversionService; import run.halo.app.theme.finders.vo.ContributorVo; diff --git a/application/src/main/java/run/halo/app/theme/router/SinglePageRoute.java b/application/src/main/java/run/halo/app/theme/router/SinglePageRoute.java index c48bb15d0e..d0fc787a20 100644 --- a/application/src/main/java/run/halo/app/theme/router/SinglePageRoute.java +++ b/application/src/main/java/run/halo/app/theme/router/SinglePageRoute.java @@ -35,6 +35,7 @@ import run.halo.app.extension.controller.Reconciler; import run.halo.app.infra.exception.NotFoundException; import run.halo.app.theme.DefaultTemplateEnum; +import run.halo.app.theme.ViewNameResolver; import run.halo.app.theme.finders.SinglePageFinder; /** diff --git a/application/src/main/java/run/halo/app/theme/router/factories/CategoryPostRouteFactory.java b/application/src/main/java/run/halo/app/theme/router/factories/CategoryPostRouteFactory.java index 02edd2c368..4b013c0617 100644 --- a/application/src/main/java/run/halo/app/theme/router/factories/CategoryPostRouteFactory.java +++ b/application/src/main/java/run/halo/app/theme/router/factories/CategoryPostRouteFactory.java @@ -23,6 +23,7 @@ import run.halo.app.infra.exception.NotFoundException; import run.halo.app.infra.utils.PathUtils; import run.halo.app.theme.DefaultTemplateEnum; +import run.halo.app.theme.ViewNameResolver; import run.halo.app.theme.finders.PostFinder; import run.halo.app.theme.finders.vo.CategoryVo; import run.halo.app.theme.finders.vo.ListedPostVo; @@ -30,7 +31,6 @@ import run.halo.app.theme.router.PageUrlUtils; import run.halo.app.theme.router.TitleVisibilityIdentifyCalculator; import run.halo.app.theme.router.UrlContextListResult; -import run.halo.app.theme.router.ViewNameResolver; /** * The {@link CategoryPostRouteFactory} for generate {@link RouterFunction} specific to the template diff --git a/application/src/main/java/run/halo/app/theme/router/factories/PostRouteFactory.java b/application/src/main/java/run/halo/app/theme/router/factories/PostRouteFactory.java index 5dd474be06..5874c84c47 100644 --- a/application/src/main/java/run/halo/app/theme/router/factories/PostRouteFactory.java +++ b/application/src/main/java/run/halo/app/theme/router/factories/PostRouteFactory.java @@ -35,12 +35,12 @@ import run.halo.app.infra.exception.NotFoundException; import run.halo.app.infra.utils.JsonUtils; import run.halo.app.theme.DefaultTemplateEnum; +import run.halo.app.theme.ViewNameResolver; import run.halo.app.theme.finders.PostFinder; import run.halo.app.theme.finders.vo.PostVo; import run.halo.app.theme.router.ModelMapUtils; import run.halo.app.theme.router.ReactiveQueryPostPredicateResolver; import run.halo.app.theme.router.TitleVisibilityIdentifyCalculator; -import run.halo.app.theme.router.ViewNameResolver; /** * The {@link PostRouteFactory} for generate {@link RouterFunction} specific to the template diff --git a/application/src/test/java/run/halo/app/theme/router/ViewNameResolverTest.java b/application/src/test/java/run/halo/app/theme/ViewNameResolverTest.java similarity index 94% rename from application/src/test/java/run/halo/app/theme/router/ViewNameResolverTest.java rename to application/src/test/java/run/halo/app/theme/ViewNameResolverTest.java index 8ec9e863c5..2c8c6021b6 100644 --- a/application/src/test/java/run/halo/app/theme/router/ViewNameResolverTest.java +++ b/application/src/test/java/run/halo/app/theme/ViewNameResolverTest.java @@ -1,4 +1,4 @@ -package run.halo.app.theme.router; +package run.halo.app.theme; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; @@ -24,11 +24,9 @@ import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Mono; import reactor.test.StepVerifier; -import run.halo.app.theme.ThemeContext; -import run.halo.app.theme.ThemeResolver; /** - * Tests for {@link ViewNameResolver}. + * Tests for {@link DefaultViewNameResolver}. * * @author guqing * @since 2.0.0 @@ -43,7 +41,7 @@ class ViewNameResolverTest { private ThymeleafProperties thymeleafProperties; @InjectMocks - private ViewNameResolver viewNameResolver; + private DefaultViewNameResolver viewNameResolver; @TempDir private File themePath; diff --git a/application/src/test/java/run/halo/app/theme/engine/PluginClassloaderTemplateResolverTest.java b/application/src/test/java/run/halo/app/theme/engine/PluginClassloaderTemplateResolverTest.java new file mode 100644 index 0000000000..afc1c92b7d --- /dev/null +++ b/application/src/test/java/run/halo/app/theme/engine/PluginClassloaderTemplateResolverTest.java @@ -0,0 +1,53 @@ +package run.halo.app.theme.engine; + +import static org.assertj.core.api.Assertions.assertThat; + +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.plugin.HaloPluginManager; + +/** + * Tests for {@link PluginClassloaderTemplateResolver}. + * + * @author guqing + * @since 2.11.0 + */ +@ExtendWith(MockitoExtension.class) +class PluginClassloaderTemplateResolverTest { + + @Mock + private HaloPluginManager haloPluginManager; + + @InjectMocks + private PluginClassloaderTemplateResolver templateResolver; + + @Test + void matchPluginTemplateWhenOwnerTemplateMatch() { + var result = + templateResolver.matchPluginTemplate("plugin:fake-plugin:doc", "modules/layout"); + assertThat(result.matches()).isTrue(); + assertThat(result.pluginName()).isEqualTo("fake-plugin"); + assertThat(result.templateName()).isEqualTo("modules/layout"); + assertThat(result.ownerTemplateName()).isEqualTo("doc"); + } + + @Test + void matchPluginTemplateWhenDoesNotMatch() { + var result = + templateResolver.matchPluginTemplate("doc", "modules/layout"); + assertThat(result.matches()).isFalse(); + } + + @Test + void matchPluginTemplateWhenTemplateMatch() { + var result = + templateResolver.matchPluginTemplate("doc", "plugin:fake-plugin:modules/layout"); + assertThat(result.matches()).isTrue(); + assertThat(result.pluginName()).isEqualTo("fake-plugin"); + assertThat(result.templateName()).isEqualTo("modules/layout"); + assertThat(result.ownerTemplateName()).isEqualTo("doc"); + } +} \ No newline at end of file diff --git a/application/src/test/java/run/halo/app/theme/router/PreviewRouterFunctionTest.java b/application/src/test/java/run/halo/app/theme/router/PreviewRouterFunctionTest.java index a039ce9f1a..0628957cce 100644 --- a/application/src/test/java/run/halo/app/theme/router/PreviewRouterFunctionTest.java +++ b/application/src/test/java/run/halo/app/theme/router/PreviewRouterFunctionTest.java @@ -26,6 +26,7 @@ import run.halo.app.extension.Metadata; import run.halo.app.extension.ReactiveExtensionClient; import run.halo.app.infra.AnonymousUserConst; +import run.halo.app.theme.ViewNameResolver; import run.halo.app.theme.finders.PostPublicQueryService; import run.halo.app.theme.finders.SinglePageConversionService; import run.halo.app.theme.finders.vo.ContributorVo; @@ -99,8 +100,8 @@ public void previewPost() { when(postPublicQueryService.convertToVo(eq(post), eq(post.getSpec().getHeadSnapshot()))) .thenReturn(Mono.just(postVo)); - when(viewNameResolver.resolveViewNameOrDefault(any(), eq("postTemplate"), - eq("post"))).thenReturn(Mono.just("postView")); + when(viewNameResolver.resolveViewNameOrDefault(any(ServerWebExchange.class), + eq("postTemplate"), eq("post"))).thenReturn(Mono.just("postView")); webTestClient.get().uri("/preview/posts/post1") .exchange() @@ -135,8 +136,8 @@ public void previewSinglePage() { when(singlePageConversionService.convertToVo(singlePage, "snapshot1")) .thenReturn(Mono.just(singlePageVo)); - when(viewNameResolver.resolveViewNameOrDefault(any(), eq("pageTemplate"), - eq("page"))).thenReturn(Mono.just("pageView")); + when(viewNameResolver.resolveViewNameOrDefault(any(ServerWebExchange.class), + eq("pageTemplate"), eq("page"))).thenReturn(Mono.just("pageView")); webTestClient.get().uri("/preview/singlepages/page1") .exchange() diff --git a/application/src/test/java/run/halo/app/theme/router/SinglePageRouteTest.java b/application/src/test/java/run/halo/app/theme/router/SinglePageRouteTest.java index 249978c993..b74e05120d 100644 --- a/application/src/test/java/run/halo/app/theme/router/SinglePageRouteTest.java +++ b/application/src/test/java/run/halo/app/theme/router/SinglePageRouteTest.java @@ -47,6 +47,7 @@ import run.halo.app.extension.Metadata; import run.halo.app.extension.controller.Reconciler; import run.halo.app.theme.DefaultTemplateEnum; +import run.halo.app.theme.ViewNameResolver; import run.halo.app.theme.finders.SinglePageFinder; import run.halo.app.theme.finders.vo.SinglePageVo; import run.halo.app.theme.router.SinglePageRoute.NameSlugPair; @@ -84,7 +85,7 @@ class SinglePageRouteTest { @Test void handlerFunction() { // fix gh-3448 - when(viewNameResolver.resolveViewNameOrDefault(any(), any(), any())) + when(viewNameResolver.resolveViewNameOrDefault(any(ServerWebExchange.class), any(), any())) .thenReturn(Mono.just(DefaultTemplateEnum.POST.getValue())); String pageName = "fake-page"; diff --git a/application/src/test/java/run/halo/app/theme/router/factories/PostRouteFactoryTest.java b/application/src/test/java/run/halo/app/theme/router/factories/PostRouteFactoryTest.java index 01008cab85..27488fa864 100644 --- a/application/src/test/java/run/halo/app/theme/router/factories/PostRouteFactoryTest.java +++ b/application/src/test/java/run/halo/app/theme/router/factories/PostRouteFactoryTest.java @@ -26,6 +26,7 @@ import run.halo.app.extension.MetadataUtil; import run.halo.app.extension.ReactiveExtensionClient; import run.halo.app.theme.DefaultTemplateEnum; +import run.halo.app.theme.ViewNameResolver; import run.halo.app.theme.finders.PostFinder; import run.halo.app.theme.finders.vo.PostVo; import run.halo.app.theme.router.DefaultQueryPostPredicateResolver; @@ -33,7 +34,6 @@ import run.halo.app.theme.router.ModelConst; import run.halo.app.theme.router.ReactiveQueryPostPredicateResolver; import run.halo.app.theme.router.TitleVisibilityIdentifyCalculator; -import run.halo.app.theme.router.ViewNameResolver; /** * Tests for {@link PostRouteFactory}. @@ -77,7 +77,7 @@ void create() { when(client.fetch(eq(Post.class), eq("fake-name"))).thenReturn(Mono.just(post)); - when(viewNameResolver.resolveViewNameOrDefault(any(), any(), any())) + when(viewNameResolver.resolveViewNameOrDefault(any(ServerWebExchange.class), any(), any())) .thenReturn(Mono.just(DefaultTemplateEnum.POST.getValue())); when(predicateResolver.getPredicate()) .thenReturn(new DefaultQueryPostPredicateResolver().getPredicate()); From ca35bfb8324ee6dbdb705ed96f7d8b81e65b0e43 Mon Sep 17 00:00:00 2001 From: guqing Date: Fri, 17 Nov 2023 10:49:18 +0800 Subject: [PATCH 2/2] fix: unit test cases --- .../run/halo/app/theme/router/PreviewRouterFunctionTest.java | 5 +++-- .../java/run/halo/app/theme/router/SinglePageRouteTest.java | 3 ++- .../app/theme/router/factories/PostRouteFactoryTest.java | 3 ++- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/application/src/test/java/run/halo/app/theme/router/PreviewRouterFunctionTest.java b/application/src/test/java/run/halo/app/theme/router/PreviewRouterFunctionTest.java index 0628957cce..683401a1d2 100644 --- a/application/src/test/java/run/halo/app/theme/router/PreviewRouterFunctionTest.java +++ b/application/src/test/java/run/halo/app/theme/router/PreviewRouterFunctionTest.java @@ -17,6 +17,7 @@ import org.springframework.test.context.junit.jupiter.SpringExtension; import org.springframework.test.web.reactive.server.WebTestClient; import org.springframework.web.reactive.function.server.HandlerStrategies; +import org.springframework.web.reactive.function.server.ServerRequest; import org.springframework.web.reactive.result.view.ViewResolver; import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Mono; @@ -100,7 +101,7 @@ public void previewPost() { when(postPublicQueryService.convertToVo(eq(post), eq(post.getSpec().getHeadSnapshot()))) .thenReturn(Mono.just(postVo)); - when(viewNameResolver.resolveViewNameOrDefault(any(ServerWebExchange.class), + when(viewNameResolver.resolveViewNameOrDefault(any(ServerRequest.class), eq("postTemplate"), eq("post"))).thenReturn(Mono.just("postView")); webTestClient.get().uri("/preview/posts/post1") @@ -136,7 +137,7 @@ public void previewSinglePage() { when(singlePageConversionService.convertToVo(singlePage, "snapshot1")) .thenReturn(Mono.just(singlePageVo)); - when(viewNameResolver.resolveViewNameOrDefault(any(ServerWebExchange.class), + when(viewNameResolver.resolveViewNameOrDefault(any(ServerRequest.class), eq("pageTemplate"), eq("page"))).thenReturn(Mono.just("pageView")); webTestClient.get().uri("/preview/singlepages/page1") diff --git a/application/src/test/java/run/halo/app/theme/router/SinglePageRouteTest.java b/application/src/test/java/run/halo/app/theme/router/SinglePageRouteTest.java index b74e05120d..db8e55e6ef 100644 --- a/application/src/test/java/run/halo/app/theme/router/SinglePageRouteTest.java +++ b/application/src/test/java/run/halo/app/theme/router/SinglePageRouteTest.java @@ -34,6 +34,7 @@ import org.springframework.web.reactive.function.server.HandlerStrategies; import org.springframework.web.reactive.function.server.RouterFunction; import org.springframework.web.reactive.function.server.RouterFunctions; +import org.springframework.web.reactive.function.server.ServerRequest; import org.springframework.web.reactive.function.server.ServerResponse; import org.springframework.web.reactive.result.view.ViewResolver; import org.springframework.web.server.ServerWebExchange; @@ -85,7 +86,7 @@ class SinglePageRouteTest { @Test void handlerFunction() { // fix gh-3448 - when(viewNameResolver.resolveViewNameOrDefault(any(ServerWebExchange.class), any(), any())) + when(viewNameResolver.resolveViewNameOrDefault(any(ServerRequest.class), any(), any())) .thenReturn(Mono.just(DefaultTemplateEnum.POST.getValue())); String pageName = "fake-page"; diff --git a/application/src/test/java/run/halo/app/theme/router/factories/PostRouteFactoryTest.java b/application/src/test/java/run/halo/app/theme/router/factories/PostRouteFactoryTest.java index 27488fa864..21ecdf870e 100644 --- a/application/src/test/java/run/halo/app/theme/router/factories/PostRouteFactoryTest.java +++ b/application/src/test/java/run/halo/app/theme/router/factories/PostRouteFactoryTest.java @@ -16,6 +16,7 @@ import org.springframework.http.MediaType; import org.springframework.test.web.reactive.server.WebTestClient; import org.springframework.web.reactive.function.server.RouterFunction; +import org.springframework.web.reactive.function.server.ServerRequest; import org.springframework.web.reactive.function.server.ServerResponse; import org.springframework.web.server.ServerWebExchange; import org.springframework.web.server.i18n.LocaleContextResolver; @@ -77,7 +78,7 @@ void create() { when(client.fetch(eq(Post.class), eq("fake-name"))).thenReturn(Mono.just(post)); - when(viewNameResolver.resolveViewNameOrDefault(any(ServerWebExchange.class), any(), any())) + when(viewNameResolver.resolveViewNameOrDefault(any(ServerRequest.class), any(), any())) .thenReturn(Mono.just(DefaultTemplateEnum.POST.getValue())); when(predicateResolver.getPredicate()) .thenReturn(new DefaultQueryPostPredicateResolver().getPredicate());