Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Provide ElementTagProcessor to handle element tag in plugin #6670

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package run.halo.app.plugin.extensionpoint;

import java.util.List;
import org.pf4j.ExtensionPoint;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
Expand Down Expand Up @@ -34,4 +35,14 @@ public interface ExtensionGetter {
* @return a bunch of extension points.
*/
<T extends ExtensionPoint> Flux<T> getExtensions(Class<T> extensionPointClass);

/**
* Get all extensions according to extension point class.
*
* @param extensionPointClass extension point class
* @param <T> type of extension point
* @return a bunch of extension points.
*/
<T extends ExtensionPoint> List<T> getExtensionList(Class<T> extensionPointClass);

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package run.halo.app.theme.dialect;

import org.pf4j.ExtensionPoint;
import org.thymeleaf.context.ITemplateContext;
import org.thymeleaf.model.IProcessableElementTag;
import reactor.core.publisher.Mono;

/**
* An extension point for post-processing element tag.
*
* @author johnniang
* @since 2.20.0
*/
public interface ElementTagPostProcessor extends ExtensionPoint {

/**
* <p>
* Execute the processor.
* </p>
* <p>
* The {@link IProcessableElementTag} object argument is immutable, so all modifications to
* this object or any
* instructions to be given to the engine should be done through the specified
* {@link org.thymeleaf.model.IModelFactory} model factory in context.
* </p>
* <p>
* Don't forget to return the new tag after processing or
* {@link reactor.core.publisher.Mono#empty()} if not processable.
* </p>
*
* @param context the template context.
* @param tag the event this processor is executing on.
* @return a {@link reactor.core.publisher.Mono} that will complete when processing finishes
* or empty mono if not support.
*/
Mono<IProcessableElementTag> process(
ITemplateContext context,
final IProcessableElementTag tag
);

}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@

import static run.halo.app.extension.index.query.QueryFactory.equal;

import java.util.LinkedList;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import lombok.RequiredArgsConstructor;
import org.pf4j.ExtensionPoint;
import org.pf4j.PluginManager;
Expand Down Expand Up @@ -41,6 +44,16 @@ public <T extends ExtensionPoint> Flux<T> getExtensions(Class<T> extensionPoint)
.sort(new AnnotationAwareOrderComparator());
}

@Override
public <T extends ExtensionPoint> List<T> getExtensionList(Class<T> extensionPoint) {
var extensions = new LinkedList<T>();
Optional.ofNullable(pluginManager.getExtensions(extensionPoint))
.ifPresent(extensions::addAll);
extensions.addAll(beanFactory.getBeanProvider(extensionPoint).orderedStream().toList());
extensions.sort(new AnnotationAwareOrderComparator());
return extensions;
}

@Override
public <T extends ExtensionPoint> Mono<T> getEnabledExtension(Class<T> extensionPoint) {
return getEnabledExtensions(extensionPoint).next();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package run.halo.app.theme.dialect;

import static org.thymeleaf.spring6.context.SpringContextUtils.getApplicationContext;

import java.time.Duration;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import org.springframework.lang.NonNull;
import org.springframework.util.CollectionUtils;
import org.thymeleaf.context.ITemplateContext;
import org.thymeleaf.engine.AbstractTemplateHandler;
import org.thymeleaf.model.IOpenElementTag;
import org.thymeleaf.model.IProcessableElementTag;
import org.thymeleaf.model.IStandaloneElementTag;
import reactor.core.publisher.Mono;
import run.halo.app.plugin.extensionpoint.ExtensionGetter;

/**
* Template post-handler.
*
* @author johnniang
* @since 2.20.0
*/
public class HaloPostTemplateHandler extends AbstractTemplateHandler {

private List<ElementTagPostProcessor> postProcessors = List.of();

@Override
public void setContext(ITemplateContext context) {
super.setContext(context);
this.postProcessors = Optional.ofNullable(getApplicationContext(context))
.map(appContext -> appContext.getBeanProvider(ExtensionGetter.class).getIfUnique())
.map(extensionGetter -> extensionGetter.getExtensionList(ElementTagPostProcessor.class))
.orElseGet(List::of);
}

@Override
public void handleStandaloneElement(IStandaloneElementTag standaloneElementTag) {
var processedTag = handleElementTag(standaloneElementTag);
super.handleStandaloneElement((IStandaloneElementTag) processedTag);
}

@Override
public void handleOpenElement(IOpenElementTag openElementTag) {
var processedTag = handleElementTag(openElementTag);
super.handleOpenElement((IOpenElementTag) processedTag);
}

@NonNull
private IProcessableElementTag handleElementTag(
@NonNull IProcessableElementTag processableElementTag
) {
IProcessableElementTag processedTag = processableElementTag;
if (!CollectionUtils.isEmpty(postProcessors)) {
var tagProcessorChain = Mono.just(processableElementTag);
var context = getContext();
for (ElementTagPostProcessor elementTagPostProcessor : postProcessors) {
tagProcessorChain = tagProcessorChain.flatMap(
tag -> elementTagPostProcessor.process(context, tag).defaultIfEmpty(tag)
);
}
processedTag =
Objects.requireNonNull(tagProcessorChain.defaultIfEmpty(processableElementTag)
.block(Duration.ofMinutes(1)));
}
return processedTag;
}
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
package run.halo.app.theme.dialect;

import static org.thymeleaf.templatemode.TemplateMode.HTML;

import java.util.HashSet;
import java.util.Set;
import org.thymeleaf.dialect.AbstractProcessorDialect;
import org.thymeleaf.dialect.IExpressionObjectDialect;
import org.thymeleaf.dialect.IPostProcessorDialect;
import org.thymeleaf.expression.IExpressionObjectFactory;
import org.thymeleaf.postprocessor.IPostProcessor;
import org.thymeleaf.postprocessor.PostProcessor;
import org.thymeleaf.processor.IProcessor;
import org.thymeleaf.standard.StandardDialect;

Expand All @@ -14,8 +19,8 @@
* @author guqing
* @since 2.0.0
*/
public class HaloProcessorDialect extends AbstractProcessorDialect implements
IExpressionObjectDialect {
public class HaloProcessorDialect extends AbstractProcessorDialect
implements IExpressionObjectDialect, IPostProcessorDialect {
private static final String DIALECT_NAME = "haloThemeProcessorDialect";

private static final IExpressionObjectFactory HALO_EXPRESSION_OBJECTS_FACTORY =
Expand Down Expand Up @@ -43,4 +48,14 @@ public Set<IProcessor> getProcessors(String dialectPrefix) {
public IExpressionObjectFactory getExpressionObjectFactory() {
return HALO_EXPRESSION_OBJECTS_FACTORY;
}

@Override
public int getDialectPostProcessorPrecedence() {
return Integer.MAX_VALUE;
}

@Override
public Set<IPostProcessor> getPostProcessors() {
return Set.of(new PostProcessor(HTML, HaloPostTemplateHandler.class, Integer.MAX_VALUE));
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package run.halo.app.plugin.extensionpoint;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.same;
import static org.mockito.Mockito.mock;
Expand Down Expand Up @@ -44,6 +45,9 @@ class DefaultExtensionGetterTest {
@Mock
BeanFactory beanFactory;

@Mock
ObjectProvider<FakeExtensionPoint> extensionPointObjectProvider;

@InjectMocks
DefaultExtensionGetter getter;

Expand Down Expand Up @@ -209,6 +213,20 @@ void shouldGetMultiInstanceExtensionWhileExtensionPointEnabledNotSet() {
.verifyComplete();
}

@Test
void shouldGetExtensionsFromPluginManagerAndApplicationContext() {
var extensionFromPlugin = new FakeExtensionPointDefaultImpl();
var extensionFromAppContext = new FakeExtensionPointImpl();
when(pluginManager.getExtensions(FakeExtensionPoint.class))
.thenReturn(List.of(extensionFromPlugin));
when(beanFactory.getBeanProvider(FakeExtensionPoint.class))
.thenReturn(extensionPointObjectProvider);
when(extensionPointObjectProvider.orderedStream())
.thenReturn(Stream.of(extensionFromAppContext));
var extensions = getter.getExtensionList(FakeExtensionPoint.class);
assertEquals(List.of(extensionFromAppContext, extensionFromPlugin), extensions);
}

interface FakeExtensionPoint extends ExtensionPoint {

}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.lenient;
import static org.mockito.Mockito.when;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ArrayNode;
Expand All @@ -14,6 +15,7 @@
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.context.ApplicationContext;
import org.thymeleaf.IEngineConfiguration;
import org.thymeleaf.TemplateEngine;
Expand All @@ -29,6 +31,7 @@
import run.halo.app.infra.SystemConfigurableEnvironmentFetcher;
import run.halo.app.infra.SystemSetting;
import run.halo.app.infra.utils.JsonUtils;
import run.halo.app.plugin.extensionpoint.ExtensionGetter;
import run.halo.app.theme.dialect.HaloProcessorDialect;

/**
Expand All @@ -44,6 +47,9 @@ public class ReactiveFinderExpressionParserTests {
@Mock
private ApplicationContext applicationContext;

@Mock
private ObjectProvider<ExtensionGetter> extensionGetterProvider;

@Mock
private SystemConfigurableEnvironmentFetcher environmentFetcher;

Expand All @@ -62,6 +68,9 @@ public IStandardVariableExpressionEvaluator getVariableExpressionEvaluator() {
templateEngine.addTemplateResolver(new TestTemplateResolver());
lenient().when(applicationContext.getBean(eq(SystemConfigurableEnvironmentFetcher.class)))
.thenReturn(environmentFetcher);
when(applicationContext.getBeanProvider(ExtensionGetter.class))
.thenReturn(extensionGetterProvider);
when(extensionGetterProvider.getIfUnique()).thenReturn(null);
lenient().when(environmentFetcher.fetchComment())
.thenReturn(Mono.just(new SystemSetting.Comment()));
}
Expand Down Expand Up @@ -155,7 +164,7 @@ protected ITemplateResource computeTemplateResource(IEngineConfiguration configu
var mapMono = /*[[${target.mapMono.foo}]]*/;
var arrayNodeMono = /*[[${target.arrayNodeMono.get(0).foo}]]*/;
</script>
""");
""");
}

}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.context.ApplicationContext;
import org.thymeleaf.IEngineConfiguration;
import org.thymeleaf.TemplateEngine;
Expand Down Expand Up @@ -48,6 +49,9 @@ class CommentElementTagProcessorTest {
@Mock
private ExtensionGetter extensionGetter;

@Mock
private ObjectProvider<ExtensionGetter> extensionGetterProvider;

@Mock
private SystemConfigurableEnvironmentFetcher environmentFetcher;

Expand All @@ -61,6 +65,9 @@ void setUp() {
templateEngine.addTemplateResolver(new TestTemplateResolver());
lenient().when(applicationContext.getBean(eq(ExtensionGetter.class)))
.thenReturn(extensionGetter);
when(applicationContext.getBeanProvider(ExtensionGetter.class))
.thenReturn(extensionGetterProvider);
when(extensionGetterProvider.getIfUnique()).thenReturn(null);
}

@Test
Expand Down
Loading
Loading