From b30d43f795085f97452b717ff069e697c642323c Mon Sep 17 00:00:00 2001 From: Eric Bussieres Date: Wed, 26 Oct 2022 15:58:34 -0400 Subject: [PATCH 1/3] Add support for spring boot 3 --- README.md | 6 +- docs/pom.xml | 2 +- docs/src/orchid/resources/changelog/v4_0_0.md | 6 + .../pebble-legacy-spring-boot-starter/pom.xml | 62 +++++ .../AbstractPebbleConfiguration.java | 14 ++ .../PebbleAutoConfiguration.java | 68 ++++++ .../boot/autoconfigure/PebbleProperties.java | 45 ++++ .../PebbleReactiveWebConfiguration.java | 32 +++ .../PebbleServletWebConfiguration.java | 29 +++ .../PebbleTemplateAvailabilityProvider.java | 23 ++ .../boot/autoconfigure/package-info.java | 4 + .../main/resources/META-INF/spring.factories | 6 + .../main/resources/META-INF/spring.provides | 1 + .../pebble/boot/AppConfig.java | 64 ++++++ .../pebble/boot/Application.java | 12 + .../pebble/boot/Controllers.java | 39 ++++ .../com/mitchellbosecke/pebble/boot/Foo.java | 9 + .../boot/autoconfigure/NonWebAppTests.java | 25 +++ .../PebbleAutoConfigurationTest.java | 200 +++++++++++++++++ .../boot/autoconfigure/ReactiveAppTest.java | 100 +++++++++ .../boot/autoconfigure/ServletAppTest.java | 88 ++++++++ .../src/test/resources/messages.properties | 1 + .../src/test/resources/messages_es.properties | 1 + .../src/test/resources/templates/beans.pebble | 1 + .../resources/templates/contextPath.pebble | 1 + .../resources/templates/extensions.pebble | 1 + .../src/test/resources/templates/hello.pebble | 1 + .../src/test/resources/templates/index.pebble | 1 + .../resources/templates/responseObject.pebble | 1 + .../pebble-spring-boot-starter/pom.xml | 22 +- pebble-spring/pebble-spring5/pom.xml | 2 +- pebble-spring/pebble-spring6/pom.xml | 91 ++++++++ .../pebble/spring/context/Beans.java | 97 ++++++++ .../spring/extension/SpringExtension.java | 54 +++++ .../extension/function/HrefFunction.java | 81 +++++++ .../function/MessageSourceFunction.java | 65 ++++++ .../BaseBindingResultFunction.java | 51 +++++ .../bindingresult/GetAllErrorsFunction.java | 59 +++++ .../bindingresult/GetFieldErrorsFunction.java | 65 ++++++ .../GetGlobalErrorsFunction.java | 59 +++++ .../bindingresult/HasErrorsFunction.java | 38 ++++ .../bindingresult/HasFieldErrorsFunction.java | 48 ++++ .../HasGlobalErrorsFunction.java | 38 ++++ .../spring/reactive/PebbleReactiveView.java | 101 +++++++++ .../reactive/PebbleReactiveViewResolver.java | 29 +++ .../pebble/spring/servlet/PebbleView.java | 114 ++++++++++ .../spring/servlet/PebbleViewResolver.java | 50 +++++ .../pebble/spring/PebbleViewResolverTest.java | 211 ++++++++++++++++++ .../pebble/spring/bean/SomeBean.java | 19 ++ .../pebble/spring/config/MVCConfig.java | 70 ++++++ .../spring/expectedResponse/beansTest.html | 10 + .../expectedResponse/bindingResultTest.html | 21 ++ .../bindingResultWithMacroTest.html | 21 ++ .../expectedResponse/hrefFunctionTest.html | 13 ++ .../expectedResponse/messageEnTest.html | 12 + .../expectedResponse/messageFrTest.html | 12 + .../spring/expectedResponse/requestTest.html | 11 + .../spring/expectedResponse/responseTest.html | 12 + .../spring/expectedResponse/sessionTest.html | 11 + .../pebble/spring/messages_en.properties | 5 + .../pebble/spring/messages_fr.properties | 5 + .../pebble/spring/template/beansTest.html | 10 + .../spring/template/bindingResultTest.html | 26 +++ .../template/bindingResultWithMacroTest.html | 29 +++ .../spring/template/hrefFunctionTest.html | 13 ++ .../pebble/spring/template/messageEnTest.html | 12 + .../pebble/spring/template/messageFrTest.html | 12 + .../pebble/spring/template/requestTest.html | 11 + .../pebble/spring/template/responseTest.html | 12 + .../pebble/spring/template/sessionTest.html | 11 + pebble-spring/pom.xml | 2 +- pebble/pom.xml | 2 +- pom.xml | 19 +- 73 files changed, 2479 insertions(+), 20 deletions(-) create mode 100644 docs/src/orchid/resources/changelog/v4_0_0.md create mode 100644 pebble-spring/pebble-legacy-spring-boot-starter/pom.xml create mode 100644 pebble-spring/pebble-legacy-spring-boot-starter/src/main/java/com/mitchellbosecke/pebble/boot/autoconfigure/AbstractPebbleConfiguration.java create mode 100644 pebble-spring/pebble-legacy-spring-boot-starter/src/main/java/com/mitchellbosecke/pebble/boot/autoconfigure/PebbleAutoConfiguration.java create mode 100644 pebble-spring/pebble-legacy-spring-boot-starter/src/main/java/com/mitchellbosecke/pebble/boot/autoconfigure/PebbleProperties.java create mode 100644 pebble-spring/pebble-legacy-spring-boot-starter/src/main/java/com/mitchellbosecke/pebble/boot/autoconfigure/PebbleReactiveWebConfiguration.java create mode 100644 pebble-spring/pebble-legacy-spring-boot-starter/src/main/java/com/mitchellbosecke/pebble/boot/autoconfigure/PebbleServletWebConfiguration.java create mode 100644 pebble-spring/pebble-legacy-spring-boot-starter/src/main/java/com/mitchellbosecke/pebble/boot/autoconfigure/PebbleTemplateAvailabilityProvider.java create mode 100644 pebble-spring/pebble-legacy-spring-boot-starter/src/main/java/com/mitchellbosecke/pebble/boot/autoconfigure/package-info.java create mode 100644 pebble-spring/pebble-legacy-spring-boot-starter/src/main/resources/META-INF/spring.factories create mode 100644 pebble-spring/pebble-legacy-spring-boot-starter/src/main/resources/META-INF/spring.provides create mode 100644 pebble-spring/pebble-legacy-spring-boot-starter/src/test/java/com/mitchellbosecke/pebble/boot/AppConfig.java create mode 100644 pebble-spring/pebble-legacy-spring-boot-starter/src/test/java/com/mitchellbosecke/pebble/boot/Application.java create mode 100644 pebble-spring/pebble-legacy-spring-boot-starter/src/test/java/com/mitchellbosecke/pebble/boot/Controllers.java create mode 100644 pebble-spring/pebble-legacy-spring-boot-starter/src/test/java/com/mitchellbosecke/pebble/boot/Foo.java create mode 100644 pebble-spring/pebble-legacy-spring-boot-starter/src/test/java/com/mitchellbosecke/pebble/boot/autoconfigure/NonWebAppTests.java create mode 100644 pebble-spring/pebble-legacy-spring-boot-starter/src/test/java/com/mitchellbosecke/pebble/boot/autoconfigure/PebbleAutoConfigurationTest.java create mode 100644 pebble-spring/pebble-legacy-spring-boot-starter/src/test/java/com/mitchellbosecke/pebble/boot/autoconfigure/ReactiveAppTest.java create mode 100644 pebble-spring/pebble-legacy-spring-boot-starter/src/test/java/com/mitchellbosecke/pebble/boot/autoconfigure/ServletAppTest.java create mode 100644 pebble-spring/pebble-legacy-spring-boot-starter/src/test/resources/messages.properties create mode 100644 pebble-spring/pebble-legacy-spring-boot-starter/src/test/resources/messages_es.properties create mode 100644 pebble-spring/pebble-legacy-spring-boot-starter/src/test/resources/templates/beans.pebble create mode 100644 pebble-spring/pebble-legacy-spring-boot-starter/src/test/resources/templates/contextPath.pebble create mode 100644 pebble-spring/pebble-legacy-spring-boot-starter/src/test/resources/templates/extensions.pebble create mode 100644 pebble-spring/pebble-legacy-spring-boot-starter/src/test/resources/templates/hello.pebble create mode 100644 pebble-spring/pebble-legacy-spring-boot-starter/src/test/resources/templates/index.pebble create mode 100644 pebble-spring/pebble-legacy-spring-boot-starter/src/test/resources/templates/responseObject.pebble create mode 100644 pebble-spring/pebble-spring6/pom.xml create mode 100644 pebble-spring/pebble-spring6/src/main/java/com/mitchellbosecke/pebble/spring/context/Beans.java create mode 100644 pebble-spring/pebble-spring6/src/main/java/com/mitchellbosecke/pebble/spring/extension/SpringExtension.java create mode 100644 pebble-spring/pebble-spring6/src/main/java/com/mitchellbosecke/pebble/spring/extension/function/HrefFunction.java create mode 100644 pebble-spring/pebble-spring6/src/main/java/com/mitchellbosecke/pebble/spring/extension/function/MessageSourceFunction.java create mode 100644 pebble-spring/pebble-spring6/src/main/java/com/mitchellbosecke/pebble/spring/extension/function/bindingresult/BaseBindingResultFunction.java create mode 100644 pebble-spring/pebble-spring6/src/main/java/com/mitchellbosecke/pebble/spring/extension/function/bindingresult/GetAllErrorsFunction.java create mode 100644 pebble-spring/pebble-spring6/src/main/java/com/mitchellbosecke/pebble/spring/extension/function/bindingresult/GetFieldErrorsFunction.java create mode 100644 pebble-spring/pebble-spring6/src/main/java/com/mitchellbosecke/pebble/spring/extension/function/bindingresult/GetGlobalErrorsFunction.java create mode 100644 pebble-spring/pebble-spring6/src/main/java/com/mitchellbosecke/pebble/spring/extension/function/bindingresult/HasErrorsFunction.java create mode 100644 pebble-spring/pebble-spring6/src/main/java/com/mitchellbosecke/pebble/spring/extension/function/bindingresult/HasFieldErrorsFunction.java create mode 100644 pebble-spring/pebble-spring6/src/main/java/com/mitchellbosecke/pebble/spring/extension/function/bindingresult/HasGlobalErrorsFunction.java create mode 100644 pebble-spring/pebble-spring6/src/main/java/com/mitchellbosecke/pebble/spring/reactive/PebbleReactiveView.java create mode 100644 pebble-spring/pebble-spring6/src/main/java/com/mitchellbosecke/pebble/spring/reactive/PebbleReactiveViewResolver.java create mode 100644 pebble-spring/pebble-spring6/src/main/java/com/mitchellbosecke/pebble/spring/servlet/PebbleView.java create mode 100644 pebble-spring/pebble-spring6/src/main/java/com/mitchellbosecke/pebble/spring/servlet/PebbleViewResolver.java create mode 100644 pebble-spring/pebble-spring6/src/test/java/com/mitchellbosecke/pebble/spring/PebbleViewResolverTest.java create mode 100644 pebble-spring/pebble-spring6/src/test/java/com/mitchellbosecke/pebble/spring/bean/SomeBean.java create mode 100644 pebble-spring/pebble-spring6/src/test/java/com/mitchellbosecke/pebble/spring/config/MVCConfig.java create mode 100644 pebble-spring/pebble-spring6/src/test/resources/com/mitchellbosecke/pebble/spring/expectedResponse/beansTest.html create mode 100644 pebble-spring/pebble-spring6/src/test/resources/com/mitchellbosecke/pebble/spring/expectedResponse/bindingResultTest.html create mode 100644 pebble-spring/pebble-spring6/src/test/resources/com/mitchellbosecke/pebble/spring/expectedResponse/bindingResultWithMacroTest.html create mode 100644 pebble-spring/pebble-spring6/src/test/resources/com/mitchellbosecke/pebble/spring/expectedResponse/hrefFunctionTest.html create mode 100644 pebble-spring/pebble-spring6/src/test/resources/com/mitchellbosecke/pebble/spring/expectedResponse/messageEnTest.html create mode 100644 pebble-spring/pebble-spring6/src/test/resources/com/mitchellbosecke/pebble/spring/expectedResponse/messageFrTest.html create mode 100644 pebble-spring/pebble-spring6/src/test/resources/com/mitchellbosecke/pebble/spring/expectedResponse/requestTest.html create mode 100644 pebble-spring/pebble-spring6/src/test/resources/com/mitchellbosecke/pebble/spring/expectedResponse/responseTest.html create mode 100644 pebble-spring/pebble-spring6/src/test/resources/com/mitchellbosecke/pebble/spring/expectedResponse/sessionTest.html create mode 100644 pebble-spring/pebble-spring6/src/test/resources/com/mitchellbosecke/pebble/spring/messages_en.properties create mode 100644 pebble-spring/pebble-spring6/src/test/resources/com/mitchellbosecke/pebble/spring/messages_fr.properties create mode 100644 pebble-spring/pebble-spring6/src/test/resources/com/mitchellbosecke/pebble/spring/template/beansTest.html create mode 100644 pebble-spring/pebble-spring6/src/test/resources/com/mitchellbosecke/pebble/spring/template/bindingResultTest.html create mode 100644 pebble-spring/pebble-spring6/src/test/resources/com/mitchellbosecke/pebble/spring/template/bindingResultWithMacroTest.html create mode 100644 pebble-spring/pebble-spring6/src/test/resources/com/mitchellbosecke/pebble/spring/template/hrefFunctionTest.html create mode 100644 pebble-spring/pebble-spring6/src/test/resources/com/mitchellbosecke/pebble/spring/template/messageEnTest.html create mode 100644 pebble-spring/pebble-spring6/src/test/resources/com/mitchellbosecke/pebble/spring/template/messageFrTest.html create mode 100644 pebble-spring/pebble-spring6/src/test/resources/com/mitchellbosecke/pebble/spring/template/requestTest.html create mode 100644 pebble-spring/pebble-spring6/src/test/resources/com/mitchellbosecke/pebble/spring/template/responseTest.html create mode 100644 pebble-spring/pebble-spring6/src/test/resources/com/mitchellbosecke/pebble/spring/template/sessionTest.html diff --git a/README.md b/README.md index 9f762f44b..e2504769f 100644 --- a/README.md +++ b/README.md @@ -8,9 +8,9 @@ For more information please visit the [website](https://pebbletemplates.io). As of version 3.1.0 and in order to follow this naming [recommendation](https://github.com/spring-projects/spring-boot/wiki/Building-On-Spring-Boot#naming), the artifactId of pebble-spring-boot-starter has been renamed as is: | Old artifactId | New artifactId | spring-boot version | -| --- |--------------------------------------------------------------------| --- | -| pebble-spring-boot-starter | pebble-legacy-spring-boot-starter (No longer supported as of 3.1.6 | 1.5.x | -| pebble-spring-boot-2-starter | pebble-spring-boot-starter | 2.x.x | +| --- |--------------------------------------------------------------------|---------------------| +| pebble-spring-boot-starter | pebble-legacy-spring-boot-starter | 2.7.x | +| pebble-spring-boot-2-starter | pebble-spring-boot-starter | 3.x.x | # New group id Please note that the pebble's groupId has been updated as of version 2.5.0 diff --git a/docs/pom.xml b/docs/pom.xml index abde8b43b..70c25e078 100644 --- a/docs/pom.xml +++ b/docs/pom.xml @@ -5,7 +5,7 @@ io.pebbletemplates pebble-project - 3.1.7-SNAPSHOT + 4.0.0-SNAPSHOT docs diff --git a/docs/src/orchid/resources/changelog/v4_0_0.md b/docs/src/orchid/resources/changelog/v4_0_0.md new file mode 100644 index 000000000..2ad16bfd2 --- /dev/null +++ b/docs/src/orchid/resources/changelog/v4_0_0.md @@ -0,0 +1,6 @@ +--- +version: '4.0.0' +--- + +- Add support for spring framework 6 and spring-boot 3 (#) +- Bump minimum supported java version to 17 in order to work with spring \ No newline at end of file diff --git a/pebble-spring/pebble-legacy-spring-boot-starter/pom.xml b/pebble-spring/pebble-legacy-spring-boot-starter/pom.xml new file mode 100644 index 000000000..495de9101 --- /dev/null +++ b/pebble-spring/pebble-legacy-spring-boot-starter/pom.xml @@ -0,0 +1,62 @@ + + + 4.0.0 + + pebble-spring + io.pebbletemplates + 4.0.0-SNAPSHOT + + + pebble-legacy-spring-boot-starter + + Pebble Spring Boot 2 Starter + Spring Boot 2 starter for Pebble Template Engine + http://pebbletemplates.io + + + 2.7.5 + + + + + org.springframework.boot + spring-boot-starter-web + ${boot.version} + true + + + org.springframework.boot + spring-boot-starter-webflux + ${boot.version} + true + + + io.pebbletemplates + pebble-spring5 + ${project.version} + + + + + org.springframework.boot + spring-boot-starter-test + ${boot.version} + test + + + + + + + maven-jar-plugin + + + + io.pebbletemplates.spring.boot + + + + + + + \ No newline at end of file diff --git a/pebble-spring/pebble-legacy-spring-boot-starter/src/main/java/com/mitchellbosecke/pebble/boot/autoconfigure/AbstractPebbleConfiguration.java b/pebble-spring/pebble-legacy-spring-boot-starter/src/main/java/com/mitchellbosecke/pebble/boot/autoconfigure/AbstractPebbleConfiguration.java new file mode 100644 index 000000000..b8b9b5406 --- /dev/null +++ b/pebble-spring/pebble-legacy-spring-boot-starter/src/main/java/com/mitchellbosecke/pebble/boot/autoconfigure/AbstractPebbleConfiguration.java @@ -0,0 +1,14 @@ +package com.mitchellbosecke.pebble.boot.autoconfigure; + +abstract class AbstractPebbleConfiguration { + + protected String stripLeadingSlash(String value) { + if (value == null) { + return null; + } + if (value.startsWith("/")) { + return value.substring(1); + } + return value; + } +} diff --git a/pebble-spring/pebble-legacy-spring-boot-starter/src/main/java/com/mitchellbosecke/pebble/boot/autoconfigure/PebbleAutoConfiguration.java b/pebble-spring/pebble-legacy-spring-boot-starter/src/main/java/com/mitchellbosecke/pebble/boot/autoconfigure/PebbleAutoConfiguration.java new file mode 100644 index 000000000..1e56d8c9b --- /dev/null +++ b/pebble-spring/pebble-legacy-spring-boot-starter/src/main/java/com/mitchellbosecke/pebble/boot/autoconfigure/PebbleAutoConfiguration.java @@ -0,0 +1,68 @@ +package com.mitchellbosecke.pebble.boot.autoconfigure; + +import com.mitchellbosecke.pebble.PebbleEngine; +import com.mitchellbosecke.pebble.attributes.methodaccess.MethodAccessValidator; +import com.mitchellbosecke.pebble.extension.Extension; +import com.mitchellbosecke.pebble.loader.ClasspathLoader; +import com.mitchellbosecke.pebble.loader.Loader; +import com.mitchellbosecke.pebble.spring.extension.SpringExtension; +import java.util.List; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.MessageSource; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.lang.Nullable; + +@Configuration(proxyBeanMethods = false) +@ConditionalOnClass(PebbleEngine.class) +@EnableConfigurationProperties(PebbleProperties.class) +@Import({PebbleServletWebConfiguration.class, PebbleReactiveWebConfiguration.class}) +public class PebbleAutoConfiguration extends AbstractPebbleConfiguration { + + @Bean + @ConditionalOnMissingBean(name = "pebbleLoader") + public Loader pebbleLoader(PebbleProperties properties) { + ClasspathLoader loader = new ClasspathLoader(); + loader.setCharset(properties.getCharsetName()); + // classpath loader does not like leading slashes in resource paths + loader.setPrefix(this.stripLeadingSlash(properties.getPrefix())); + loader.setSuffix(properties.getSuffix()); + return loader; + } + + @Bean + @ConditionalOnMissingBean + public SpringExtension springExtension(MessageSource messageSource) { + return new SpringExtension(messageSource); + } + + @Bean + @ConditionalOnMissingBean(name = "pebbleEngine") + public PebbleEngine pebbleEngine(PebbleProperties properties, + Loader pebbleLoader, + SpringExtension springExtension, + @Nullable List extensions, + @Nullable MethodAccessValidator methodAccessValidator) { + PebbleEngine.Builder builder = new PebbleEngine.Builder(); + builder.loader(pebbleLoader); + builder.extension(springExtension); + if (extensions != null && !extensions.isEmpty()) { + builder.extension(extensions.toArray(new Extension[extensions.size()])); + } + if (!properties.isCache()) { + builder.cacheActive(false); + } + if (properties.getDefaultLocale() != null) { + builder.defaultLocale(properties.getDefaultLocale()); + } + builder.strictVariables(properties.isStrictVariables()); + builder.greedyMatchMethod(properties.isGreedyMatchMethod()); + if (methodAccessValidator != null) { + builder.methodAccessValidator(methodAccessValidator); + } + return builder.build(); + } +} diff --git a/pebble-spring/pebble-legacy-spring-boot-starter/src/main/java/com/mitchellbosecke/pebble/boot/autoconfigure/PebbleProperties.java b/pebble-spring/pebble-legacy-spring-boot-starter/src/main/java/com/mitchellbosecke/pebble/boot/autoconfigure/PebbleProperties.java new file mode 100644 index 000000000..53a421e98 --- /dev/null +++ b/pebble-spring/pebble-legacy-spring-boot-starter/src/main/java/com/mitchellbosecke/pebble/boot/autoconfigure/PebbleProperties.java @@ -0,0 +1,45 @@ +package com.mitchellbosecke.pebble.boot.autoconfigure; + +import java.util.Locale; +import org.springframework.boot.autoconfigure.template.AbstractTemplateViewResolverProperties; +import org.springframework.boot.context.properties.ConfigurationProperties; + +@ConfigurationProperties("pebble") +public class PebbleProperties extends AbstractTemplateViewResolverProperties { + + public static final String DEFAULT_PREFIX = "/templates/"; + public static final String DEFAULT_SUFFIX = ".pebble"; + + private Locale defaultLocale; + private boolean strictVariables; + private boolean greedyMatchMethod; + + public PebbleProperties() { + super(DEFAULT_PREFIX, DEFAULT_SUFFIX); + this.setCache(true); + } + + public Locale getDefaultLocale() { + return this.defaultLocale; + } + + public void setDefaultLocale(Locale defaultLocale) { + this.defaultLocale = defaultLocale; + } + + public boolean isStrictVariables() { + return this.strictVariables; + } + + public void setStrictVariables(boolean strictVariables) { + this.strictVariables = strictVariables; + } + + public boolean isGreedyMatchMethod() { + return this.greedyMatchMethod; + } + + public void setGreedyMatchMethod(boolean greedyMatchMethod) { + this.greedyMatchMethod = greedyMatchMethod; + } +} diff --git a/pebble-spring/pebble-legacy-spring-boot-starter/src/main/java/com/mitchellbosecke/pebble/boot/autoconfigure/PebbleReactiveWebConfiguration.java b/pebble-spring/pebble-legacy-spring-boot-starter/src/main/java/com/mitchellbosecke/pebble/boot/autoconfigure/PebbleReactiveWebConfiguration.java new file mode 100644 index 000000000..4d40b79ff --- /dev/null +++ b/pebble-spring/pebble-legacy-spring-boot-starter/src/main/java/com/mitchellbosecke/pebble/boot/autoconfigure/PebbleReactiveWebConfiguration.java @@ -0,0 +1,32 @@ +package com.mitchellbosecke.pebble.boot.autoconfigure; + +import com.mitchellbosecke.pebble.PebbleEngine; +import com.mitchellbosecke.pebble.loader.ClasspathLoader; +import com.mitchellbosecke.pebble.spring.reactive.PebbleReactiveViewResolver; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; +import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration(proxyBeanMethods = false) +@ConditionalOnWebApplication(type = Type.REACTIVE) +class PebbleReactiveWebConfiguration extends AbstractPebbleConfiguration { + + @Bean + @ConditionalOnMissingBean + PebbleReactiveViewResolver pebbleReactiveViewResolver(PebbleProperties properties, + PebbleEngine pebbleEngine) { + String prefix = properties.getPrefix(); + if (pebbleEngine.getLoader() instanceof ClasspathLoader) { + // classpathloader doesn't like leading slashes in paths + prefix = this.stripLeadingSlash(properties.getPrefix()); + } + PebbleReactiveViewResolver resolver = new PebbleReactiveViewResolver(pebbleEngine); + resolver.setPrefix(prefix); + resolver.setSuffix(properties.getSuffix()); + resolver.setViewNames(properties.getViewNames()); + resolver.setRequestContextAttribute(properties.getRequestContextAttribute()); + return resolver; + } +} diff --git a/pebble-spring/pebble-legacy-spring-boot-starter/src/main/java/com/mitchellbosecke/pebble/boot/autoconfigure/PebbleServletWebConfiguration.java b/pebble-spring/pebble-legacy-spring-boot-starter/src/main/java/com/mitchellbosecke/pebble/boot/autoconfigure/PebbleServletWebConfiguration.java new file mode 100644 index 000000000..b42b16a4e --- /dev/null +++ b/pebble-spring/pebble-legacy-spring-boot-starter/src/main/java/com/mitchellbosecke/pebble/boot/autoconfigure/PebbleServletWebConfiguration.java @@ -0,0 +1,29 @@ +package com.mitchellbosecke.pebble.boot.autoconfigure; + +import com.mitchellbosecke.pebble.PebbleEngine; +import com.mitchellbosecke.pebble.loader.ClasspathLoader; +import com.mitchellbosecke.pebble.spring.servlet.PebbleViewResolver; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; +import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration(proxyBeanMethods = false) +@ConditionalOnWebApplication(type = Type.SERVLET) +class PebbleServletWebConfiguration extends AbstractPebbleConfiguration { + + @Bean + @ConditionalOnMissingBean + PebbleViewResolver pebbleViewResolver(PebbleProperties properties, + PebbleEngine pebbleEngine) { + PebbleViewResolver pvr = new PebbleViewResolver(pebbleEngine); + properties.applyToMvcViewResolver(pvr); + if (pebbleEngine.getLoader() instanceof ClasspathLoader) { + // classpathloader doesn't like leading slashes in paths + pvr.setPrefix(this.stripLeadingSlash(properties.getPrefix())); + } + + return pvr; + } +} diff --git a/pebble-spring/pebble-legacy-spring-boot-starter/src/main/java/com/mitchellbosecke/pebble/boot/autoconfigure/PebbleTemplateAvailabilityProvider.java b/pebble-spring/pebble-legacy-spring-boot-starter/src/main/java/com/mitchellbosecke/pebble/boot/autoconfigure/PebbleTemplateAvailabilityProvider.java new file mode 100644 index 000000000..b3a52f6f3 --- /dev/null +++ b/pebble-spring/pebble-legacy-spring-boot-starter/src/main/java/com/mitchellbosecke/pebble/boot/autoconfigure/PebbleTemplateAvailabilityProvider.java @@ -0,0 +1,23 @@ +package com.mitchellbosecke.pebble.boot.autoconfigure; + +import org.springframework.boot.autoconfigure.template.TemplateAvailabilityProvider; +import org.springframework.core.env.Environment; +import org.springframework.core.io.ResourceLoader; +import org.springframework.util.ClassUtils; + +import static org.springframework.core.io.ResourceLoader.CLASSPATH_URL_PREFIX; + +public class PebbleTemplateAvailabilityProvider implements TemplateAvailabilityProvider { + + @Override + public boolean isTemplateAvailable(String view, Environment environment, ClassLoader classLoader, + ResourceLoader resourceLoader) { + if (ClassUtils.isPresent("com.mitchellbosecke.pebble.PebbleEngine", classLoader)) { + String prefix = environment.getProperty("pebble.prefix", PebbleProperties.DEFAULT_PREFIX); + String suffix = environment.getProperty("pebble.suffix", PebbleProperties.DEFAULT_SUFFIX); + return resourceLoader.getResource(CLASSPATH_URL_PREFIX + prefix + view + suffix).exists(); + } + return false; + } + +} diff --git a/pebble-spring/pebble-legacy-spring-boot-starter/src/main/java/com/mitchellbosecke/pebble/boot/autoconfigure/package-info.java b/pebble-spring/pebble-legacy-spring-boot-starter/src/main/java/com/mitchellbosecke/pebble/boot/autoconfigure/package-info.java new file mode 100644 index 000000000..2066b0801 --- /dev/null +++ b/pebble-spring/pebble-legacy-spring-boot-starter/src/main/java/com/mitchellbosecke/pebble/boot/autoconfigure/package-info.java @@ -0,0 +1,4 @@ +/** + * Auto-configuration for Pebble Template Engine. + */ +package com.mitchellbosecke.pebble.boot.autoconfigure; \ No newline at end of file diff --git a/pebble-spring/pebble-legacy-spring-boot-starter/src/main/resources/META-INF/spring.factories b/pebble-spring/pebble-legacy-spring-boot-starter/src/main/resources/META-INF/spring.factories new file mode 100644 index 000000000..803001fae --- /dev/null +++ b/pebble-spring/pebble-legacy-spring-boot-starter/src/main/resources/META-INF/spring.factories @@ -0,0 +1,6 @@ +# Auto Configure +org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ +com.mitchellbosecke.pebble.boot.autoconfigure.PebbleAutoConfiguration +# Template availability providers +org.springframework.boot.autoconfigure.template.TemplateAvailabilityProvider=\ +com.mitchellbosecke.pebble.boot.autoconfigure.PebbleTemplateAvailabilityProvider \ No newline at end of file diff --git a/pebble-spring/pebble-legacy-spring-boot-starter/src/main/resources/META-INF/spring.provides b/pebble-spring/pebble-legacy-spring-boot-starter/src/main/resources/META-INF/spring.provides new file mode 100644 index 000000000..fa6b11812 --- /dev/null +++ b/pebble-spring/pebble-legacy-spring-boot-starter/src/main/resources/META-INF/spring.provides @@ -0,0 +1 @@ +provides: pebble,pebble-spring5 diff --git a/pebble-spring/pebble-legacy-spring-boot-starter/src/test/java/com/mitchellbosecke/pebble/boot/AppConfig.java b/pebble-spring/pebble-legacy-spring-boot-starter/src/test/java/com/mitchellbosecke/pebble/boot/AppConfig.java new file mode 100644 index 000000000..eca8f60f7 --- /dev/null +++ b/pebble-spring/pebble-legacy-spring-boot-starter/src/test/java/com/mitchellbosecke/pebble/boot/AppConfig.java @@ -0,0 +1,64 @@ +package com.mitchellbosecke.pebble.boot; + +import com.mitchellbosecke.pebble.extension.AbstractExtension; +import com.mitchellbosecke.pebble.extension.Extension; +import com.mitchellbosecke.pebble.extension.Function; +import com.mitchellbosecke.pebble.template.EvaluationContext; +import com.mitchellbosecke.pebble.template.PebbleTemplate; + +import org.springframework.context.MessageSource; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.support.ResourceBundleMessageSource; +import org.springframework.web.servlet.LocaleResolver; +import org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver; + +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +@Configuration(proxyBeanMethods = false) +public class AppConfig { + @Bean + public MessageSource messageSource() { + ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource(); + messageSource.setBasename("messages"); + messageSource.setFallbackToSystemLocale(false); + return messageSource; + } + + @Bean + public LocaleResolver localeResolver() { + return new AcceptHeaderLocaleResolver(); + } + + @Bean + public Extension testExtension() { + return new TestExtension(); + } + + public static class TestExtension extends AbstractExtension { + + @Override + public Map getFunctions() { + Map functions = new HashMap(); + functions.put("testFunction", new TestFunction()); + return functions; + } + + public static class TestFunction implements Function { + + @Override + public List getArgumentNames() { + return Collections.emptyList(); + } + + @Override + public Object execute(Map args, PebbleTemplate self, + EvaluationContext context, int lineNumber) { + return "Tested!"; + } + } + } +} diff --git a/pebble-spring/pebble-legacy-spring-boot-starter/src/test/java/com/mitchellbosecke/pebble/boot/Application.java b/pebble-spring/pebble-legacy-spring-boot-starter/src/test/java/com/mitchellbosecke/pebble/boot/Application.java new file mode 100644 index 000000000..5a8c365d1 --- /dev/null +++ b/pebble-spring/pebble-legacy-spring-boot-starter/src/test/java/com/mitchellbosecke/pebble/boot/Application.java @@ -0,0 +1,12 @@ +package com.mitchellbosecke.pebble.boot; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class Application { + + public static void main(String[] args) { + SpringApplication.run(Application.class, args); + } +} \ No newline at end of file diff --git a/pebble-spring/pebble-legacy-spring-boot-starter/src/test/java/com/mitchellbosecke/pebble/boot/Controllers.java b/pebble-spring/pebble-legacy-spring-boot-starter/src/test/java/com/mitchellbosecke/pebble/boot/Controllers.java new file mode 100644 index 000000000..795b843de --- /dev/null +++ b/pebble-spring/pebble-legacy-spring-boot-starter/src/test/java/com/mitchellbosecke/pebble/boot/Controllers.java @@ -0,0 +1,39 @@ +package com.mitchellbosecke.pebble.boot; + +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.RequestMapping; + +@Controller +public class Controllers { + + @RequestMapping("/hello.action") + public String hello() { + return "hello"; + } + + @RequestMapping("/index.action") + public String index() { + return "index"; + } + + @RequestMapping("/contextPath.action") + public String contextPath() { + return "contextPath"; + } + + @RequestMapping("/extensions.action") + public String extensions() { + return "extensions"; + } + + @RequestMapping("/beans.action") + public String beans() { + return "beans"; + } + + @RequestMapping("/response.action") + public String response() { + return "responseObject"; + } + +} \ No newline at end of file diff --git a/pebble-spring/pebble-legacy-spring-boot-starter/src/test/java/com/mitchellbosecke/pebble/boot/Foo.java b/pebble-spring/pebble-legacy-spring-boot-starter/src/test/java/com/mitchellbosecke/pebble/boot/Foo.java new file mode 100644 index 000000000..f45315082 --- /dev/null +++ b/pebble-spring/pebble-legacy-spring-boot-starter/src/test/java/com/mitchellbosecke/pebble/boot/Foo.java @@ -0,0 +1,9 @@ +package com.mitchellbosecke.pebble.boot; + +import org.springframework.stereotype.Component; + +@Component +public class Foo { + + public String value = "bar"; +} diff --git a/pebble-spring/pebble-legacy-spring-boot-starter/src/test/java/com/mitchellbosecke/pebble/boot/autoconfigure/NonWebAppTests.java b/pebble-spring/pebble-legacy-spring-boot-starter/src/test/java/com/mitchellbosecke/pebble/boot/autoconfigure/NonWebAppTests.java new file mode 100644 index 000000000..007e81269 --- /dev/null +++ b/pebble-spring/pebble-legacy-spring-boot-starter/src/test/java/com/mitchellbosecke/pebble/boot/autoconfigure/NonWebAppTests.java @@ -0,0 +1,25 @@ +package com.mitchellbosecke.pebble.boot.autoconfigure; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.mitchellbosecke.pebble.PebbleEngine; +import com.mitchellbosecke.pebble.boot.Application; +import java.io.StringWriter; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest(classes = Application.class, + properties = "spring.main.web-application-type=none") +class NonWebAppTests { + + @Autowired + private PebbleEngine pebbleEngine; + + @Test + void testOk() throws Exception { + StringWriter sw = new StringWriter(); + this.pebbleEngine.getTemplate("hello").evaluate(sw); + assertThat(sw.toString() != null && !sw.toString().isEmpty()).isTrue(); + } +} diff --git a/pebble-spring/pebble-legacy-spring-boot-starter/src/test/java/com/mitchellbosecke/pebble/boot/autoconfigure/PebbleAutoConfigurationTest.java b/pebble-spring/pebble-legacy-spring-boot-starter/src/test/java/com/mitchellbosecke/pebble/boot/autoconfigure/PebbleAutoConfigurationTest.java new file mode 100644 index 000000000..9ed885476 --- /dev/null +++ b/pebble-spring/pebble-legacy-spring-boot-starter/src/test/java/com/mitchellbosecke/pebble/boot/autoconfigure/PebbleAutoConfigurationTest.java @@ -0,0 +1,200 @@ +package com.mitchellbosecke.pebble.boot.autoconfigure; + +import static java.util.Locale.CHINESE; +import static java.util.Locale.FRENCH; +import static org.assertj.core.api.Assertions.assertThat; + +import com.mitchellbosecke.pebble.PebbleEngine; +import com.mitchellbosecke.pebble.attributes.methodaccess.BlacklistMethodAccessValidator; +import com.mitchellbosecke.pebble.attributes.methodaccess.MethodAccessValidator; +import com.mitchellbosecke.pebble.attributes.methodaccess.NoOpMethodAccessValidator; +import com.mitchellbosecke.pebble.loader.Loader; +import com.mitchellbosecke.pebble.spring.extension.SpringExtension; +import com.mitchellbosecke.pebble.spring.reactive.PebbleReactiveViewResolver; +import com.mitchellbosecke.pebble.spring.servlet.PebbleViewResolver; +import java.util.Locale; +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.util.TestPropertyValues; +import org.springframework.boot.web.reactive.context.AnnotationConfigReactiveWebApplicationContext; +import org.springframework.boot.web.servlet.context.AnnotationConfigServletWebApplicationContext; +import org.springframework.context.MessageSource; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; + +class PebbleAutoConfigurationTest { + + private static final Locale DEFAULT_LOCALE = CHINESE; + private static final Locale CUSTOM_LOCALE = FRENCH; + private AnnotationConfigServletWebApplicationContext webContext; + + private AnnotationConfigReactiveWebApplicationContext reactiveWebContext; + + @Test + void registerBeansForServletApp() { + this.loadWithServlet(null); + assertThat(this.webContext.getBeansOfType(Loader.class)).hasSize(1); + assertThat(this.webContext.getBeansOfType(SpringExtension.class)).hasSize(1); + assertThat(this.webContext.getBeansOfType(PebbleEngine.class)).hasSize(1); + assertThat(this.webContext.getBean(PebbleEngine.class).getDefaultLocale()) + .isEqualTo(DEFAULT_LOCALE); + assertThat(this.webContext.getBean(PebbleEngine.class).isStrictVariables()).isTrue(); + assertThat( + this.webContext.getBean(PebbleEngine.class).getEvaluationOptions().isGreedyMatchMethod()) + .isTrue(); + assertThat(this.webContext.getBean(PebbleEngine.class).getEvaluationOptions() + .getMethodAccessValidator()).isInstanceOf( + BlacklistMethodAccessValidator.class); + assertThat(this.webContext.getBeansOfType(PebbleViewResolver.class)).hasSize(1); + } + + @Test + void registerCompilerForServletApp() { + this.loadWithServlet(CustomPebbleEngineCompilerConfiguration.class); + assertThat(this.webContext.getBeansOfType(Loader.class)).hasSize(1); + assertThat(this.webContext.getBeansOfType(SpringExtension.class)).hasSize(1); + assertThat(this.webContext.getBeansOfType(PebbleEngine.class)).hasSize(1); + assertThat(this.webContext.getBean(PebbleEngine.class).getDefaultLocale()) + .isEqualTo(CUSTOM_LOCALE); + assertThat(this.webContext.getBean(PebbleEngine.class).isStrictVariables()).isFalse(); + assertThat( + this.webContext.getBean(PebbleEngine.class).getEvaluationOptions().isGreedyMatchMethod()) + .isFalse(); + assertThat(this.webContext.getBean(PebbleEngine.class).getEvaluationOptions() + .getMethodAccessValidator()).isInstanceOf( + BlacklistMethodAccessValidator.class); + assertThat(this.webContext.getBeansOfType(PebbleViewResolver.class)).hasSize(1); + assertThat(this.webContext.getBeansOfType(PebbleReactiveViewResolver.class)).isEmpty(); + } + + @Test + void registerCustomMethodAccessValidatorForServletApp() { + this.loadWithServlet(CustomMethodAccessValidatorConfiguration.class); + assertThat(this.webContext.getBeansOfType(Loader.class)).hasSize(1); + assertThat(this.webContext.getBeansOfType(SpringExtension.class)).hasSize(1); + assertThat(this.webContext.getBeansOfType(PebbleEngine.class)).hasSize(1); + assertThat(this.webContext.getBean(PebbleEngine.class).getDefaultLocale()) + .isEqualTo(DEFAULT_LOCALE); + assertThat(this.webContext.getBean(PebbleEngine.class).isStrictVariables()).isTrue(); + assertThat( + this.webContext.getBean(PebbleEngine.class).getEvaluationOptions().isGreedyMatchMethod()) + .isTrue(); + assertThat(this.webContext.getBean(PebbleEngine.class).getEvaluationOptions() + .getMethodAccessValidator()).isInstanceOf( + NoOpMethodAccessValidator.class); + assertThat(this.webContext.getBeansOfType(PebbleViewResolver.class)).hasSize(1); + assertThat(this.webContext.getBeansOfType(PebbleReactiveViewResolver.class)).isEmpty(); + } + + @Test + void registerBeansForReactiveApp() { + this.loadWithReactive(null); + assertThat(this.reactiveWebContext.getBeansOfType(Loader.class)).hasSize(1); + assertThat(this.reactiveWebContext.getBeansOfType(SpringExtension.class)).hasSize(1); + assertThat(this.reactiveWebContext.getBeansOfType(PebbleEngine.class)).hasSize(1); + assertThat(this.reactiveWebContext.getBean(PebbleEngine.class).getDefaultLocale()) + .isEqualTo(DEFAULT_LOCALE); + assertThat(this.reactiveWebContext.getBean(PebbleEngine.class).isStrictVariables()).isTrue(); + assertThat(this.reactiveWebContext.getBean(PebbleEngine.class).getEvaluationOptions() + .isGreedyMatchMethod()).isTrue(); + assertThat(this.reactiveWebContext.getBean(PebbleEngine.class).getEvaluationOptions() + .getMethodAccessValidator()).isInstanceOf( + BlacklistMethodAccessValidator.class); + assertThat(this.reactiveWebContext.getBeansOfType(PebbleViewResolver.class)).isEmpty(); + assertThat(this.reactiveWebContext.getBeansOfType(PebbleReactiveViewResolver.class)).hasSize(1); + assertThat(this.reactiveWebContext.getBeansOfType(PebbleViewResolver.class)).isEmpty(); + } + + @Test + void registerCompilerForReactiveApp() { + this.loadWithReactive(CustomPebbleEngineCompilerConfiguration.class); + assertThat(this.reactiveWebContext.getBeansOfType(Loader.class)).hasSize(1); + assertThat(this.reactiveWebContext.getBeansOfType(SpringExtension.class)).hasSize(1); + assertThat(this.reactiveWebContext.getBeansOfType(PebbleEngine.class)).hasSize(1); + assertThat(this.reactiveWebContext.getBean(PebbleEngine.class).getDefaultLocale()) + .isEqualTo(CUSTOM_LOCALE); + assertThat(this.reactiveWebContext.getBean(PebbleEngine.class).isStrictVariables()).isFalse(); + assertThat(this.reactiveWebContext.getBean(PebbleEngine.class).getEvaluationOptions() + .isGreedyMatchMethod()).isFalse(); + assertThat(this.reactiveWebContext.getBean(PebbleEngine.class).getEvaluationOptions() + .getMethodAccessValidator()).isInstanceOf( + BlacklistMethodAccessValidator.class); + assertThat(this.reactiveWebContext.getBeansOfType(PebbleReactiveViewResolver.class)).hasSize(1); + assertThat(this.reactiveWebContext.getBeansOfType(PebbleViewResolver.class)).isEmpty(); + } + + @Test + void registerCustomMethodAccessValidatorForReactiveApp() { + this.loadWithReactive(CustomMethodAccessValidatorConfiguration.class); + assertThat(this.reactiveWebContext.getBeansOfType(Loader.class)).hasSize(1); + assertThat(this.reactiveWebContext.getBeansOfType(SpringExtension.class)).hasSize(1); + assertThat(this.reactiveWebContext.getBeansOfType(PebbleEngine.class)).hasSize(1); + assertThat(this.reactiveWebContext.getBean(PebbleEngine.class).getDefaultLocale()) + .isEqualTo(DEFAULT_LOCALE); + assertThat(this.reactiveWebContext.getBean(PebbleEngine.class).isStrictVariables()).isTrue(); + assertThat(this.reactiveWebContext.getBean(PebbleEngine.class).getEvaluationOptions() + .isGreedyMatchMethod()).isTrue(); + assertThat(this.reactiveWebContext.getBean(PebbleEngine.class).getEvaluationOptions() + .getMethodAccessValidator()).isInstanceOf( + NoOpMethodAccessValidator.class); + assertThat(this.reactiveWebContext.getBeansOfType(PebbleViewResolver.class)).isEmpty(); + assertThat(this.reactiveWebContext.getBeansOfType(PebbleReactiveViewResolver.class)).hasSize(1); + assertThat(this.reactiveWebContext.getBeansOfType(PebbleViewResolver.class)).isEmpty(); + } + + private void loadWithServlet(Class config) { + this.webContext = new AnnotationConfigServletWebApplicationContext(); + TestPropertyValues.of("pebble.prefix=classpath:/templates/").applyTo(this.webContext); + TestPropertyValues.of("pebble.defaultLocale=zh").applyTo(this.webContext); + TestPropertyValues.of("pebble.strictVariables=true").applyTo(this.webContext); + TestPropertyValues.of("pebble.greedyMatchMethod=true").applyTo(this.webContext); + if (config != null) { + this.webContext.register(config); + } + this.webContext.register(BaseConfiguration.class); + this.webContext.refresh(); + } + + private void loadWithReactive(Class config) { + this.reactiveWebContext = new AnnotationConfigReactiveWebApplicationContext(); + TestPropertyValues.of("pebble.prefix=classpath:/templates/").applyTo(this.reactiveWebContext); + TestPropertyValues.of("pebble.defaultLocale=zh").applyTo(this.reactiveWebContext); + TestPropertyValues.of("pebble.strictVariables=true").applyTo(this.reactiveWebContext); + TestPropertyValues.of("pebble.greedyMatchMethod=true").applyTo(this.reactiveWebContext); + if (config != null) { + this.reactiveWebContext.register(config); + } + this.reactiveWebContext.register(BaseConfiguration.class); + this.reactiveWebContext.refresh(); + } + + @Configuration(proxyBeanMethods = false) + @Import(PebbleAutoConfiguration.class) + protected static class BaseConfiguration { + + } + + @Configuration(proxyBeanMethods = false) + protected static class CustomPebbleEngineCompilerConfiguration { + + @Bean + public PebbleEngine pebbleEngine() { + return new PebbleEngine.Builder().defaultLocale(CUSTOM_LOCALE).build(); + } + + @Bean + public SpringExtension customSpringExtension(MessageSource messageSource) { + return new SpringExtension(messageSource); + } + } + + @Configuration(proxyBeanMethods = false) + protected static class CustomMethodAccessValidatorConfiguration { + + @Bean + public MethodAccessValidator methodAccessValidator() { + return new NoOpMethodAccessValidator(); + } + } + +} \ No newline at end of file diff --git a/pebble-spring/pebble-legacy-spring-boot-starter/src/test/java/com/mitchellbosecke/pebble/boot/autoconfigure/ReactiveAppTest.java b/pebble-spring/pebble-legacy-spring-boot-starter/src/test/java/com/mitchellbosecke/pebble/boot/autoconfigure/ReactiveAppTest.java new file mode 100644 index 000000000..60d6a8b26 --- /dev/null +++ b/pebble-spring/pebble-legacy-spring-boot-starter/src/test/java/com/mitchellbosecke/pebble/boot/autoconfigure/ReactiveAppTest.java @@ -0,0 +1,100 @@ +package com.mitchellbosecke.pebble.boot.autoconfigure; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.mitchellbosecke.pebble.boot.Application; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.MediaType; +import org.springframework.test.web.reactive.server.WebTestClient; + +@SpringBootTest(classes = Application.class, + webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, + properties = "spring.main.web-application-type=reactive") +class ReactiveAppTest { + + @Autowired + private WebTestClient client; + + @Test + void testOk() throws Exception { + String result = this.client.get().uri("/index.action").exchange() + .expectStatus().isOk() + .expectHeader().contentTypeCompatibleWith(MediaType.TEXT_HTML) + .expectBody(String.class) + .returnResult().getResponseBody(); + + assertThat(result).isEqualTo("Hello Pebbleworld!"); + } + + @Test + void testRequestAccess() throws Exception { + String result = this.client.get().uri("/contextPath.action").exchange() + .expectStatus().isOk() + .expectHeader().contentTypeCompatibleWith(MediaType.TEXT_HTML) + .expectBody(String.class) + .returnResult().getResponseBody(); + + assertThat(result).isEqualTo("ctx path:/contextPath.action"); + } + + @Test + void testEnglishHello() throws Exception { + String result = this.client.get().uri("/hello.action") + .header("Accept-Language", "en").exchange() + .expectStatus().isOk() + .expectHeader().contentTypeCompatibleWith(MediaType.TEXT_HTML) + .expectBody(String.class) + .returnResult().getResponseBody(); + + assertThat(result).isEqualTo("Hello Boot!"); + } + + @Test + void testSpanishHello() throws Exception { + String result = this.client.get().uri("/hello.action") + .header("Accept-Language", "es").exchange() + .expectStatus().isOk() + .expectHeader().contentTypeCompatibleWith(MediaType.TEXT_HTML) + .expectBody(String.class) + .returnResult().getResponseBody(); + + assertThat(result).isEqualTo("Hola Boot!"); + } + + @Test + void testAdditionalExtensions() throws Exception { + String result = this.client.get().uri("/extensions.action") + .header("Accept-Language", "es").exchange() + .expectStatus().isOk() + .expectHeader().contentTypeCompatibleWith(MediaType.TEXT_HTML) + .expectBody(String.class) + .returnResult().getResponseBody(); + + assertThat(result).isEqualTo("Hola Boot! Tested!"); + } + + @Test + void testBeansAccess() throws Exception { + String result = this.client.get().uri("/beans.action").exchange() + .expectStatus().isOk() + .expectHeader().contentTypeCompatibleWith(MediaType.TEXT_HTML) + .expectBody(String.class) + .returnResult().getResponseBody(); + + assertThat(result).isEqualTo("beans:bar"); + } + + @Test + void testResponseAccess() throws Exception { + String result = this.client.get().uri("/response.action").exchange() + .expectStatus().isOk() + .expectHeader().contentTypeCompatibleWith(MediaType.TEXT_HTML) + .expectBody(String.class) + .returnResult().getResponseBody(); + + assertThat(result).isEqualTo("response:200 OK"); + } +} + diff --git a/pebble-spring/pebble-legacy-spring-boot-starter/src/test/java/com/mitchellbosecke/pebble/boot/autoconfigure/ServletAppTest.java b/pebble-spring/pebble-legacy-spring-boot-starter/src/test/java/com/mitchellbosecke/pebble/boot/autoconfigure/ServletAppTest.java new file mode 100644 index 000000000..b4bdeb587 --- /dev/null +++ b/pebble-spring/pebble-legacy-spring-boot-starter/src/test/java/com/mitchellbosecke/pebble/boot/autoconfigure/ServletAppTest.java @@ -0,0 +1,88 @@ +package com.mitchellbosecke.pebble.boot.autoconfigure; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import com.mitchellbosecke.pebble.boot.Application; +import java.util.Locale; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.MvcResult; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.web.context.WebApplicationContext; + +@SpringBootTest(classes = Application.class, webEnvironment = WebEnvironment.RANDOM_PORT) +class ServletAppTest { + + @Autowired + private WebApplicationContext wac; + + protected MockMvc mockMvc; + + @BeforeEach + void setup() { + this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build(); + } + + @Test + void testOk() throws Exception { + this.mockMvc.perform(get("/index.action")).andExpect(status().isOk()) + .andExpect(content().contentTypeCompatibleWith(MediaType.TEXT_HTML)) + .andExpect(content().string("Hello Pebbleworld!")); + } + + @Test + void testRequestAccess() throws Exception { + MvcResult result = this.mockMvc.perform(get("/contextPath.action")).andExpect(status().isOk()) + .andExpect(content().contentTypeCompatibleWith(MediaType.TEXT_HTML)).andReturn(); + + assertThat(result.getResponse().getContentAsString()).isEqualTo("ctx path:" + result.getRequest().getContextPath()); + } + + @Test + void testEnglishHello() throws Exception { + this.mockMvc.perform(get("/hello.action").locale(Locale.forLanguageTag("en"))) + .andExpect(status().isOk()) + .andExpect(content().contentTypeCompatibleWith(MediaType.TEXT_HTML)) + .andExpect(content().string("Hello Boot!")); + } + + @Test + void testSpanishHello() throws Exception { + this.mockMvc.perform(get("/hello.action").locale(Locale.forLanguageTag("es"))) + .andExpect(status().isOk()) + .andExpect(content().contentTypeCompatibleWith(MediaType.TEXT_HTML)) + .andExpect(content().string("Hola Boot!")); + } + + @Test + void testAdditionalExtensions() throws Exception { + this.mockMvc.perform(get("/extensions.action").locale(Locale.forLanguageTag("es"))) + .andExpect(status().isOk()) + .andExpect(content().contentTypeCompatibleWith(MediaType.TEXT_HTML)) + .andExpect(content().string("Hola Boot! Tested!")); + } + + @Test + void testBeansAccess() throws Exception { + this.mockMvc.perform(get("/beans.action")) + .andExpect(status().isOk()) + .andExpect(content().contentTypeCompatibleWith(MediaType.TEXT_HTML)) + .andExpect(content().string("beans:bar")); + } + + @Test + void testResponseAccess() throws Exception { + this.mockMvc.perform(get("/response.action")) + .andExpect(status().isOk()) + .andExpect(content().contentTypeCompatibleWith(MediaType.TEXT_HTML)) + .andExpect(content().string("response:200")); + } +} diff --git a/pebble-spring/pebble-legacy-spring-boot-starter/src/test/resources/messages.properties b/pebble-spring/pebble-legacy-spring-boot-starter/src/test/resources/messages.properties new file mode 100644 index 000000000..88f15adee --- /dev/null +++ b/pebble-spring/pebble-legacy-spring-boot-starter/src/test/resources/messages.properties @@ -0,0 +1 @@ +hello.boot=Hello Boot! \ No newline at end of file diff --git a/pebble-spring/pebble-legacy-spring-boot-starter/src/test/resources/messages_es.properties b/pebble-spring/pebble-legacy-spring-boot-starter/src/test/resources/messages_es.properties new file mode 100644 index 000000000..a9af80a5b --- /dev/null +++ b/pebble-spring/pebble-legacy-spring-boot-starter/src/test/resources/messages_es.properties @@ -0,0 +1 @@ +hello.boot=Hola Boot! \ No newline at end of file diff --git a/pebble-spring/pebble-legacy-spring-boot-starter/src/test/resources/templates/beans.pebble b/pebble-spring/pebble-legacy-spring-boot-starter/src/test/resources/templates/beans.pebble new file mode 100644 index 000000000..c0a95852f --- /dev/null +++ b/pebble-spring/pebble-legacy-spring-boot-starter/src/test/resources/templates/beans.pebble @@ -0,0 +1 @@ +beans:{{beans.foo.value}} \ No newline at end of file diff --git a/pebble-spring/pebble-legacy-spring-boot-starter/src/test/resources/templates/contextPath.pebble b/pebble-spring/pebble-legacy-spring-boot-starter/src/test/resources/templates/contextPath.pebble new file mode 100644 index 000000000..bf5fc36c5 --- /dev/null +++ b/pebble-spring/pebble-legacy-spring-boot-starter/src/test/resources/templates/contextPath.pebble @@ -0,0 +1 @@ +ctx path:{{request.contextPath}}{{request.path}} \ No newline at end of file diff --git a/pebble-spring/pebble-legacy-spring-boot-starter/src/test/resources/templates/extensions.pebble b/pebble-spring/pebble-legacy-spring-boot-starter/src/test/resources/templates/extensions.pebble new file mode 100644 index 000000000..f7020c4ae --- /dev/null +++ b/pebble-spring/pebble-legacy-spring-boot-starter/src/test/resources/templates/extensions.pebble @@ -0,0 +1 @@ +{{ message('hello.boot') }} {{ testFunction() }} \ No newline at end of file diff --git a/pebble-spring/pebble-legacy-spring-boot-starter/src/test/resources/templates/hello.pebble b/pebble-spring/pebble-legacy-spring-boot-starter/src/test/resources/templates/hello.pebble new file mode 100644 index 000000000..113ac77e3 --- /dev/null +++ b/pebble-spring/pebble-legacy-spring-boot-starter/src/test/resources/templates/hello.pebble @@ -0,0 +1 @@ +{{ message('hello.boot') }} \ No newline at end of file diff --git a/pebble-spring/pebble-legacy-spring-boot-starter/src/test/resources/templates/index.pebble b/pebble-spring/pebble-legacy-spring-boot-starter/src/test/resources/templates/index.pebble new file mode 100644 index 000000000..3222a1e37 --- /dev/null +++ b/pebble-spring/pebble-legacy-spring-boot-starter/src/test/resources/templates/index.pebble @@ -0,0 +1 @@ +{{'Hello Pebbleworld!'}} \ No newline at end of file diff --git a/pebble-spring/pebble-legacy-spring-boot-starter/src/test/resources/templates/responseObject.pebble b/pebble-spring/pebble-legacy-spring-boot-starter/src/test/resources/templates/responseObject.pebble new file mode 100644 index 000000000..1b11aa7d5 --- /dev/null +++ b/pebble-spring/pebble-legacy-spring-boot-starter/src/test/resources/templates/responseObject.pebble @@ -0,0 +1 @@ +response:{{response.status}}{{response.statusCode}} \ No newline at end of file diff --git a/pebble-spring/pebble-spring-boot-starter/pom.xml b/pebble-spring/pebble-spring-boot-starter/pom.xml index 68a253fac..e158dc1de 100644 --- a/pebble-spring/pebble-spring-boot-starter/pom.xml +++ b/pebble-spring/pebble-spring-boot-starter/pom.xml @@ -4,17 +4,17 @@ pebble-spring io.pebbletemplates - 3.1.7-SNAPSHOT + 4.0.0-SNAPSHOT pebble-spring-boot-starter - Pebble Spring Boot 2 Starter - Spring Boot 2 starter for Pebble Template Engine + Pebble Spring Boot 3 Starter + Spring Boot 3 starter for Pebble Template Engine http://pebbletemplates.io - 2.7.3 + 3.0.0-RC1 @@ -23,6 +23,12 @@ spring-boot-starter-web ${boot.version} true + + + ch.qos.logback + logback-classic + + org.springframework.boot @@ -32,7 +38,7 @@ io.pebbletemplates - pebble-spring5 + pebble-spring6 ${project.version} @@ -42,12 +48,6 @@ spring-boot-starter-test ${boot.version} test - - - org.junit.vintage - junit-vintage-engine - - diff --git a/pebble-spring/pebble-spring5/pom.xml b/pebble-spring/pebble-spring5/pom.xml index fd00f11a9..aa4cc78cb 100644 --- a/pebble-spring/pebble-spring5/pom.xml +++ b/pebble-spring/pebble-spring5/pom.xml @@ -4,7 +4,7 @@ pebble-spring io.pebbletemplates - 3.1.7-SNAPSHOT + 4.0.0-SNAPSHOT pebble-spring5 diff --git a/pebble-spring/pebble-spring6/pom.xml b/pebble-spring/pebble-spring6/pom.xml new file mode 100644 index 000000000..836eda21f --- /dev/null +++ b/pebble-spring/pebble-spring6/pom.xml @@ -0,0 +1,91 @@ + + + 4.0.0 + + pebble-spring + io.pebbletemplates + 4.0.0-SNAPSHOT + + + pebble-spring6 + + Pebble Integration with Spring 6.x + Pebble Integration with Spring 6.x + http://pebbletemplates.io + + + 5.0.0 + 6.0.0-RC2 + 4.8.1 + 5.9.1 + + + + + + org.springframework + spring-framework-bom + ${spring-framework.version} + pom + import + + + + + + + io.pebbletemplates + pebble + + + jakarta.servlet + jakarta.servlet-api + ${servlet-api.version} + provided + + + org.springframework + spring-webmvc + provided + + + org.springframework + spring-webflux + provided + + + + + org.springframework + spring-test + test + + + org.junit.jupiter + junit-jupiter + ${junit-jupiter.version} + test + + + org.mockito + mockito-junit-jupiter + ${mockito-junit-jupiter.version} + test + + + + + + + maven-jar-plugin + + + + io.pebbletemplates.spring + + + + + + + \ No newline at end of file diff --git a/pebble-spring/pebble-spring6/src/main/java/com/mitchellbosecke/pebble/spring/context/Beans.java b/pebble-spring/pebble-spring6/src/main/java/com/mitchellbosecke/pebble/spring/context/Beans.java new file mode 100644 index 000000000..f15751127 --- /dev/null +++ b/pebble-spring/pebble-spring6/src/main/java/com/mitchellbosecke/pebble/spring/context/Beans.java @@ -0,0 +1,97 @@ +/* + * Copyright (c) 2013 by Mitchell Bösecke + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +package com.mitchellbosecke.pebble.spring.context; + +import java.util.Arrays; +import java.util.Collection; +import java.util.LinkedHashSet; +import java.util.Map; +import java.util.Set; +import org.springframework.context.ApplicationContext; +import org.springframework.util.Assert; + +/** + *

+ * Special object made available to templates in Spring MVC applications in order to access beans in + * the Application Context. + *

+ * + * @author Eric Bussieres + */ +public class Beans implements Map { + + private final ApplicationContext ctx; + + public Beans(ApplicationContext ctx) { + Assert.notNull(ctx, "Application Context cannot be null"); + this.ctx = ctx; + } + + @Override + public void clear() { + throw new UnsupportedOperationException("Method \"clear\" not supported in Beans object"); + } + + @Override + public boolean containsKey(Object key) { + Assert.notNull(key, "Key cannot be null"); + return this.ctx.containsBean(key.toString()); + } + + @Override + public boolean containsValue(Object value) { + throw new UnsupportedOperationException( + "Method \"containsValue\" not supported in Beans object"); + } + + @Override + public Set> entrySet() { + throw new UnsupportedOperationException("Method \"entrySet\" not supported in Beans object"); + } + + @Override + public Object get(Object key) { + Assert.notNull(key, "Key cannot be null"); + return this.ctx.getBean(key.toString()); + } + + @Override + public boolean isEmpty() { + return this.size() <= 0; + } + + @Override + public Set keySet() { + return new LinkedHashSet<>(Arrays.asList(this.ctx.getBeanDefinitionNames())); + } + + @Override + public Object put(String key, Object value) { + throw new UnsupportedOperationException("Method \"put\" not supported in Beans object"); + } + + @Override + public void putAll(Map m) { + throw new UnsupportedOperationException("Method \"putAll\" not supported in Beans object"); + } + + @Override + public Object remove(Object key) { + throw new UnsupportedOperationException("Method \"remove\" not supported in Beans object"); + } + + @Override + public int size() { + return this.ctx.getBeanDefinitionCount(); + } + + @Override + public Collection values() { + throw new UnsupportedOperationException("Method \"values\" not supported in Beans object"); + } + +} diff --git a/pebble-spring/pebble-spring6/src/main/java/com/mitchellbosecke/pebble/spring/extension/SpringExtension.java b/pebble-spring/pebble-spring6/src/main/java/com/mitchellbosecke/pebble/spring/extension/SpringExtension.java new file mode 100644 index 000000000..888be297c --- /dev/null +++ b/pebble-spring/pebble-spring6/src/main/java/com/mitchellbosecke/pebble/spring/extension/SpringExtension.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2013 by Mitchell Bösecke + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +package com.mitchellbosecke.pebble.spring.extension; + +import com.mitchellbosecke.pebble.extension.AbstractExtension; +import com.mitchellbosecke.pebble.extension.Function; +import com.mitchellbosecke.pebble.spring.extension.function.HrefFunction; +import com.mitchellbosecke.pebble.spring.extension.function.MessageSourceFunction; +import com.mitchellbosecke.pebble.spring.extension.function.bindingresult.GetAllErrorsFunction; +import com.mitchellbosecke.pebble.spring.extension.function.bindingresult.GetFieldErrorsFunction; +import com.mitchellbosecke.pebble.spring.extension.function.bindingresult.GetGlobalErrorsFunction; +import com.mitchellbosecke.pebble.spring.extension.function.bindingresult.HasErrorsFunction; +import com.mitchellbosecke.pebble.spring.extension.function.bindingresult.HasFieldErrorsFunction; +import com.mitchellbosecke.pebble.spring.extension.function.bindingresult.HasGlobalErrorsFunction; +import java.util.HashMap; +import java.util.Map; +import org.springframework.context.MessageSource; + +/** + *

+ * Extension for PebbleEngine to add spring functionality + *

+ * + * @author Eric Bussieres + */ +public class SpringExtension extends AbstractExtension { + + private final MessageSource messageSource; + + public SpringExtension(MessageSource messageSource) { + this.messageSource = messageSource; + } + + @Override + public Map getFunctions() { + Map functions = new HashMap<>(); + functions + .put(MessageSourceFunction.FUNCTION_NAME, new MessageSourceFunction(this.messageSource)); + functions.put(HasErrorsFunction.FUNCTION_NAME, new HasErrorsFunction()); + functions.put(HasGlobalErrorsFunction.FUNCTION_NAME, new HasGlobalErrorsFunction()); + functions.put(HasFieldErrorsFunction.FUNCTION_NAME, new HasFieldErrorsFunction()); + functions.put(GetAllErrorsFunction.FUNCTION_NAME, new GetAllErrorsFunction(this.messageSource)); + functions.put(GetGlobalErrorsFunction.FUNCTION_NAME, + new GetGlobalErrorsFunction(this.messageSource)); + functions + .put(GetFieldErrorsFunction.FUNCTION_NAME, new GetFieldErrorsFunction(this.messageSource)); + functions.put(HrefFunction.FUNCTION_NAME, new HrefFunction()); + return functions; + } +} diff --git a/pebble-spring/pebble-spring6/src/main/java/com/mitchellbosecke/pebble/spring/extension/function/HrefFunction.java b/pebble-spring/pebble-spring6/src/main/java/com/mitchellbosecke/pebble/spring/extension/function/HrefFunction.java new file mode 100644 index 000000000..2219994c5 --- /dev/null +++ b/pebble-spring/pebble-spring6/src/main/java/com/mitchellbosecke/pebble/spring/extension/function/HrefFunction.java @@ -0,0 +1,81 @@ +package com.mitchellbosecke.pebble.spring.extension.function; + +import com.mitchellbosecke.pebble.extension.Function; +import com.mitchellbosecke.pebble.template.EvaluationContext; +import com.mitchellbosecke.pebble.template.PebbleTemplate; +import jakarta.servlet.http.HttpServletRequest; +import org.springframework.util.StringUtils; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +/** + * Pebble function which adds the context path to the given url + * + * @author Eric Bussieres + */ +public class HrefFunction implements Function { + + public static final String FUNCTION_NAME = "href"; + + protected static final String PARAM_URL = "url"; + + protected List argumentNames; + private String contextPath; + + /** + * Constructor + */ + public HrefFunction() { + this.argumentNames = new ArrayList<>(); + this.argumentNames.add(PARAM_URL); + } + + /** + * {@inheritDoc} + * + * @see Function#execute(Map, PebbleTemplate, EvaluationContext, int) + */ + @Override + public Object execute(Map args, PebbleTemplate self, EvaluationContext context, + int lineNumber) { + StringBuffer result = new StringBuffer(); + + result.append(this.getContextPath()); + this.addUrlParameter(args, result); + + return result.toString(); + } + + private void addUrlParameter(Map args, StringBuffer result) { + String url = (String) args.get(PARAM_URL); + if (StringUtils.hasText(url)) { + result.append(url); + } + } + + private String getContextPath() { + if (this.contextPath == null) { + this.contextPath = this.getRequest().getContextPath(); + } + return this.contextPath; + } + + private HttpServletRequest getRequest() { + ServletRequestAttributes attr = (ServletRequestAttributes) RequestContextHolder.currentRequestAttributes(); + return attr.getRequest(); + } + + /** + * {@inheritDoc} + * + * @see com.mitchellbosecke.pebble.extension.NamedArguments#getArgumentNames() + */ + @Override + public List getArgumentNames() { + return this.argumentNames; + } +} diff --git a/pebble-spring/pebble-spring6/src/main/java/com/mitchellbosecke/pebble/spring/extension/function/MessageSourceFunction.java b/pebble-spring/pebble-spring6/src/main/java/com/mitchellbosecke/pebble/spring/extension/function/MessageSourceFunction.java new file mode 100644 index 000000000..6ec6f5f73 --- /dev/null +++ b/pebble-spring/pebble-spring6/src/main/java/com/mitchellbosecke/pebble/spring/extension/function/MessageSourceFunction.java @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2013 by Mitchell Bösecke + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +package com.mitchellbosecke.pebble.spring.extension.function; + +import com.mitchellbosecke.pebble.extension.Function; +import com.mitchellbosecke.pebble.template.EvaluationContext; +import com.mitchellbosecke.pebble.template.PebbleTemplate; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import org.springframework.context.MessageSource; + +/** + *

+ * Function available to templates in Spring MVC applications in order to resolve message in the + * application context + *

+ * + * @author Eric Bussieres + */ +public class MessageSourceFunction implements Function { + + public static final String FUNCTION_NAME = "message"; + + private final MessageSource messageSource; + + public MessageSourceFunction(MessageSource messageSource) { + this.messageSource = messageSource; + } + + @Override + public Object execute(Map args, PebbleTemplate self, EvaluationContext context, + int lineNumber) { + String key = this.extractKey(args); + List arguments = this.extractArguments(args); + Locale locale = context.getLocale(); + + return this.messageSource.getMessage(key, arguments.toArray(), "???" + key + "???", locale); + } + + private String extractKey(Map args) { + return (String) args.get("0"); + } + + private List extractArguments(Map args) { + int i = 1; + List arguments = new ArrayList<>(); + while (args.containsKey(String.valueOf(i))) { + Object param = args.get(String.valueOf(i)); + arguments.add(param); + i++; + } + return arguments; + } + + @Override + public List getArgumentNames() { + return null; + } +} diff --git a/pebble-spring/pebble-spring6/src/main/java/com/mitchellbosecke/pebble/spring/extension/function/bindingresult/BaseBindingResultFunction.java b/pebble-spring/pebble-spring6/src/main/java/com/mitchellbosecke/pebble/spring/extension/function/bindingresult/BaseBindingResultFunction.java new file mode 100644 index 000000000..4a42f71d0 --- /dev/null +++ b/pebble-spring/pebble-spring6/src/main/java/com/mitchellbosecke/pebble/spring/extension/function/bindingresult/BaseBindingResultFunction.java @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2013 by Mitchell Bösecke + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +package com.mitchellbosecke.pebble.spring.extension.function.bindingresult; + +import com.mitchellbosecke.pebble.extension.Function; +import com.mitchellbosecke.pebble.template.EvaluationContext; +import com.mitchellbosecke.pebble.template.GlobalContext; + +import org.springframework.validation.BindingResult; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * Base class of the function interacting with the BindingResult + * + * @author Eric Bussieres + */ +public abstract class BaseBindingResultFunction implements Function { + + protected static final String PARAM_FIELD_NAME = "fieldName"; + protected static final String PARAM_FORM_NAME = "formName"; + + private final List argumentNames = new ArrayList<>(); + + protected BaseBindingResultFunction(String... argumentsName) { + Collections.addAll(this.argumentNames, argumentsName); + } + + @Override + public List getArgumentNames() { + return this.argumentNames; + } + + protected BindingResult getBindingResult(String formName, EvaluationContext context) { + String attribute = BindingResult.MODEL_KEY_PREFIX + formName; + BindingResult bindingResult = (BindingResult) context.getVariable(attribute); + if (bindingResult == null) { + GlobalContext globalContext = (GlobalContext) context.getVariable("_context"); + if (globalContext != null) { + return (BindingResult) globalContext.get(attribute); + } + } + return bindingResult; + } +} diff --git a/pebble-spring/pebble-spring6/src/main/java/com/mitchellbosecke/pebble/spring/extension/function/bindingresult/GetAllErrorsFunction.java b/pebble-spring/pebble-spring6/src/main/java/com/mitchellbosecke/pebble/spring/extension/function/bindingresult/GetAllErrorsFunction.java new file mode 100644 index 000000000..cb7d7881b --- /dev/null +++ b/pebble-spring/pebble-spring6/src/main/java/com/mitchellbosecke/pebble/spring/extension/function/bindingresult/GetAllErrorsFunction.java @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2013 by Mitchell Bösecke + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +package com.mitchellbosecke.pebble.spring.extension.function.bindingresult; + +import com.mitchellbosecke.pebble.template.EvaluationContext; +import com.mitchellbosecke.pebble.template.PebbleTemplate; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import org.springframework.context.MessageSource; +import org.springframework.validation.BindingResult; +import org.springframework.validation.ObjectError; + +/** + *

+ * Function available to templates in Spring MVC applications in order to access the BindingResult + * of a form + *

+ * + * @author Eric Bussieres + */ +public class GetAllErrorsFunction extends BaseBindingResultFunction { + + public static final String FUNCTION_NAME = "getAllErrors"; + + private final MessageSource messageSource; + + public GetAllErrorsFunction(MessageSource messageSource) { + super(PARAM_FORM_NAME); + this.messageSource = messageSource; + } + + @Override + public Object execute(Map args, PebbleTemplate self, EvaluationContext context, + int lineNumber) { + String formName = (String) args.get(PARAM_FORM_NAME); + + Locale locale = context.getLocale(); + BindingResult bindingResult = this.getBindingResult(formName, context); + + return this.constructErrorMessage(locale, bindingResult); + } + + private List constructErrorMessage(Locale locale, BindingResult bindingResult) { + List errorMessages = new ArrayList<>(); + if (bindingResult != null) { + for (ObjectError error : bindingResult.getAllErrors()) { + String msg = this.messageSource.getMessage(error, locale); + errorMessages.add(msg); + } + } + return errorMessages; + } +} diff --git a/pebble-spring/pebble-spring6/src/main/java/com/mitchellbosecke/pebble/spring/extension/function/bindingresult/GetFieldErrorsFunction.java b/pebble-spring/pebble-spring6/src/main/java/com/mitchellbosecke/pebble/spring/extension/function/bindingresult/GetFieldErrorsFunction.java new file mode 100644 index 000000000..e29e0291e --- /dev/null +++ b/pebble-spring/pebble-spring6/src/main/java/com/mitchellbosecke/pebble/spring/extension/function/bindingresult/GetFieldErrorsFunction.java @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2013 by Mitchell Bösecke + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +package com.mitchellbosecke.pebble.spring.extension.function.bindingresult; + +import com.mitchellbosecke.pebble.template.EvaluationContext; +import com.mitchellbosecke.pebble.template.PebbleTemplate; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import org.springframework.context.MessageSource; +import org.springframework.validation.BindingResult; +import org.springframework.validation.FieldError; + +/** + *

+ * Function available to templates in Spring MVC applications in order to access the BindingResult + * of a form + *

+ * + * @author Eric Bussieres + */ +public class GetFieldErrorsFunction extends BaseBindingResultFunction { + + public static final String FUNCTION_NAME = "getFieldErrors"; + + private final MessageSource messageSource; + + public GetFieldErrorsFunction(MessageSource messageSource) { + super(PARAM_FORM_NAME, PARAM_FIELD_NAME); + this.messageSource = messageSource; + } + + @Override + public Object execute(Map args, PebbleTemplate self, EvaluationContext context, + int lineNumber) { + String formName = (String) args.get(PARAM_FORM_NAME); + String field = (String) args.get(PARAM_FIELD_NAME); + + if (field == null) { + throw new IllegalArgumentException("Field parameter is required in GetFieldErrorsFunction"); + } + + Locale locale = context.getLocale(); + BindingResult bindingResult = this.getBindingResult(formName, context); + + return this.constructErrorMessages(field, locale, bindingResult); + } + + private List constructErrorMessages(String field, Locale locale, + BindingResult bindingResult) { + List errorMessages = new ArrayList<>(); + if (bindingResult != null) { + for (FieldError error : bindingResult.getFieldErrors(field)) { + String msg = this.messageSource.getMessage(error, locale); + errorMessages.add(msg); + } + } + return errorMessages; + } +} diff --git a/pebble-spring/pebble-spring6/src/main/java/com/mitchellbosecke/pebble/spring/extension/function/bindingresult/GetGlobalErrorsFunction.java b/pebble-spring/pebble-spring6/src/main/java/com/mitchellbosecke/pebble/spring/extension/function/bindingresult/GetGlobalErrorsFunction.java new file mode 100644 index 000000000..2baee6a73 --- /dev/null +++ b/pebble-spring/pebble-spring6/src/main/java/com/mitchellbosecke/pebble/spring/extension/function/bindingresult/GetGlobalErrorsFunction.java @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2013 by Mitchell Bösecke + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +package com.mitchellbosecke.pebble.spring.extension.function.bindingresult; + +import com.mitchellbosecke.pebble.template.EvaluationContext; +import com.mitchellbosecke.pebble.template.PebbleTemplate; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import org.springframework.context.MessageSource; +import org.springframework.validation.BindingResult; +import org.springframework.validation.ObjectError; + +/** + *

+ * Function available to templates in Spring MVC applications in order to access the BindingResult + * of a form + *

+ * + * @author Eric Bussieres + */ +public class GetGlobalErrorsFunction extends BaseBindingResultFunction { + + public static final String FUNCTION_NAME = "getGlobalErrors"; + + private final MessageSource messageSource; + + public GetGlobalErrorsFunction(MessageSource messageSource) { + super(PARAM_FORM_NAME); + this.messageSource = messageSource; + } + + @Override + public Object execute(Map args, PebbleTemplate self, EvaluationContext context, + int lineNumber) { + String formName = (String) args.get(PARAM_FORM_NAME); + + Locale locale = context.getLocale(); + BindingResult bindingResult = this.getBindingResult(formName, context); + + return this.constructErrorMessages(locale, bindingResult); + } + + private List constructErrorMessages(Locale locale, BindingResult bindingResult) { + List errorMessages = new ArrayList<>(); + if (bindingResult != null) { + for (ObjectError error : bindingResult.getGlobalErrors()) { + String msg = this.messageSource.getMessage(error, locale); + errorMessages.add(msg); + } + } + return errorMessages; + } +} diff --git a/pebble-spring/pebble-spring6/src/main/java/com/mitchellbosecke/pebble/spring/extension/function/bindingresult/HasErrorsFunction.java b/pebble-spring/pebble-spring6/src/main/java/com/mitchellbosecke/pebble/spring/extension/function/bindingresult/HasErrorsFunction.java new file mode 100644 index 000000000..9ed0016e3 --- /dev/null +++ b/pebble-spring/pebble-spring6/src/main/java/com/mitchellbosecke/pebble/spring/extension/function/bindingresult/HasErrorsFunction.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2013 by Mitchell Bösecke + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +package com.mitchellbosecke.pebble.spring.extension.function.bindingresult; + +import com.mitchellbosecke.pebble.template.EvaluationContext; +import com.mitchellbosecke.pebble.template.PebbleTemplate; +import java.util.Map; +import org.springframework.validation.BindingResult; + +/** + *

+ * Function available to templates in Spring MVC applications in order to access the BindingResult + * of a form + *

+ * + * @author Eric Bussieres + */ +public class HasErrorsFunction extends BaseBindingResultFunction { + + public static final String FUNCTION_NAME = "hasErrors"; + + public HasErrorsFunction() { + super(PARAM_FORM_NAME); + } + + @Override + public Object execute(Map args, PebbleTemplate self, EvaluationContext context, + int lineNumber) { + String formName = (String) args.get(PARAM_FORM_NAME); + + BindingResult bindingResult = this.getBindingResult(formName, context); + return bindingResult != null && bindingResult.hasErrors(); + } +} diff --git a/pebble-spring/pebble-spring6/src/main/java/com/mitchellbosecke/pebble/spring/extension/function/bindingresult/HasFieldErrorsFunction.java b/pebble-spring/pebble-spring6/src/main/java/com/mitchellbosecke/pebble/spring/extension/function/bindingresult/HasFieldErrorsFunction.java new file mode 100644 index 000000000..894960ea8 --- /dev/null +++ b/pebble-spring/pebble-spring6/src/main/java/com/mitchellbosecke/pebble/spring/extension/function/bindingresult/HasFieldErrorsFunction.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2013 by Mitchell Bösecke + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +package com.mitchellbosecke.pebble.spring.extension.function.bindingresult; + +import com.mitchellbosecke.pebble.template.EvaluationContext; +import com.mitchellbosecke.pebble.template.PebbleTemplate; +import java.util.Map; +import org.springframework.validation.BindingResult; + +/** + *

+ * Function available to templates in Spring MVC applications in order to access the BindingResult + * of a form + *

+ * + * @author Eric Bussieres + */ +public class HasFieldErrorsFunction extends BaseBindingResultFunction { + + public static final String FUNCTION_NAME = "hasFieldErrors"; + + public HasFieldErrorsFunction() { + super(PARAM_FORM_NAME, PARAM_FIELD_NAME); + } + + @Override + public Object execute(Map args, PebbleTemplate self, EvaluationContext context, + int lineNumber) { + String formName = (String) args.get(PARAM_FORM_NAME); + String fieldName = (String) args.get(PARAM_FIELD_NAME); + + BindingResult bindingResult = this.getBindingResult(formName, context); + + if (bindingResult != null) { + if (fieldName == null) { + return bindingResult.hasFieldErrors(); + } else { + return bindingResult.hasFieldErrors(fieldName); + } + } else { + return false; + } + } +} diff --git a/pebble-spring/pebble-spring6/src/main/java/com/mitchellbosecke/pebble/spring/extension/function/bindingresult/HasGlobalErrorsFunction.java b/pebble-spring/pebble-spring6/src/main/java/com/mitchellbosecke/pebble/spring/extension/function/bindingresult/HasGlobalErrorsFunction.java new file mode 100644 index 000000000..bc9253aa3 --- /dev/null +++ b/pebble-spring/pebble-spring6/src/main/java/com/mitchellbosecke/pebble/spring/extension/function/bindingresult/HasGlobalErrorsFunction.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2013 by Mitchell Bösecke + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +package com.mitchellbosecke.pebble.spring.extension.function.bindingresult; + +import com.mitchellbosecke.pebble.template.EvaluationContext; +import com.mitchellbosecke.pebble.template.PebbleTemplate; +import java.util.Map; +import org.springframework.validation.BindingResult; + +/** + *

+ * Function available to templates in Spring MVC applications in order to access the BindingResult + * of a form + *

+ * + * @author Eric Bussieres + */ +public class HasGlobalErrorsFunction extends BaseBindingResultFunction { + + public static final String FUNCTION_NAME = "hasGlobalErrors"; + + public HasGlobalErrorsFunction() { + super(PARAM_FORM_NAME); + } + + @Override + public Object execute(Map args, PebbleTemplate self, EvaluationContext context, + int lineNumber) { + String formName = (String) args.get(PARAM_FORM_NAME); + + BindingResult bindingResult = this.getBindingResult(formName, context); + return bindingResult != null && bindingResult.hasGlobalErrors(); + } +} diff --git a/pebble-spring/pebble-spring6/src/main/java/com/mitchellbosecke/pebble/spring/reactive/PebbleReactiveView.java b/pebble-spring/pebble-spring6/src/main/java/com/mitchellbosecke/pebble/spring/reactive/PebbleReactiveView.java new file mode 100644 index 000000000..48c2c51d8 --- /dev/null +++ b/pebble-spring/pebble-spring6/src/main/java/com/mitchellbosecke/pebble/spring/reactive/PebbleReactiveView.java @@ -0,0 +1,101 @@ +package com.mitchellbosecke.pebble.spring.reactive; + +import static java.util.Optional.ofNullable; + +import com.mitchellbosecke.pebble.PebbleEngine; +import com.mitchellbosecke.pebble.error.PebbleException; +import com.mitchellbosecke.pebble.spring.context.Beans; +import com.mitchellbosecke.pebble.template.PebbleTemplate; +import java.io.IOException; +import java.io.OutputStreamWriter; +import java.io.Writer; +import java.nio.charset.Charset; +import java.util.Locale; +import java.util.Map; +import org.springframework.context.i18n.LocaleContextHolder; +import org.springframework.core.io.buffer.DataBuffer; +import org.springframework.core.io.buffer.DataBufferUtils; +import org.springframework.http.MediaType; +import org.springframework.lang.Nullable; +import org.springframework.util.MimeType; +import org.springframework.web.reactive.result.view.AbstractUrlBasedView; +import org.springframework.web.server.ServerWebExchange; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +public class PebbleReactiveView extends AbstractUrlBasedView { + + private static final String BEANS_VARIABLE_NAME = "beans"; + private static final String REQUEST_VARIABLE_NAME = "request"; + private static final String RESPONSE_VARIABLE_NAME = "response"; + private static final String SESSION_VARIABLE_NAME = "session"; + + private PebbleEngine pebbleEngine; + private String templateName; + + @Override + public boolean checkResourceExists(Locale locale) { + return this.pebbleEngine.getLoader().resourceExists(this.templateName); + } + + @Override + protected Mono renderInternal(Map renderAttributes, + MediaType contentType, + ServerWebExchange exchange) { + DataBuffer dataBuffer = exchange.getResponse().bufferFactory().allocateBuffer(); + if (this.logger.isDebugEnabled()) { + this.logger.debug(exchange.getLogPrefix() + "Rendering [" + this.getUrl() + "]"); + } + + Locale locale = LocaleContextHolder.getLocale(exchange.getLocaleContext()); + try { + Charset charset = this.getCharset(contentType); + Writer writer = new OutputStreamWriter(dataBuffer.asOutputStream(), charset); + this.addVariablesToModel(renderAttributes, exchange); + this.evaluateTemplate(renderAttributes, locale, writer); + } catch (Exception ex) { + DataBufferUtils.release(dataBuffer); + return Mono.error(ex); + } + return exchange.getResponse().writeWith(Flux.just(dataBuffer)); + } + + private void addVariablesToModel(Map model, ServerWebExchange exchange) { + model.put(BEANS_VARIABLE_NAME, new Beans(this.getApplicationContext())); + model.put(REQUEST_VARIABLE_NAME, exchange.getRequest()); + model.put(RESPONSE_VARIABLE_NAME, exchange.getResponse()); + model.put(SESSION_VARIABLE_NAME, exchange.getSession()); + } + + private Charset getCharset(@Nullable MediaType mediaType) { + return ofNullable(mediaType) + .map(MimeType::getCharset) + .orElse(this.getDefaultCharset()); + } + + private void evaluateTemplate(Map model, Locale locale, Writer writer) + throws IOException, PebbleException { + try { + PebbleTemplate template = this.pebbleEngine.getTemplate(this.templateName); + template.evaluate(writer, model, locale); + } finally { + writer.flush(); + } + } + + public PebbleEngine getPebbleEngine() { + return this.pebbleEngine; + } + + public void setPebbleEngine(PebbleEngine pebbleEngine) { + this.pebbleEngine = pebbleEngine; + } + + public String getTemplateName() { + return this.templateName; + } + + public void setTemplateName(String templateName) { + this.templateName = templateName; + } +} diff --git a/pebble-spring/pebble-spring6/src/main/java/com/mitchellbosecke/pebble/spring/reactive/PebbleReactiveViewResolver.java b/pebble-spring/pebble-spring6/src/main/java/com/mitchellbosecke/pebble/spring/reactive/PebbleReactiveViewResolver.java new file mode 100644 index 000000000..3f4a4734d --- /dev/null +++ b/pebble-spring/pebble-spring6/src/main/java/com/mitchellbosecke/pebble/spring/reactive/PebbleReactiveViewResolver.java @@ -0,0 +1,29 @@ +package com.mitchellbosecke.pebble.spring.reactive; + +import com.mitchellbosecke.pebble.PebbleEngine; +import org.springframework.web.reactive.result.view.AbstractUrlBasedView; +import org.springframework.web.reactive.result.view.UrlBasedViewResolver; + +public class PebbleReactiveViewResolver extends UrlBasedViewResolver { + + private final PebbleEngine pebbleEngine; + + public PebbleReactiveViewResolver(PebbleEngine pebbleEngine) { + this.setViewClass(this.requiredViewClass()); + this.pebbleEngine = pebbleEngine; + } + + @Override + protected AbstractUrlBasedView createView(String viewName) { + PebbleReactiveView view = (PebbleReactiveView) super.createView(viewName); + view.setPebbleEngine(this.pebbleEngine); + view.setTemplateName(viewName); + + return view; + } + + @Override + protected Class requiredViewClass() { + return PebbleReactiveView.class; + } +} diff --git a/pebble-spring/pebble-spring6/src/main/java/com/mitchellbosecke/pebble/spring/servlet/PebbleView.java b/pebble-spring/pebble-spring6/src/main/java/com/mitchellbosecke/pebble/spring/servlet/PebbleView.java new file mode 100644 index 000000000..80404b4bc --- /dev/null +++ b/pebble-spring/pebble-spring6/src/main/java/com/mitchellbosecke/pebble/spring/servlet/PebbleView.java @@ -0,0 +1,114 @@ +/* + * Copyright (c) 2013 by Mitchell Bösecke + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +package com.mitchellbosecke.pebble.spring.servlet; + +import com.mitchellbosecke.pebble.PebbleEngine; +import com.mitchellbosecke.pebble.error.PebbleException; +import com.mitchellbosecke.pebble.spring.context.Beans; +import com.mitchellbosecke.pebble.template.PebbleTemplate; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.web.servlet.support.RequestContextUtils; +import org.springframework.web.servlet.view.AbstractTemplateView; + +import java.io.IOException; +import java.io.Writer; +import java.util.Locale; +import java.util.Map; + +public class PebbleView extends AbstractTemplateView { + + public static final String REQUEST_VARIABLE_NAME = "request"; + public static final String RESPONSE_VARIABLE_NAME = "response"; + public static final String SESSION_VARIABLE_NAME = "session"; + + private static final String BEANS_VARIABLE_NAME = "beans"; + private static final int NANO_PER_SECOND = 1000000; + /** + *

+ * TIMER logger. This logger will output the time required for executing each template processing + * operation. + *

+ *

+ * The value of this constant is + * com.mitchellbosecke.pebble.spring.servlet.PebbleView.timer. This allows + * you to set a specific configuration and/or appenders for timing info at your logging system + * configuration. + *

+ */ + private static final Logger TIMER_LOGGER = LoggerFactory + .getLogger(PebbleView.class.getName() + ".timer"); + + private String characterEncoding = "UTF-8"; + private PebbleEngine pebbleEngine; + private String templateName; + + @Override + protected void renderMergedTemplateModel(Map model, HttpServletRequest request, + HttpServletResponse response) throws Exception { + long startNanoTime = System.nanoTime(); + + this.setCharacterEncoding(response); + this.addVariablesToModel(model, request, response); + this.evaluateTemplate(model, request, response); + this.logElapsedTime(startNanoTime, request); + } + + private void setCharacterEncoding(HttpServletResponse response) { + if (this.characterEncoding != null) { + response.setCharacterEncoding(this.characterEncoding); + } + } + + private void addVariablesToModel(Map model, HttpServletRequest request, + HttpServletResponse response) { + model.put(BEANS_VARIABLE_NAME, new Beans(this.getApplicationContext())); + model.put(REQUEST_VARIABLE_NAME, request); + model.put(RESPONSE_VARIABLE_NAME, response); + model.put(SESSION_VARIABLE_NAME, request.getSession(false)); + } + + private void evaluateTemplate(Map model, HttpServletRequest request, + HttpServletResponse response) throws IOException, PebbleException { + Locale locale = RequestContextUtils.getLocale(request); + + Writer writer = response.getWriter(); + try { + PebbleTemplate template = this.pebbleEngine.getTemplate(this.templateName); + template.evaluate(writer, model, locale); + } finally { + writer.flush(); + } + } + + private void logElapsedTime(long startNanoTime, HttpServletRequest request) { + if (TIMER_LOGGER.isDebugEnabled()) { + Locale locale = RequestContextUtils.getLocale(request); + long endNanoTime = System.nanoTime(); + + long elapsed = endNanoTime - startNanoTime; + long elapsedMs = elapsed / NANO_PER_SECOND; + TIMER_LOGGER + .debug("Pebble template \"{}\" with locale {} processed in {} nanoseconds (approx. {}ms)", + this.templateName, locale, elapsed, elapsedMs); + } + } + + public void setCharacterEncoding(String characterEncoding) { + this.characterEncoding = characterEncoding; + } + + public void setPebbleEngine(PebbleEngine pebbleEngine) { + this.pebbleEngine = pebbleEngine; + } + + public void setTemplateName(String name) { + this.templateName = name; + } +} \ No newline at end of file diff --git a/pebble-spring/pebble-spring6/src/main/java/com/mitchellbosecke/pebble/spring/servlet/PebbleViewResolver.java b/pebble-spring/pebble-spring6/src/main/java/com/mitchellbosecke/pebble/spring/servlet/PebbleViewResolver.java new file mode 100644 index 000000000..01271123d --- /dev/null +++ b/pebble-spring/pebble-spring6/src/main/java/com/mitchellbosecke/pebble/spring/servlet/PebbleViewResolver.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2013 by Mitchell Bösecke + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +package com.mitchellbosecke.pebble.spring.servlet; + +import com.mitchellbosecke.pebble.PebbleEngine; +import com.mitchellbosecke.pebble.loader.Loader; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.web.servlet.view.AbstractTemplateViewResolver; +import org.springframework.web.servlet.view.AbstractUrlBasedView; + +public class PebbleViewResolver extends AbstractTemplateViewResolver implements InitializingBean { + + private String characterEncoding = "UTF-8"; + private final PebbleEngine pebbleEngine; + + public PebbleViewResolver(PebbleEngine pebbleEngine) { + this.pebbleEngine = pebbleEngine; + this.setViewClass(this.requiredViewClass()); + } + + @Override + public void afterPropertiesSet() { + Loader templateLoader = this.pebbleEngine.getLoader(); + templateLoader.setPrefix(this.getPrefix()); + templateLoader.setSuffix(this.getSuffix()); + } + + public void setCharacterEncoding(String characterEncoding) { + this.characterEncoding = characterEncoding; + } + + @Override + protected AbstractUrlBasedView buildView(String viewName) throws Exception { + PebbleView view = (PebbleView) super.buildView(viewName); + view.setTemplateName(viewName); + view.setPebbleEngine(this.pebbleEngine); + view.setCharacterEncoding(this.characterEncoding); + + return view; + } + + @Override + protected Class requiredViewClass() { + return PebbleView.class; + } +} diff --git a/pebble-spring/pebble-spring6/src/test/java/com/mitchellbosecke/pebble/spring/PebbleViewResolverTest.java b/pebble-spring/pebble-spring6/src/test/java/com/mitchellbosecke/pebble/spring/PebbleViewResolverTest.java new file mode 100644 index 000000000..e9602e64a --- /dev/null +++ b/pebble-spring/pebble-spring6/src/test/java/com/mitchellbosecke/pebble/spring/PebbleViewResolverTest.java @@ -0,0 +1,211 @@ +/* + * Copyright (c) 2013 by Mitchell Bösecke + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +package com.mitchellbosecke.pebble.spring; + +import com.mitchellbosecke.pebble.spring.config.MVCConfig; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit.jupiter.SpringExtension; +import org.springframework.test.context.web.WebAppConfiguration; +import org.springframework.validation.BindingResult; +import org.springframework.validation.FieldError; +import org.springframework.validation.ObjectError; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; +import org.springframework.web.servlet.ViewResolver; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +/** + * Unit test for the PebbleViewResolver + * + * @author Eric Bussieres + */ +@ExtendWith(SpringExtension.class) +@WebAppConfiguration +@ContextConfiguration(classes = MVCConfig.class) +class PebbleViewResolverTest { + + private static final String CONTEXT_PATH = "/testContextPath"; + private static final Locale DEFAULT_LOCALE = Locale.CANADA; + private static final String EXPECTED_RESPONSE_PATH = "/com/mitchellbosecke/pebble/spring/expectedResponse"; + private static final String FORM_NAME = "formName"; + + private BindingResult mockBindingResult = mock(BindingResult.class); + private MockHttpServletRequest mockRequest = new MockHttpServletRequest(); + private MockHttpServletResponse mockResponse = new MockHttpServletResponse(); + + @Autowired + private ViewResolver viewResolver; + + @BeforeEach + void initRequest() { + this.mockRequest.setContextPath(CONTEXT_PATH); + this.mockRequest.getSession().setMaxInactiveInterval(600); + + RequestContextHolder.setRequestAttributes(new ServletRequestAttributes(this.mockRequest)); + } + + @BeforeEach + void initBindingResult() { + this.initBindingResultAllErrors(); + this.initBindingResultGlobalErrors(); + this.initBindingResultFieldErrors(); + } + + private void initBindingResultAllErrors() { + when(this.mockBindingResult.hasErrors()).thenReturn(true); + + List allErrors = new ArrayList<>(); + allErrors.add( + new ObjectError(FORM_NAME, new String[]{"error.test"}, new String[]{}, "???error.test???")); + when(this.mockBindingResult.getAllErrors()).thenReturn(allErrors); + } + + private void initBindingResultGlobalErrors() { + when(this.mockBindingResult.hasGlobalErrors()).thenReturn(true); + + List globalErrors = new ArrayList<>(); + globalErrors.add(new ObjectError(FORM_NAME, new String[]{"error.global.test.params"}, + new String[]{"param1", "param2"}, "???error.global.test.params???")); + when(this.mockBindingResult.getGlobalErrors()).thenReturn(globalErrors); + } + + private void initBindingResultFieldErrors() { + when(this.mockBindingResult.hasFieldErrors("testField")).thenReturn(true); + + List fieldErrors = new ArrayList<>(); + fieldErrors.add( + new FieldError(FORM_NAME, "testField", null, false, new String[]{"error.field.test.params"}, + new String[]{"param1", "param2"}, "???error.field.test.params???")); + when(this.mockBindingResult.getFieldErrors("testField")).thenReturn(fieldErrors); + } + + @Test + void whenRenderingAPage_givenPageWithBeanVariable_thenRenderingIsOK() throws Exception { + String result = this.render("beansTest", new HashMap()); + + this.assertOutput(result, EXPECTED_RESPONSE_PATH + "/beansTest.html"); + } + + @Test + void whenRenderingAPage_givenPageWithBindingResult_thenRenderingIsOK() throws Exception { + Map model = this.givenBindingResult(); + + String result = this.render("bindingResultTest", model); + + this.assertOutput(result, EXPECTED_RESPONSE_PATH + "/bindingResultTest.html"); + } + + private Map givenBindingResult() { + Map model = new HashMap<>(); + model.put(BindingResult.MODEL_KEY_PREFIX + FORM_NAME, this.mockBindingResult); + return model; + } + + @Test + void whenRenderingAPage_givenPageWithBindingResultAndMacro_thenRenderingIsOK() throws Exception { + Map model = this.givenBindingResult(); + + String result = this.render("bindingResultWithMacroTest", model); + + this.assertOutput(result, EXPECTED_RESPONSE_PATH + "/bindingResultWithMacroTest.html"); + } + + @Test + void whenRenderingAPage_givenPageWithHrefFunction_thenRenderingIsOK() throws Exception { + String result = this.render("hrefFunctionTest", new HashMap()); + + this.assertOutput(result, EXPECTED_RESPONSE_PATH + "/hrefFunctionTest.html"); + } + + @Test + void whenRenderingAPageInEnglish_givenPageWithResourceBundleMessage_thenRenderingIsOK() + throws Exception { + String result = this.render("messageEnTest", new HashMap()); + + this.assertOutput(result, EXPECTED_RESPONSE_PATH + "/messageEnTest.html"); + } + + @Test + void whenRenderingAPageInFrench_givenPageWithResourceBundleMessage_thenRenderingIsOK() + throws Exception { + this.mockRequest.addPreferredLocale(Locale.CANADA_FRENCH); + + String result = this.render("messageFrTest", new HashMap()); + + this.assertOutput(result, EXPECTED_RESPONSE_PATH + "/messageFrTest.html"); + } + + @Test + void whenRenderingAPage_givenPageWithHttpRequestVariable_thenRenderingIsOK() + throws Exception { + String result = this.render("requestTest", new HashMap()); + + this.assertOutput(result, EXPECTED_RESPONSE_PATH + "/requestTest.html"); + } + + @Test + void whenRenderingAPage_givenPageWithHttpResponseVariable_thenRenderingIsOK() + throws Exception { + String result = this.render("responseTest", new HashMap()); + + this.assertOutput(result, EXPECTED_RESPONSE_PATH + "/responseTest.html"); + } + + @Test + void whenRenderingAPage_givenPageWithHttpSessionVariable_thenRenderingIsOK() + throws Exception { + String result = this.render("sessionTest", new HashMap()); + + this.assertOutput(result, EXPECTED_RESPONSE_PATH + "/sessionTest.html"); + } + + private void assertOutput(String output, String expectedOutput) throws IOException { + assertEquals(this.readExpectedOutputResource(expectedOutput), output.replaceAll("\\s", "")); + } + + private String readExpectedOutputResource(String expectedOutput) throws IOException { + BufferedReader reader = new BufferedReader( + new InputStreamReader(this.getClass().getResourceAsStream(expectedOutput))); + + StringBuilder builder = new StringBuilder(); + for (String currentLine = reader.readLine(); currentLine != null; + currentLine = reader.readLine()) { + builder.append(currentLine); + } + + return this.removeAllWhitespaces(builder.toString()); + } + + private String removeAllWhitespaces(String source) { + return source.replaceAll("\\s", ""); + } + + private String render(String location, Map model) throws Exception { + this.viewResolver.resolveViewName(location, DEFAULT_LOCALE) + .render(model, this.mockRequest, this.mockResponse); + return this.mockResponse.getContentAsString(); + } +} diff --git a/pebble-spring/pebble-spring6/src/test/java/com/mitchellbosecke/pebble/spring/bean/SomeBean.java b/pebble-spring/pebble-spring6/src/test/java/com/mitchellbosecke/pebble/spring/bean/SomeBean.java new file mode 100644 index 000000000..21bce7b34 --- /dev/null +++ b/pebble-spring/pebble-spring6/src/test/java/com/mitchellbosecke/pebble/spring/bean/SomeBean.java @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2013 by Mitchell Bösecke + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +package com.mitchellbosecke.pebble.spring.bean; + +/** + * Spring bean for unit test + * + * @author Eric Bussieres + */ +public class SomeBean { + + public String foo() { + return "foo"; + } +} diff --git a/pebble-spring/pebble-spring6/src/test/java/com/mitchellbosecke/pebble/spring/config/MVCConfig.java b/pebble-spring/pebble-spring6/src/test/java/com/mitchellbosecke/pebble/spring/config/MVCConfig.java new file mode 100644 index 000000000..10122412e --- /dev/null +++ b/pebble-spring/pebble-spring6/src/test/java/com/mitchellbosecke/pebble/spring/config/MVCConfig.java @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2013 by Mitchell Bösecke + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +package com.mitchellbosecke.pebble.spring.config; + +import com.mitchellbosecke.pebble.PebbleEngine; +import com.mitchellbosecke.pebble.loader.ClasspathLoader; +import com.mitchellbosecke.pebble.loader.Loader; +import com.mitchellbosecke.pebble.spring.bean.SomeBean; +import com.mitchellbosecke.pebble.spring.extension.SpringExtension; +import com.mitchellbosecke.pebble.spring.servlet.PebbleViewResolver; +import org.springframework.context.MessageSource; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.support.ResourceBundleMessageSource; +import org.springframework.web.servlet.ViewResolver; + +/** + * Spring configuration for unit test + * + * @author Eric Bussieres + */ +@Configuration(proxyBeanMethods = false) +public class MVCConfig { + + @Bean + public SomeBean foo() { + return new SomeBean(); + } + + @Bean + public MessageSource messageSource() { + ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource(); + messageSource.setBasename("com.mitchellbosecke.pebble.spring.messages"); + + return messageSource; + } + + @Bean + public PebbleEngine pebbleEngine(SpringExtension springExtension, + Loader templateLoader) { + return new PebbleEngine.Builder() + .loader(templateLoader) + .strictVariables(false) + .extension(springExtension) + .build(); + } + + @Bean + public SpringExtension springExtension(MessageSource messageSource) { + return new SpringExtension(messageSource); + } + + @Bean + public Loader templateLoader() { + return new ClasspathLoader(); + } + + @Bean + public ViewResolver viewResolver(PebbleEngine pebbleEngine) { + PebbleViewResolver viewResolver = new PebbleViewResolver(pebbleEngine); + viewResolver.setPrefix("com/mitchellbosecke/pebble/spring/template/"); + viewResolver.setSuffix(".html"); + viewResolver.setContentType("text/html"); + return viewResolver; + } +} diff --git a/pebble-spring/pebble-spring6/src/test/resources/com/mitchellbosecke/pebble/spring/expectedResponse/beansTest.html b/pebble-spring/pebble-spring6/src/test/resources/com/mitchellbosecke/pebble/spring/expectedResponse/beansTest.html new file mode 100644 index 000000000..d4e7ab655 --- /dev/null +++ b/pebble-spring/pebble-spring6/src/test/resources/com/mitchellbosecke/pebble/spring/expectedResponse/beansTest.html @@ -0,0 +1,10 @@ + + + + + foo + + + + + \ No newline at end of file diff --git a/pebble-spring/pebble-spring6/src/test/resources/com/mitchellbosecke/pebble/spring/expectedResponse/bindingResultTest.html b/pebble-spring/pebble-spring6/src/test/resources/com/mitchellbosecke/pebble/spring/expectedResponse/bindingResultTest.html new file mode 100644 index 000000000..d48671b3e --- /dev/null +++ b/pebble-spring/pebble-spring6/src/test/resources/com/mitchellbosecke/pebble/spring/expectedResponse/bindingResultTest.html @@ -0,0 +1,21 @@ + + + + + test + + + +true +true +true + +false +false +false + +Some error +Some global error with params param1 and param2 +Some field error with params param1 and param2 + + \ No newline at end of file diff --git a/pebble-spring/pebble-spring6/src/test/resources/com/mitchellbosecke/pebble/spring/expectedResponse/bindingResultWithMacroTest.html b/pebble-spring/pebble-spring6/src/test/resources/com/mitchellbosecke/pebble/spring/expectedResponse/bindingResultWithMacroTest.html new file mode 100644 index 000000000..d48671b3e --- /dev/null +++ b/pebble-spring/pebble-spring6/src/test/resources/com/mitchellbosecke/pebble/spring/expectedResponse/bindingResultWithMacroTest.html @@ -0,0 +1,21 @@ + + + + + test + + + +true +true +true + +false +false +false + +Some error +Some global error with params param1 and param2 +Some field error with params param1 and param2 + + \ No newline at end of file diff --git a/pebble-spring/pebble-spring6/src/test/resources/com/mitchellbosecke/pebble/spring/expectedResponse/hrefFunctionTest.html b/pebble-spring/pebble-spring6/src/test/resources/com/mitchellbosecke/pebble/spring/expectedResponse/hrefFunctionTest.html new file mode 100644 index 000000000..02d2040e0 --- /dev/null +++ b/pebble-spring/pebble-spring6/src/test/resources/com/mitchellbosecke/pebble/spring/expectedResponse/hrefFunctionTest.html @@ -0,0 +1,13 @@ + + + + + test + + + +HrefFunction string interpolation = /testContextPath/foo +HrefFunction static = /testContextPath/foobar +HrefFunction expression = /testContextPath/foo + + \ No newline at end of file diff --git a/pebble-spring/pebble-spring6/src/test/resources/com/mitchellbosecke/pebble/spring/expectedResponse/messageEnTest.html b/pebble-spring/pebble-spring6/src/test/resources/com/mitchellbosecke/pebble/spring/expectedResponse/messageEnTest.html new file mode 100644 index 000000000..8e8c535a8 --- /dev/null +++ b/pebble-spring/pebble-spring6/src/test/resources/com/mitchellbosecke/pebble/spring/expectedResponse/messageEnTest.html @@ -0,0 +1,12 @@ + + + + + test + + + +Label = Some label +Label with params = Some label with params params1 and params2 + + \ No newline at end of file diff --git a/pebble-spring/pebble-spring6/src/test/resources/com/mitchellbosecke/pebble/spring/expectedResponse/messageFrTest.html b/pebble-spring/pebble-spring6/src/test/resources/com/mitchellbosecke/pebble/spring/expectedResponse/messageFrTest.html new file mode 100644 index 000000000..c94d10eea --- /dev/null +++ b/pebble-spring/pebble-spring6/src/test/resources/com/mitchellbosecke/pebble/spring/expectedResponse/messageFrTest.html @@ -0,0 +1,12 @@ + + + + + test + + + +Label = Un libellé +Label with params = Un libellé avec les paramètres params1 et params2 + + \ No newline at end of file diff --git a/pebble-spring/pebble-spring6/src/test/resources/com/mitchellbosecke/pebble/spring/expectedResponse/requestTest.html b/pebble-spring/pebble-spring6/src/test/resources/com/mitchellbosecke/pebble/spring/expectedResponse/requestTest.html new file mode 100644 index 000000000..e5db6c754 --- /dev/null +++ b/pebble-spring/pebble-spring6/src/test/resources/com/mitchellbosecke/pebble/spring/expectedResponse/requestTest.html @@ -0,0 +1,11 @@ + + + + + test + + + +Context path = /testContextPath + + \ No newline at end of file diff --git a/pebble-spring/pebble-spring6/src/test/resources/com/mitchellbosecke/pebble/spring/expectedResponse/responseTest.html b/pebble-spring/pebble-spring6/src/test/resources/com/mitchellbosecke/pebble/spring/expectedResponse/responseTest.html new file mode 100644 index 000000000..377e2fc16 --- /dev/null +++ b/pebble-spring/pebble-spring6/src/test/resources/com/mitchellbosecke/pebble/spring/expectedResponse/responseTest.html @@ -0,0 +1,12 @@ + + + + + test + + + +Response contentType = text/html;charset=UTF-8 +Response character encoding = UTF-8 + + \ No newline at end of file diff --git a/pebble-spring/pebble-spring6/src/test/resources/com/mitchellbosecke/pebble/spring/expectedResponse/sessionTest.html b/pebble-spring/pebble-spring6/src/test/resources/com/mitchellbosecke/pebble/spring/expectedResponse/sessionTest.html new file mode 100644 index 000000000..71313d1a1 --- /dev/null +++ b/pebble-spring/pebble-spring6/src/test/resources/com/mitchellbosecke/pebble/spring/expectedResponse/sessionTest.html @@ -0,0 +1,11 @@ + + + + + test + + + +Session maxInactiveInterval = 600 + + \ No newline at end of file diff --git a/pebble-spring/pebble-spring6/src/test/resources/com/mitchellbosecke/pebble/spring/messages_en.properties b/pebble-spring/pebble-spring6/src/test/resources/com/mitchellbosecke/pebble/spring/messages_en.properties new file mode 100644 index 000000000..83bd8f773 --- /dev/null +++ b/pebble-spring/pebble-spring6/src/test/resources/com/mitchellbosecke/pebble/spring/messages_en.properties @@ -0,0 +1,5 @@ +label.test=Some label +label.test.params=Some label with params {0} and {1} +error.test=Some error +error.global.test.params=Some global error with params {0} and {1} +error.field.test.params=Some field error with params {0} and {1} \ No newline at end of file diff --git a/pebble-spring/pebble-spring6/src/test/resources/com/mitchellbosecke/pebble/spring/messages_fr.properties b/pebble-spring/pebble-spring6/src/test/resources/com/mitchellbosecke/pebble/spring/messages_fr.properties new file mode 100644 index 000000000..24c990fd7 --- /dev/null +++ b/pebble-spring/pebble-spring6/src/test/resources/com/mitchellbosecke/pebble/spring/messages_fr.properties @@ -0,0 +1,5 @@ +label.test=Un libell� +label.test.params=Un libell� avec les param�tres {0} et {1} +error.test=Une erreur +error.global.test.params=Une erreur globale avec les param�tres {0} et {1} +error.field.test.params=Une erreur sur un champ avec les param�tres {0} et {1} \ No newline at end of file diff --git a/pebble-spring/pebble-spring6/src/test/resources/com/mitchellbosecke/pebble/spring/template/beansTest.html b/pebble-spring/pebble-spring6/src/test/resources/com/mitchellbosecke/pebble/spring/template/beansTest.html new file mode 100644 index 000000000..acc8a913b --- /dev/null +++ b/pebble-spring/pebble-spring6/src/test/resources/com/mitchellbosecke/pebble/spring/template/beansTest.html @@ -0,0 +1,10 @@ + + + + + {{ beans.foo.foo() }} + + + + + \ No newline at end of file diff --git a/pebble-spring/pebble-spring6/src/test/resources/com/mitchellbosecke/pebble/spring/template/bindingResultTest.html b/pebble-spring/pebble-spring6/src/test/resources/com/mitchellbosecke/pebble/spring/template/bindingResultTest.html new file mode 100644 index 000000000..3df8bdf4c --- /dev/null +++ b/pebble-spring/pebble-spring6/src/test/resources/com/mitchellbosecke/pebble/spring/template/bindingResultTest.html @@ -0,0 +1,26 @@ + + + + + test + + + +{{ hasErrors('formName') }} +{{ hasGlobalErrors('formName') }} +{{ hasFieldErrors('formName', 'testField') }} + +{{ hasErrors('') }} +{{ hasGlobalErrors('') }} +{{ hasFieldErrors('formName', '') }} + +{% for err in getAllErrors('formName') %}{{ err }}{% endfor %} +{% for err in getGlobalErrors('formName') %}{{ err }}{% endfor %} +{% for err in getFieldErrors('formName', 'testField') %}{{ err }}{% endfor %} + +{% for err in getAllErrors('') %}{{ err }}{% endfor %} +{% for err in getGlobalErrors('') %}{{ err }}{% endfor %} +{% for err in getFieldErrors('', 'testField') %}{{ err }}{% endfor %} +{% for err in getFieldErrors('formName', '') %}{{ err }}{% endfor %} + + \ No newline at end of file diff --git a/pebble-spring/pebble-spring6/src/test/resources/com/mitchellbosecke/pebble/spring/template/bindingResultWithMacroTest.html b/pebble-spring/pebble-spring6/src/test/resources/com/mitchellbosecke/pebble/spring/template/bindingResultWithMacroTest.html new file mode 100644 index 000000000..d60aa6c1d --- /dev/null +++ b/pebble-spring/pebble-spring6/src/test/resources/com/mitchellbosecke/pebble/spring/template/bindingResultWithMacroTest.html @@ -0,0 +1,29 @@ + + + + + test + + + +{{ test(_context) }} +{% macro test(_context) %} + {{ hasErrors('formName') }} + {{ hasGlobalErrors('formName') }} + {{ hasFieldErrors('formName', 'testField') }} + + {{ hasErrors('') }} + {{ hasGlobalErrors('') }} + {{ hasFieldErrors('formName', '') }} + + {% for err in getAllErrors('formName') %}{{ err }}{% endfor %} + {% for err in getGlobalErrors('formName') %}{{ err }}{% endfor %} + {% for err in getFieldErrors('formName', 'testField') %}{{ err }}{% endfor %} + + {% for err in getAllErrors('') %}{{ err }}{% endfor %} + {% for err in getGlobalErrors('') %}{{ err }}{% endfor %} + {% for err in getFieldErrors('', 'testField') %}{{ err }}{% endfor %} + {% for err in getFieldErrors('formName', '') %}{{ err }}{% endfor %} +{% endmacro %} + + \ No newline at end of file diff --git a/pebble-spring/pebble-spring6/src/test/resources/com/mitchellbosecke/pebble/spring/template/hrefFunctionTest.html b/pebble-spring/pebble-spring6/src/test/resources/com/mitchellbosecke/pebble/spring/template/hrefFunctionTest.html new file mode 100644 index 000000000..9c6781027 --- /dev/null +++ b/pebble-spring/pebble-spring6/src/test/resources/com/mitchellbosecke/pebble/spring/template/hrefFunctionTest.html @@ -0,0 +1,13 @@ + + + + + test + + + +HrefFunction string interpolation = {{ href("/#{beans.foo.foo}") }} +HrefFunction static = {{ href('/foobar') }} +HrefFunction expression = {{ href('/' + beans.foo.foo) }} + + \ No newline at end of file diff --git a/pebble-spring/pebble-spring6/src/test/resources/com/mitchellbosecke/pebble/spring/template/messageEnTest.html b/pebble-spring/pebble-spring6/src/test/resources/com/mitchellbosecke/pebble/spring/template/messageEnTest.html new file mode 100644 index 000000000..db8f0ff96 --- /dev/null +++ b/pebble-spring/pebble-spring6/src/test/resources/com/mitchellbosecke/pebble/spring/template/messageEnTest.html @@ -0,0 +1,12 @@ + + + + + test + + + +Label = {{ message('label.test') }} +Label with params = {{ message('label.test.params', 'params1', 'params2') }} + + \ No newline at end of file diff --git a/pebble-spring/pebble-spring6/src/test/resources/com/mitchellbosecke/pebble/spring/template/messageFrTest.html b/pebble-spring/pebble-spring6/src/test/resources/com/mitchellbosecke/pebble/spring/template/messageFrTest.html new file mode 100644 index 000000000..db8f0ff96 --- /dev/null +++ b/pebble-spring/pebble-spring6/src/test/resources/com/mitchellbosecke/pebble/spring/template/messageFrTest.html @@ -0,0 +1,12 @@ + + + + + test + + + +Label = {{ message('label.test') }} +Label with params = {{ message('label.test.params', 'params1', 'params2') }} + + \ No newline at end of file diff --git a/pebble-spring/pebble-spring6/src/test/resources/com/mitchellbosecke/pebble/spring/template/requestTest.html b/pebble-spring/pebble-spring6/src/test/resources/com/mitchellbosecke/pebble/spring/template/requestTest.html new file mode 100644 index 000000000..36bae1c17 --- /dev/null +++ b/pebble-spring/pebble-spring6/src/test/resources/com/mitchellbosecke/pebble/spring/template/requestTest.html @@ -0,0 +1,11 @@ + + + + + test + + + +Context path = {{ request.contextPath }} + + \ No newline at end of file diff --git a/pebble-spring/pebble-spring6/src/test/resources/com/mitchellbosecke/pebble/spring/template/responseTest.html b/pebble-spring/pebble-spring6/src/test/resources/com/mitchellbosecke/pebble/spring/template/responseTest.html new file mode 100644 index 000000000..d468d1ee9 --- /dev/null +++ b/pebble-spring/pebble-spring6/src/test/resources/com/mitchellbosecke/pebble/spring/template/responseTest.html @@ -0,0 +1,12 @@ + + + + + test + + + +Response contentType = {{ response.contentType }} +Response character encoding = {{ response.characterEncoding }} + + \ No newline at end of file diff --git a/pebble-spring/pebble-spring6/src/test/resources/com/mitchellbosecke/pebble/spring/template/sessionTest.html b/pebble-spring/pebble-spring6/src/test/resources/com/mitchellbosecke/pebble/spring/template/sessionTest.html new file mode 100644 index 000000000..856fd1113 --- /dev/null +++ b/pebble-spring/pebble-spring6/src/test/resources/com/mitchellbosecke/pebble/spring/template/sessionTest.html @@ -0,0 +1,11 @@ + + + + + test + + + +Session maxInactiveInterval = {{ session.maxInactiveInterval }} + + \ No newline at end of file diff --git a/pebble-spring/pom.xml b/pebble-spring/pom.xml index 572a8db3c..c804a7c0a 100644 --- a/pebble-spring/pom.xml +++ b/pebble-spring/pom.xml @@ -4,7 +4,7 @@ io.pebbletemplates pebble-project - 3.1.7-SNAPSHOT + 4.0.0-SNAPSHOT pebble-spring diff --git a/pebble/pom.xml b/pebble/pom.xml index ef183effe..56e40abd2 100644 --- a/pebble/pom.xml +++ b/pebble/pom.xml @@ -3,7 +3,7 @@ io.pebbletemplates pebble-project - 3.1.7-SNAPSHOT + 4.0.0-SNAPSHOT pebble diff --git a/pom.xml b/pom.xml index 4fa6f6b87..46cbb1065 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ 4.0.0 io.pebbletemplates pebble-project - 3.1.7-SNAPSHOT + 4.0.0-SNAPSHOT pom @@ -19,7 +19,7 @@ UTF-8 - 1.8 + 17 @@ -151,6 +151,21 @@ + + + spring-milestones + Spring Milestones + https://repo.spring.io/milestone + + + + + spring-milestones + Spring Milestones + https://repo.spring.io/milestone + + + scm:git:git://github.com/PebbleTemplates/pebble.git scm:git:git@github.com:PebbleTemplates/pebble.git From ff905bd8f099c5fca5c0338abfb938bfc4b17873 Mon Sep 17 00:00:00 2001 From: Eric Bussieres Date: Wed, 26 Oct 2022 16:24:18 -0400 Subject: [PATCH 2/3] compiler --- .travis.yml | 3 +-- docs/src/orchid/resources/changelog/v4_0_0.md | 2 +- pebble-spring/pebble-spring-boot-starter/pom.xml | 1 + pebble-spring/pebble-spring6/pom.xml | 1 + pebble-spring/pom.xml | 2 ++ pom.xml | 2 +- 6 files changed, 7 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index 864e3f765..b2e80d9ae 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,8 +8,7 @@ addons: language: java jdk: - - openjdk8 - - openjdk11 + - openjdk17 after_success: - export VERSION=$(echo "cat //*[local-name()='project']/*[local-name()='version']/text()" | xmllint --nocdata --shell pom.xml | sed '1d;$d') diff --git a/docs/src/orchid/resources/changelog/v4_0_0.md b/docs/src/orchid/resources/changelog/v4_0_0.md index 2ad16bfd2..953c78850 100644 --- a/docs/src/orchid/resources/changelog/v4_0_0.md +++ b/docs/src/orchid/resources/changelog/v4_0_0.md @@ -3,4 +3,4 @@ version: '4.0.0' --- - Add support for spring framework 6 and spring-boot 3 (#) -- Bump minimum supported java version to 17 in order to work with spring \ No newline at end of file +- Bump minimum supported java version to 17 in pebble-spring6 and pebble-spring-boot-starter in order to work with spring \ No newline at end of file diff --git a/pebble-spring/pebble-spring-boot-starter/pom.xml b/pebble-spring/pebble-spring-boot-starter/pom.xml index e158dc1de..589a65b98 100644 --- a/pebble-spring/pebble-spring-boot-starter/pom.xml +++ b/pebble-spring/pebble-spring-boot-starter/pom.xml @@ -14,6 +14,7 @@ http://pebbletemplates.io + 17 3.0.0-RC1 diff --git a/pebble-spring/pebble-spring6/pom.xml b/pebble-spring/pebble-spring6/pom.xml index 836eda21f..3d3aeef78 100644 --- a/pebble-spring/pebble-spring6/pom.xml +++ b/pebble-spring/pebble-spring6/pom.xml @@ -14,6 +14,7 @@ http://pebbletemplates.io + 17 5.0.0 6.0.0-RC2 4.8.1 diff --git a/pebble-spring/pom.xml b/pebble-spring/pom.xml index c804a7c0a..ec32cd0a0 100644 --- a/pebble-spring/pom.xml +++ b/pebble-spring/pom.xml @@ -12,6 +12,8 @@ pebble-spring5 + pebble-legacy-spring-boot-starter + pebble-spring6 pebble-spring-boot-starter diff --git a/pom.xml b/pom.xml index 46cbb1065..5772fbaa0 100644 --- a/pom.xml +++ b/pom.xml @@ -19,7 +19,7 @@ UTF-8 - 17 + 1.8 From e0bcdfb5632109ee4c528ec9a3ddf4b55edbc45e Mon Sep 17 00:00:00 2001 From: Eric Bussieres Date: Wed, 26 Oct 2022 16:42:43 -0400 Subject: [PATCH 3/3] make it work for jdk 17 --- docs/pom.xml | 2 +- .../changelog/{v4_0_0.md => v3_2_0.md} | 2 +- .../pebble-legacy-spring-boot-starter/pom.xml | 2 +- .../pebble-spring-boot-starter/pom.xml | 2 +- pebble-spring/pebble-spring5/pom.xml | 2 +- pebble-spring/pebble-spring6/pom.xml | 2 +- pebble-spring/pom.xml | 2 +- pebble/pom.xml | 2 +- .../pebble/MethodAccessTemplateTest.java | 33 ------------------- .../methodaccess/InstanceProvider.java | 9 +++++ pom.xml | 2 +- 11 files changed, 18 insertions(+), 42 deletions(-) rename docs/src/orchid/resources/changelog/{v4_0_0.md => v3_2_0.md} (91%) diff --git a/docs/pom.xml b/docs/pom.xml index 70c25e078..76ea44943 100644 --- a/docs/pom.xml +++ b/docs/pom.xml @@ -5,7 +5,7 @@ io.pebbletemplates pebble-project - 4.0.0-SNAPSHOT + 3.2.0-SNAPSHOT docs diff --git a/docs/src/orchid/resources/changelog/v4_0_0.md b/docs/src/orchid/resources/changelog/v3_2_0.md similarity index 91% rename from docs/src/orchid/resources/changelog/v4_0_0.md rename to docs/src/orchid/resources/changelog/v3_2_0.md index 953c78850..51ad2888d 100644 --- a/docs/src/orchid/resources/changelog/v4_0_0.md +++ b/docs/src/orchid/resources/changelog/v3_2_0.md @@ -1,5 +1,5 @@ --- -version: '4.0.0' +version: '3.2.0' --- - Add support for spring framework 6 and spring-boot 3 (#) diff --git a/pebble-spring/pebble-legacy-spring-boot-starter/pom.xml b/pebble-spring/pebble-legacy-spring-boot-starter/pom.xml index 495de9101..a28628a4f 100644 --- a/pebble-spring/pebble-legacy-spring-boot-starter/pom.xml +++ b/pebble-spring/pebble-legacy-spring-boot-starter/pom.xml @@ -4,7 +4,7 @@ pebble-spring io.pebbletemplates - 4.0.0-SNAPSHOT + 3.2.0-SNAPSHOT pebble-legacy-spring-boot-starter diff --git a/pebble-spring/pebble-spring-boot-starter/pom.xml b/pebble-spring/pebble-spring-boot-starter/pom.xml index 589a65b98..6bc14e0b1 100644 --- a/pebble-spring/pebble-spring-boot-starter/pom.xml +++ b/pebble-spring/pebble-spring-boot-starter/pom.xml @@ -4,7 +4,7 @@ pebble-spring io.pebbletemplates - 4.0.0-SNAPSHOT + 3.2.0-SNAPSHOT pebble-spring-boot-starter diff --git a/pebble-spring/pebble-spring5/pom.xml b/pebble-spring/pebble-spring5/pom.xml index aa4cc78cb..bdf801474 100644 --- a/pebble-spring/pebble-spring5/pom.xml +++ b/pebble-spring/pebble-spring5/pom.xml @@ -4,7 +4,7 @@ pebble-spring io.pebbletemplates - 4.0.0-SNAPSHOT + 3.2.0-SNAPSHOT pebble-spring5 diff --git a/pebble-spring/pebble-spring6/pom.xml b/pebble-spring/pebble-spring6/pom.xml index 3d3aeef78..5b66b951a 100644 --- a/pebble-spring/pebble-spring6/pom.xml +++ b/pebble-spring/pebble-spring6/pom.xml @@ -4,7 +4,7 @@ pebble-spring io.pebbletemplates - 4.0.0-SNAPSHOT + 3.2.0-SNAPSHOT pebble-spring6 diff --git a/pebble-spring/pom.xml b/pebble-spring/pom.xml index ec32cd0a0..c0e247d8a 100644 --- a/pebble-spring/pom.xml +++ b/pebble-spring/pom.xml @@ -4,7 +4,7 @@ io.pebbletemplates pebble-project - 4.0.0-SNAPSHOT + 3.2.0-SNAPSHOT pebble-spring diff --git a/pebble/pom.xml b/pebble/pom.xml index 56e40abd2..92a0c3b15 100644 --- a/pebble/pom.xml +++ b/pebble/pom.xml @@ -3,7 +3,7 @@ io.pebbletemplates pebble-project - 4.0.0-SNAPSHOT + 3.2.0-SNAPSHOT pebble diff --git a/pebble/src/test/java/com/mitchellbosecke/pebble/MethodAccessTemplateTest.java b/pebble/src/test/java/com/mitchellbosecke/pebble/MethodAccessTemplateTest.java index 0a5bfe99b..3cd973056 100644 --- a/pebble/src/test/java/com/mitchellbosecke/pebble/MethodAccessTemplateTest.java +++ b/pebble/src/test/java/com/mitchellbosecke/pebble/MethodAccessTemplateTest.java @@ -97,39 +97,6 @@ private Executable templateEvaluation(PebbleEngine pebble) { } } - @Nested - class SystemTest { - - @Test - void testIfAccessIsForbiddenWhenAllowUnsafeMethodsIsFalse() { - PebbleEngine pebble = MethodAccessTemplateTest.this.pebbleEngine(); - assertThrows(ClassAccessException.class, this.templateEvaluation(pebble)); - } - - @Test - void testIfAccessIsAllowedWhenAllowUnsafeMethodsIsTrue() throws Throwable { - PebbleEngine pebble = MethodAccessTemplateTest.this.unsafePebbleEngine(); - this.templateEvaluation(pebble).execute(); - } - - private Executable templateEvaluation(PebbleEngine pebble) { - return () -> { - Class systemClass = Class.forName("java.lang.System"); - systemClass.getMethod("gc").setAccessible(true); - - Constructor constructor = systemClass.getDeclaredConstructor(); - constructor.setAccessible(true); - System system = (System) constructor.newInstance(); - - String source = "{{system.gc()}}"; - PebbleTemplate template = pebble.getTemplate(source); - Map context = new HashMap<>(); - context.put("system", system); - MethodAccessTemplateTest.this.evaluateTemplate(template, context); - }; - } - } - @Nested class MethodTest { diff --git a/pebble/src/test/java/com/mitchellbosecke/pebble/attributes/methodaccess/InstanceProvider.java b/pebble/src/test/java/com/mitchellbosecke/pebble/attributes/methodaccess/InstanceProvider.java index b45d8db59..a14a9dd9b 100644 --- a/pebble/src/test/java/com/mitchellbosecke/pebble/attributes/methodaccess/InstanceProvider.java +++ b/pebble/src/test/java/com/mitchellbosecke/pebble/attributes/methodaccess/InstanceProvider.java @@ -1,5 +1,6 @@ package com.mitchellbosecke.pebble.attributes.methodaccess; +import java.lang.reflect.AccessibleObject; import java.lang.reflect.Constructor; public class InstanceProvider { @@ -21,6 +22,14 @@ Object createObject(Class declaringClass) throws NoSuchFieldException, NoSuch return Foo.class.getDeclaredConstructor(); case "java.lang.Integer": return Integer.valueOf(1); + case "java.lang.System": + return System.class; + case "java.lang.Runtime": + return Runtime.class; + case "java.lang.reflect.AccessibleObject": + return AccessibleObject.class; + case "java.lang.ThreadGroup": + return Runtime.class; default: throw new RuntimeException( String.format("No object instance defined for class %s", declaringClass.getName())); diff --git a/pom.xml b/pom.xml index 5772fbaa0..095889e85 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ 4.0.0 io.pebbletemplates pebble-project - 4.0.0-SNAPSHOT + 3.2.0-SNAPSHOT pom