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