Skip to content

Commit

Permalink
Provide ElementTagPostProcessor to handle element tag in plugin
Browse files Browse the repository at this point in the history
Signed-off-by: JohnNiang <[email protected]>
  • Loading branch information
JohnNiang committed Sep 18, 2024
1 parent 3fda9e6 commit 33a3230
Show file tree
Hide file tree
Showing 9 changed files with 339 additions and 3 deletions.
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

0 comments on commit 33a3230

Please sign in to comment.