diff --git a/docs/src/orchid/resources/wiki/guide/spring-integration.md b/docs/src/orchid/resources/wiki/guide/spring-integration.md index 33b1b03d1..c47bc24a7 100644 --- a/docs/src/orchid/resources/wiki/guide/spring-integration.md +++ b/docs/src/orchid/resources/wiki/guide/spring-integration.md @@ -35,7 +35,7 @@ public class MvcConfig extends WebMvcConfigurerAdapter { @Bean public Loader templateLoader(){ - return new ServletLoader(servletContext); + return new ServletLoader(this.servletContext); } @Bean @@ -47,7 +47,7 @@ public class MvcConfig extends WebMvcConfigurerAdapter { public PebbleEngine pebbleEngine() { return new PebbleEngine.Builder() .loader(this.templateLoader()) - .extension(springExtension()) + .extension(this.springExtension()) .build(); } @@ -56,7 +56,7 @@ public class MvcConfig extends WebMvcConfigurerAdapter { PebbleViewResolver viewResolver = new PebbleViewResolver(); viewResolver.setPrefix("/WEB-INF/templates/"); viewResolver.setSuffix(".html"); - viewResolver.setPebbleEngine(pebbleEngine()); + viewResolver.setPebbleEngine(this.pebbleEngine()); return viewResolver; } @@ -75,7 +75,7 @@ public class ProfileController { @RequestMapping public ModelAndView getUserProfile(@RequestParam("id") long id) { ModelAndView mav = new ModelAndView(); - mav.addObject("user", userService.getUser(id)); + mav.addObject("user", this.userService.getUser(id)); mav.setViewName("profile"); return mav; } @@ -168,7 +168,7 @@ To output any error: A timer in PebbleView is available to output the time taken to process a template. Just add the following config to your log4j.xml ```xml - + ``` diff --git a/pebble-spring/pebble-spring-boot-starter/pom.xml b/pebble-spring/pebble-spring-boot-starter/pom.xml index 979060c00..e3282b424 100644 --- a/pebble-spring/pebble-spring-boot-starter/pom.xml +++ b/pebble-spring/pebble-spring-boot-starter/pom.xml @@ -24,6 +24,12 @@ ${boot.version} true + + org.springframework.boot + spring-boot-starter-webflux + ${boot.version} + true + io.pebbletemplates pebble-spring5 diff --git a/pebble-spring/pebble-spring-boot-starter/src/main/java/com/mitchellbosecke/pebble/boot/autoconfigure/PebbleAutoConfiguration.java b/pebble-spring/pebble-spring-boot-starter/src/main/java/com/mitchellbosecke/pebble/boot/autoconfigure/PebbleAutoConfiguration.java index 57ecbbc7a..fa4bc3709 100644 --- a/pebble-spring/pebble-spring-boot-starter/src/main/java/com/mitchellbosecke/pebble/boot/autoconfigure/PebbleAutoConfiguration.java +++ b/pebble-spring/pebble-spring-boot-starter/src/main/java/com/mitchellbosecke/pebble/boot/autoconfigure/PebbleAutoConfiguration.java @@ -4,23 +4,27 @@ import com.mitchellbosecke.pebble.extension.Extension; import com.mitchellbosecke.pebble.loader.ClasspathLoader; import com.mitchellbosecke.pebble.loader.Loader; -import com.mitchellbosecke.pebble.spring.PebbleViewResolver; import com.mitchellbosecke.pebble.spring.extension.SpringExtension; -import java.util.List; -import javax.servlet.Servlet; +import com.mitchellbosecke.pebble.spring.reactive.PebbleReactiveViewResolver; +import com.mitchellbosecke.pebble.spring.servlet.PebbleViewResolver; + import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; 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.boot.autoconfigure.web.reactive.WebFluxAutoConfiguration; import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import java.util.List; + @Configuration @ConditionalOnClass(PebbleEngine.class) -@AutoConfigureAfter(WebMvcAutoConfiguration.class) +@AutoConfigureAfter({WebMvcAutoConfiguration.class, WebFluxAutoConfiguration.class}) @EnableConfigurationProperties(PebbleProperties.class) public class PebbleAutoConfiguration { @@ -40,7 +44,6 @@ public Loader pebbleLoader() { loader.setSuffix(this.properties.getSuffix()); return loader; } - } @Configuration @@ -82,9 +85,8 @@ public PebbleEngine pebbleEngine() { } @Configuration - @ConditionalOnWebApplication - @ConditionalOnClass({Servlet.class}) - public static class PebbleViewResolverConfiguration { + @ConditionalOnWebApplication(type = Type.SERVLET) + public static class PebbleWebMvcConfiguration { @Autowired private PebbleProperties properties; @@ -108,6 +110,33 @@ public PebbleViewResolver pebbleViewResolver() { } } + @Configuration + @ConditionalOnWebApplication(type = Type.REACTIVE) + public static class PebbleReactiveConfiguration { + + @Autowired + private PebbleProperties properties; + + @Autowired + private PebbleEngine pebbleEngine; + + @Bean + @ConditionalOnMissingBean(name = "pebbleReactiveViewResolver") + public PebbleReactiveViewResolver pebbleReactiveViewResolver() { + String prefix = this.properties.getPrefix(); + if (this.pebbleEngine.getLoader() instanceof ClasspathLoader) { + // classpathloader doesn't like leading slashes in paths + prefix = stripLeadingSlash(this.properties.getPrefix()); + } + PebbleReactiveViewResolver resolver = new PebbleReactiveViewResolver(this.pebbleEngine); + resolver.setPrefix(prefix); + resolver.setSuffix(this.properties.getSuffix()); + resolver.setViewNames(this.properties.getViewNames()); + resolver.setRequestContextAttribute(this.properties.getRequestContextAttribute()); + return resolver; + } + } + private static String stripLeadingSlash(String value) { if (value == null) { return null; diff --git a/pebble-spring/pebble-spring-boot-starter/src/test/java/com/mitchellbosecke/pebble/boot/AppConfig.java b/pebble-spring/pebble-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-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-spring-boot-starter/src/test/java/com/mitchellbosecke/pebble/boot/Application.java b/pebble-spring/pebble-spring-boot-starter/src/test/java/com/mitchellbosecke/pebble/boot/Application.java index a02caf2cf..5a8c365d1 100644 --- a/pebble-spring/pebble-spring-boot-starter/src/test/java/com/mitchellbosecke/pebble/boot/Application.java +++ b/pebble-spring/pebble-spring-boot-starter/src/test/java/com/mitchellbosecke/pebble/boot/Application.java @@ -1,62 +1,12 @@ 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 java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.context.MessageSource; -import org.springframework.context.annotation.Bean; -import org.springframework.context.support.ResourceBundleMessageSource; -import org.springframework.web.servlet.LocaleResolver; -import org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver; @SpringBootApplication -@ServletComponentScan public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } - - @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!"; - } - } - } - } \ No newline at end of file diff --git a/pebble-spring/pebble-spring-boot-starter/src/test/java/com/mitchellbosecke/pebble/boot/Controllers.java b/pebble-spring/pebble-spring-boot-starter/src/test/java/com/mitchellbosecke/pebble/boot/Controllers.java index d99625b08..52eb0afa7 100644 --- a/pebble-spring/pebble-spring-boot-starter/src/test/java/com/mitchellbosecke/pebble/boot/Controllers.java +++ b/pebble-spring/pebble-spring-boot-starter/src/test/java/com/mitchellbosecke/pebble/boot/Controllers.java @@ -1,7 +1,6 @@ package com.mitchellbosecke.pebble.boot; import org.springframework.stereotype.Controller; -import org.springframework.ui.ModelMap; import org.springframework.web.bind.annotation.RequestMapping; @Controller @@ -13,17 +12,17 @@ public String hello() { } @RequestMapping("/index.action") - public String index(ModelMap model) { + public String index() { return "index"; } @RequestMapping("/contextPath.action") - public String contextPath(ModelMap model) { + public String contextPath() { return "contextPath"; } @RequestMapping("/extensions.action") - public String extensions(ModelMap model) { + public String extensions() { return "extensions"; } diff --git a/pebble-spring/pebble-spring-boot-starter/src/test/java/com/mitchellbosecke/pebble/boot/NonWebApplication.java b/pebble-spring/pebble-spring-boot-starter/src/test/java/com/mitchellbosecke/pebble/boot/NonWebApplication.java index 6b4216d00..82268aea4 100644 --- a/pebble-spring/pebble-spring-boot-starter/src/test/java/com/mitchellbosecke/pebble/boot/NonWebApplication.java +++ b/pebble-spring/pebble-spring-boot-starter/src/test/java/com/mitchellbosecke/pebble/boot/NonWebApplication.java @@ -4,9 +4,6 @@ import org.springframework.boot.WebApplicationType; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.builder.SpringApplicationBuilder; -import org.springframework.context.MessageSource; -import org.springframework.context.annotation.Bean; -import org.springframework.context.support.ResourceBundleMessageSource; @SpringBootApplication public class NonWebApplication { @@ -17,13 +14,4 @@ public static void main(String[] args) { .build(); sa.run(args); } - - @Bean - public MessageSource messageSource() { - ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource(); - messageSource.setBasename("messages"); - messageSource.setFallbackToSystemLocale(false); - return messageSource; - } - } diff --git a/pebble-spring/pebble-spring-boot-starter/src/test/java/com/mitchellbosecke/pebble/boot/ReactiveApplication.java b/pebble-spring/pebble-spring-boot-starter/src/test/java/com/mitchellbosecke/pebble/boot/ReactiveApplication.java new file mode 100644 index 000000000..b505904cc --- /dev/null +++ b/pebble-spring/pebble-spring-boot-starter/src/test/java/com/mitchellbosecke/pebble/boot/ReactiveApplication.java @@ -0,0 +1,16 @@ +package com.mitchellbosecke.pebble.boot; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +import static org.springframework.boot.WebApplicationType.REACTIVE; + +@SpringBootApplication +public class ReactiveApplication { + + public static void main(String[] args) { + SpringApplication application = new SpringApplication(Application.class); + application.setWebApplicationType(REACTIVE); + application.run(args); + } +} \ No newline at end of file diff --git a/pebble-spring/pebble-spring-boot-starter/src/test/java/com/mitchellbosecke/pebble/boot/ServletComponenRegisteringPostProcessor.java b/pebble-spring/pebble-spring-boot-starter/src/test/java/com/mitchellbosecke/pebble/boot/ServletComponenRegisteringPostProcessor.java deleted file mode 100644 index 3bf919dd8..000000000 --- a/pebble-spring/pebble-spring-boot-starter/src/test/java/com/mitchellbosecke/pebble/boot/ServletComponenRegisteringPostProcessor.java +++ /dev/null @@ -1,86 +0,0 @@ -package com.mitchellbosecke.pebble.boot; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Set; -import org.springframework.beans.BeansException; -import org.springframework.beans.factory.config.BeanDefinition; -import org.springframework.beans.factory.config.BeanFactoryPostProcessor; -import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; -import org.springframework.beans.factory.support.BeanDefinitionRegistry; -import org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext; -import org.springframework.context.ApplicationContext; -import org.springframework.context.ApplicationContextAware; -import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider; -import org.springframework.context.annotation.ScannedGenericBeanDefinition; - -class ServletComponentRegisteringPostProcessor implements BeanFactoryPostProcessor, - ApplicationContextAware { - - private static final List HANDLERS; - - static { - List handlers = new ArrayList(); - handlers.add(new WebServletHandler()); - handlers.add(new WebFilterHandler()); - handlers.add(new WebListenerHandler()); - HANDLERS = Collections.unmodifiableList(handlers); - } - - private final Set packagesToScan; - - private ApplicationContext applicationContext; - - ServletComponentRegisteringPostProcessor(Set packagesToScan) { - this.packagesToScan = packagesToScan; - } - - @Override - public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) - throws BeansException { - if (isRunningInEmbeddedContainer()) { - ClassPathScanningCandidateComponentProvider componentProvider = createComponentProvider(); - for (String packageToScan : this.packagesToScan) { - scanPackage(componentProvider, packageToScan); - } - } - } - - private void scanPackage(ClassPathScanningCandidateComponentProvider componentProvider, - String packageToScan) { - for (BeanDefinition candidate : componentProvider.findCandidateComponents(packageToScan)) { - if (candidate instanceof ScannedGenericBeanDefinition) { - for (ServletComponentHandler handler : HANDLERS) { - handler.handle(((ScannedGenericBeanDefinition) candidate), - (BeanDefinitionRegistry) this.applicationContext); - } - } - } - } - - private boolean isRunningInEmbeddedContainer() { - return this.applicationContext instanceof ServletWebServerApplicationContext - && ((ServletWebServerApplicationContext) this.applicationContext).getServletContext() - == null; - } - - private ClassPathScanningCandidateComponentProvider createComponentProvider() { - ClassPathScanningCandidateComponentProvider componentProvider = new ClassPathScanningCandidateComponentProvider( - false); - for (ServletComponentHandler handler : HANDLERS) { - componentProvider.addIncludeFilter(handler.getTypeFilter()); - } - return componentProvider; - } - - Set getPackagesToScan() { - return Collections.unmodifiableSet(this.packagesToScan); - } - - @Override - public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { - this.applicationContext = applicationContext; - } - -} \ No newline at end of file diff --git a/pebble-spring/pebble-spring-boot-starter/src/test/java/com/mitchellbosecke/pebble/boot/ServletComponentHandler.java b/pebble-spring/pebble-spring-boot-starter/src/test/java/com/mitchellbosecke/pebble/boot/ServletComponentHandler.java deleted file mode 100644 index c57882a97..000000000 --- a/pebble-spring/pebble-spring-boot-starter/src/test/java/com/mitchellbosecke/pebble/boot/ServletComponentHandler.java +++ /dev/null @@ -1,61 +0,0 @@ -package com.mitchellbosecke.pebble.boot; - -import java.lang.annotation.Annotation; -import java.util.HashMap; -import java.util.Map; -import org.springframework.beans.factory.config.BeanDefinition; -import org.springframework.beans.factory.support.BeanDefinitionRegistry; -import org.springframework.context.annotation.ScannedGenericBeanDefinition; -import org.springframework.core.annotation.AnnotationAttributes; -import org.springframework.core.type.filter.AnnotationTypeFilter; -import org.springframework.core.type.filter.TypeFilter; -import org.springframework.util.Assert; - -abstract class ServletComponentHandler { - - private final Class annotationType; - - private final TypeFilter typeFilter; - - protected ServletComponentHandler(Class annotationType) { - this.typeFilter = new AnnotationTypeFilter(annotationType); - this.annotationType = annotationType; - } - - TypeFilter getTypeFilter() { - return this.typeFilter; - } - - protected String[] extractUrlPatterns(String attribute, Map attributes) { - String[] value = (String[]) attributes.get("value"); - String[] urlPatterns = (String[]) attributes.get("urlPatterns"); - if (urlPatterns.length > 0) { - Assert - .state(value.length == 0, "The urlPatterns and value attributes are mutually exclusive."); - return urlPatterns; - } - return value; - } - - protected final Map extractInitParameters(Map attributes) { - Map initParameters = new HashMap(); - for (AnnotationAttributes initParam : (AnnotationAttributes[]) attributes.get("initParams")) { - String name = (String) initParam.get("name"); - String value = (String) initParam.get("value"); - initParameters.put(name, value); - } - return initParameters; - } - - void handle(ScannedGenericBeanDefinition beanDefinition, BeanDefinitionRegistry registry) { - Map attributes = beanDefinition.getMetadata() - .getAnnotationAttributes(this.annotationType.getName()); - if (attributes != null) { - doHandle(attributes, beanDefinition, registry); - } - } - - protected abstract void doHandle(Map attributes, BeanDefinition beanDefinition, - BeanDefinitionRegistry registry); - -} \ No newline at end of file diff --git a/pebble-spring/pebble-spring-boot-starter/src/test/java/com/mitchellbosecke/pebble/boot/ServletComponentScan.java b/pebble-spring/pebble-spring-boot-starter/src/test/java/com/mitchellbosecke/pebble/boot/ServletComponentScan.java deleted file mode 100644 index ba3bb3361..000000000 --- a/pebble-spring/pebble-spring-boot-starter/src/test/java/com/mitchellbosecke/pebble/boot/ServletComponentScan.java +++ /dev/null @@ -1,43 +0,0 @@ -package com.mitchellbosecke.pebble.boot; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; -import org.springframework.context.annotation.Import; - -@Target(ElementType.TYPE) -@Retention(RetentionPolicy.RUNTIME) -@Documented -@Import(ServletComponentScanRegistrar.class) -public @interface ServletComponentScan { - - /** - * Alias for the {@link #basePackages()} attribute. Allows for more concise annotation - * declarations e.g.: {@code @ServletComponentScan("org.my.pkg")} instead of {@code - * - * @return the base packages to scan - * @ServletComponentScan(basePackages="org.my.pkg")}. - */ - String[] value() default {}; - - /** - * Base packages to scan for annotated servlet components. {@link #value()} is an alias for (and - * mutually exclusive with) this attribute. - *

- * Use {@link #basePackageClasses()} for a type-safe alternative to String-based package names. - * - * @return the base packages to scan - */ - String[] basePackages() default {}; - - /** - * Type-safe alternative to {@link #basePackages()} for specifying the packages to scan for - * annotated servlet components. The package of each class specified will be scanned. - * - * @return classes from the base packages to scan - */ - Class[] basePackageClasses() default {}; - -} \ No newline at end of file diff --git a/pebble-spring/pebble-spring-boot-starter/src/test/java/com/mitchellbosecke/pebble/boot/ServletComponentScanRegistrar.java b/pebble-spring/pebble-spring-boot-starter/src/test/java/com/mitchellbosecke/pebble/boot/ServletComponentScanRegistrar.java deleted file mode 100644 index b5c1dadee..000000000 --- a/pebble-spring/pebble-spring-boot-starter/src/test/java/com/mitchellbosecke/pebble/boot/ServletComponentScanRegistrar.java +++ /dev/null @@ -1,73 +0,0 @@ -package com.mitchellbosecke.pebble.boot; - -import java.util.Arrays; -import java.util.Collections; -import java.util.LinkedHashSet; -import java.util.Set; -import org.springframework.beans.factory.config.BeanDefinition; -import org.springframework.beans.factory.config.ConstructorArgumentValues.ValueHolder; -import org.springframework.beans.factory.support.BeanDefinitionRegistry; -import org.springframework.beans.factory.support.GenericBeanDefinition; -import org.springframework.context.annotation.ImportBeanDefinitionRegistrar; -import org.springframework.core.annotation.AnnotationAttributes; -import org.springframework.core.type.AnnotationMetadata; -import org.springframework.util.Assert; -import org.springframework.util.ClassUtils; -import org.springframework.util.ObjectUtils; - -class ServletComponentScanRegistrar implements ImportBeanDefinitionRegistrar { - - private static final String BEAN_NAME = "servletComponentRegisteringPostProcessor"; - - @Override - public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, - BeanDefinitionRegistry registry) { - Set packagesToScan = getPackagesToScan(importingClassMetadata); - if (registry.containsBeanDefinition(BEAN_NAME)) { - updatePostProcessor(registry, packagesToScan); - } else { - addPostProcessor(registry, packagesToScan); - } - } - - private void updatePostProcessor(BeanDefinitionRegistry registry, Set packagesToScan) { - BeanDefinition definition = registry.getBeanDefinition(BEAN_NAME); - ValueHolder constructorArguments = definition.getConstructorArgumentValues() - .getGenericArgumentValue(Set.class); - @SuppressWarnings("unchecked") - Set mergedPackages = (Set) constructorArguments.getValue(); - mergedPackages.addAll(packagesToScan); - constructorArguments.setValue(mergedPackages); - } - - private void addPostProcessor(BeanDefinitionRegistry registry, Set packagesToScan) { - GenericBeanDefinition beanDefinition = new GenericBeanDefinition(); - beanDefinition.setBeanClass(ServletComponentRegisteringPostProcessor.class); - beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(packagesToScan); - beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE); - registry.registerBeanDefinition(BEAN_NAME, beanDefinition); - } - - private Set getPackagesToScan(AnnotationMetadata metadata) { - AnnotationAttributes attributes = AnnotationAttributes - .fromMap(metadata.getAnnotationAttributes(ServletComponentScan.class.getName())); - String[] value = attributes.getStringArray("value"); - String[] basePackages = attributes.getStringArray("basePackages"); - Class[] basePackageClasses = attributes.getClassArray("basePackageClasses"); - if (!ObjectUtils.isEmpty(value)) { - Assert.state(ObjectUtils.isEmpty(basePackages), - "@ServletComponentScan basePackages and value attributes are" + " mutually exclusive"); - } - Set packagesToScan = new LinkedHashSet(); - packagesToScan.addAll(Arrays.asList(value)); - packagesToScan.addAll(Arrays.asList(basePackages)); - for (Class basePackageClass : basePackageClasses) { - packagesToScan.add(ClassUtils.getPackageName(basePackageClass)); - } - if (packagesToScan.isEmpty()) { - return Collections.singleton(ClassUtils.getPackageName(metadata.getClassName())); - } - return packagesToScan; - } - -} \ No newline at end of file diff --git a/pebble-spring/pebble-spring-boot-starter/src/test/java/com/mitchellbosecke/pebble/boot/WebFilterHandler.java b/pebble-spring/pebble-spring-boot-starter/src/test/java/com/mitchellbosecke/pebble/boot/WebFilterHandler.java deleted file mode 100644 index 3d5bd9e9e..000000000 --- a/pebble-spring/pebble-spring-boot-starter/src/test/java/com/mitchellbosecke/pebble/boot/WebFilterHandler.java +++ /dev/null @@ -1,54 +0,0 @@ -package com.mitchellbosecke.pebble.boot; - -import java.util.Arrays; -import java.util.EnumSet; -import java.util.Map; -import javax.servlet.DispatcherType; -import javax.servlet.annotation.WebFilter; -import org.springframework.beans.factory.config.BeanDefinition; -import org.springframework.beans.factory.support.BeanDefinitionBuilder; -import org.springframework.beans.factory.support.BeanDefinitionRegistry; -import org.springframework.boot.web.servlet.FilterRegistrationBean; -import org.springframework.util.StringUtils; - -class WebFilterHandler extends ServletComponentHandler { - - WebFilterHandler() { - super(WebFilter.class); - } - - @Override - public void doHandle(Map attributes, BeanDefinition beanDefinition, - BeanDefinitionRegistry registry) { - BeanDefinitionBuilder builder = BeanDefinitionBuilder - .rootBeanDefinition(FilterRegistrationBean.class); - builder.addPropertyValue("asyncSupported", attributes.get("asyncSupported")); - builder.addPropertyValue("dispatcherTypes", extractDispatcherTypes(attributes)); - builder.addPropertyValue("filter", beanDefinition); - builder.addPropertyValue("initParameters", extractInitParameters(attributes)); - String name = determineName(attributes, beanDefinition); - builder.addPropertyValue("name", name); - builder.addPropertyValue("servletNames", attributes.get("servletNames")); - builder.addPropertyValue("urlPatterns", extractUrlPatterns("urlPatterns", attributes)); - registry.registerBeanDefinition(name, builder.getBeanDefinition()); - } - - private EnumSet extractDispatcherTypes(Map attributes) { - DispatcherType[] dispatcherTypes = (DispatcherType[]) attributes.get("dispatcherTypes"); - if (dispatcherTypes.length == 0) { - return EnumSet.noneOf(DispatcherType.class); - } - if (dispatcherTypes.length == 1) { - return EnumSet.of(dispatcherTypes[0]); - } - return EnumSet - .of(dispatcherTypes[0], Arrays.copyOfRange(dispatcherTypes, 1, dispatcherTypes.length)); - } - - private String determineName(Map attributes, BeanDefinition beanDefinition) { - return (String) (StringUtils.hasText((String) attributes.get("filterName")) ? attributes - .get("filterName") - : beanDefinition.getBeanClassName()); - } - -} \ No newline at end of file diff --git a/pebble-spring/pebble-spring-boot-starter/src/test/java/com/mitchellbosecke/pebble/boot/WebListenerHandler.java b/pebble-spring/pebble-spring-boot-starter/src/test/java/com/mitchellbosecke/pebble/boot/WebListenerHandler.java deleted file mode 100644 index 4658858e2..000000000 --- a/pebble-spring/pebble-spring-boot-starter/src/test/java/com/mitchellbosecke/pebble/boot/WebListenerHandler.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.mitchellbosecke.pebble.boot; - -import java.util.Map; -import javax.servlet.annotation.WebListener; -import org.springframework.beans.factory.config.BeanDefinition; -import org.springframework.beans.factory.support.BeanDefinitionBuilder; -import org.springframework.beans.factory.support.BeanDefinitionRegistry; -import org.springframework.boot.web.servlet.ServletListenerRegistrationBean; - -class WebListenerHandler extends ServletComponentHandler { - - WebListenerHandler() { - super(WebListener.class); - } - - @Override - protected void doHandle(Map attributes, BeanDefinition beanDefinition, - BeanDefinitionRegistry registry) { - BeanDefinitionBuilder builder = BeanDefinitionBuilder - .rootBeanDefinition(ServletListenerRegistrationBean.class); - builder.addPropertyValue("listener", beanDefinition); - registry.registerBeanDefinition(beanDefinition.getBeanClassName(), builder.getBeanDefinition()); - } - -} \ No newline at end of file diff --git a/pebble-spring/pebble-spring-boot-starter/src/test/java/com/mitchellbosecke/pebble/boot/WebServletHandler.java b/pebble-spring/pebble-spring-boot-starter/src/test/java/com/mitchellbosecke/pebble/boot/WebServletHandler.java deleted file mode 100644 index 740c83bdd..000000000 --- a/pebble-spring/pebble-spring-boot-starter/src/test/java/com/mitchellbosecke/pebble/boot/WebServletHandler.java +++ /dev/null @@ -1,37 +0,0 @@ -package com.mitchellbosecke.pebble.boot; - -import java.util.Map; -import javax.servlet.annotation.WebServlet; -import org.springframework.beans.factory.config.BeanDefinition; -import org.springframework.beans.factory.support.BeanDefinitionBuilder; -import org.springframework.beans.factory.support.BeanDefinitionRegistry; -import org.springframework.boot.web.servlet.ServletRegistrationBean; -import org.springframework.util.StringUtils; - -class WebServletHandler extends ServletComponentHandler { - - WebServletHandler() { - super(WebServlet.class); - } - - @Override - public void doHandle(Map attributes, BeanDefinition beanDefinition, - BeanDefinitionRegistry registry) { - BeanDefinitionBuilder builder = BeanDefinitionBuilder - .rootBeanDefinition(ServletRegistrationBean.class); - builder.addPropertyValue("asyncSupported", attributes.get("asyncSupported")); - builder.addPropertyValue("initParameters", extractInitParameters(attributes)); - builder.addPropertyValue("loadOnStartup", attributes.get("loadOnStartup")); - String name = determineName(attributes, beanDefinition); - builder.addPropertyValue("name", name); - builder.addPropertyValue("servlet", beanDefinition); - builder.addPropertyValue("urlMappings", extractUrlPatterns("urlPatterns", attributes)); - registry.registerBeanDefinition(name, builder.getBeanDefinition()); - } - - private String determineName(Map attributes, BeanDefinition beanDefinition) { - return (String) (StringUtils.hasText((String) attributes.get("name")) ? attributes.get("name") - : beanDefinition.getBeanClassName()); - } - -} \ No newline at end of file diff --git a/pebble-spring/pebble-spring-boot-starter/src/test/java/com/mitchellbosecke/pebble/boot/CoreTests.java b/pebble-spring/pebble-spring-boot-starter/src/test/java/com/mitchellbosecke/pebble/boot/autoconfigure/NonWebAppTests.java similarity index 65% rename from pebble-spring/pebble-spring-boot-starter/src/test/java/com/mitchellbosecke/pebble/boot/CoreTests.java rename to pebble-spring/pebble-spring-boot-starter/src/test/java/com/mitchellbosecke/pebble/boot/autoconfigure/NonWebAppTests.java index 9ea36f50f..62b124a25 100644 --- a/pebble-spring/pebble-spring-boot-starter/src/test/java/com/mitchellbosecke/pebble/boot/CoreTests.java +++ b/pebble-spring/pebble-spring-boot-starter/src/test/java/com/mitchellbosecke/pebble/boot/autoconfigure/NonWebAppTests.java @@ -1,17 +1,20 @@ -package com.mitchellbosecke.pebble.boot; +package com.mitchellbosecke.pebble.boot.autoconfigure; import com.mitchellbosecke.pebble.PebbleEngine; -import java.io.StringWriter; +import com.mitchellbosecke.pebble.boot.NonWebApplication; + import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.junit4.SpringRunner; + +import java.io.StringWriter; -@RunWith(SpringJUnit4ClassRunner.class) +@RunWith(SpringRunner.class) @SpringBootTest(classes = NonWebApplication.class) -public class CoreTests { +public class NonWebAppTests { @Autowired private PebbleEngine pebbleEngine; @@ -19,7 +22,7 @@ public class CoreTests { @Test public void testOk() throws Exception { StringWriter sw = new StringWriter(); - pebbleEngine.getTemplate("hello").evaluate(sw); + this.pebbleEngine.getTemplate("hello").evaluate(sw); Assert.assertTrue(sw.toString() != null && !sw.toString().isEmpty()); } diff --git a/pebble-spring/pebble-spring-boot-starter/src/test/java/com/mitchellbosecke/pebble/boot/autoconfigure/PebbleAutoConfigurationTest.java b/pebble-spring/pebble-spring-boot-starter/src/test/java/com/mitchellbosecke/pebble/boot/autoconfigure/PebbleAutoConfigurationTest.java new file mode 100644 index 000000000..ab7d32ad0 --- /dev/null +++ b/pebble-spring/pebble-spring-boot-starter/src/test/java/com/mitchellbosecke/pebble/boot/autoconfigure/PebbleAutoConfigurationTest.java @@ -0,0 +1,107 @@ +package com.mitchellbosecke.pebble.boot.autoconfigure; + +import com.mitchellbosecke.pebble.PebbleEngine; +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 org.junit.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.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; + +import java.util.Locale; + +import static java.util.Locale.CHINESE; +import static org.assertj.core.api.Assertions.assertThat; + +public class PebbleAutoConfigurationTest { + + private static final Locale DEFAULT_LOCALE = CHINESE; + private AnnotationConfigServletWebApplicationContext webContext; + + private AnnotationConfigReactiveWebApplicationContext reactiveWebContext; + + @Test + public 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.getBeansOfType(PebbleViewResolver.class)).hasSize(1); + } + + @Test + public void registerCompilerForServletApp() { + this.loadWithServlet(CustomCompilerConfiguration.class); + assertThat(this.webContext.getBeansOfType(Loader.class)).hasSize(1); + assertThat(this.webContext.getBeansOfType(SpringExtension.class)).isEmpty(); + assertThat(this.webContext.getBeansOfType(PebbleEngine.class)).hasSize(1); + assertThat(this.webContext.getBean(PebbleEngine.class).getDefaultLocale()).isEqualTo(DEFAULT_LOCALE); + assertThat(this.webContext.getBeansOfType(PebbleViewResolver.class)).hasSize(1); + assertThat(this.webContext.getBeansOfType(PebbleReactiveViewResolver.class)).isEmpty(); + } + + @Test + public 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.getBeansOfType(PebbleViewResolver.class)).isEmpty(); + assertThat(this.reactiveWebContext.getBeansOfType(PebbleReactiveViewResolver.class)).hasSize(1); + assertThat(this.reactiveWebContext.getBeansOfType(PebbleViewResolver.class)).isEmpty(); + } + + @Test + public void registerCompilerForReactiveApp() { + this.loadWithReactive(CustomCompilerConfiguration.class); + assertThat(this.reactiveWebContext.getBeansOfType(Loader.class)).hasSize(1); + assertThat(this.reactiveWebContext.getBeansOfType(SpringExtension.class)).isEmpty(); + assertThat(this.reactiveWebContext.getBeansOfType(PebbleEngine.class)).hasSize(1); + assertThat(this.reactiveWebContext.getBean(PebbleEngine.class).getDefaultLocale()).isEqualTo(DEFAULT_LOCALE); + 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); + 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); + 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 CustomCompilerConfiguration { + + @Bean + public PebbleEngine pebbleEngine() { + return new PebbleEngine.Builder().defaultLocale(DEFAULT_LOCALE).build(); + } + + } + +} \ No newline at end of file diff --git a/pebble-spring/pebble-spring-boot-starter/src/test/java/com/mitchellbosecke/pebble/boot/autoconfigure/ReactiveAppTest.java b/pebble-spring/pebble-spring-boot-starter/src/test/java/com/mitchellbosecke/pebble/boot/autoconfigure/ReactiveAppTest.java new file mode 100644 index 000000000..419c7fb55 --- /dev/null +++ b/pebble-spring/pebble-spring-boot-starter/src/test/java/com/mitchellbosecke/pebble/boot/autoconfigure/ReactiveAppTest.java @@ -0,0 +1,82 @@ +package com.mitchellbosecke.pebble.boot.autoconfigure; + +import com.mitchellbosecke.pebble.boot.ReactiveApplication; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.MediaType; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.test.web.reactive.server.WebTestClient; + +import static org.assertj.core.api.Assertions.assertThat; + +@RunWith(SpringRunner.class) +@SpringBootTest(classes = ReactiveApplication.class, + webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, + properties = "spring.main.web-application-type=reactive") +public class ReactiveAppTest { + + @Autowired + private WebTestClient client; + + @Test + public 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 + public 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:"); + } + + @Test + public 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 + public 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 + public 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!"); + } +} + diff --git a/pebble-spring/pebble-spring-boot-starter/src/test/java/com/mitchellbosecke/pebble/boot/ControllerTest.java b/pebble-spring/pebble-spring-boot-starter/src/test/java/com/mitchellbosecke/pebble/boot/autoconfigure/ServletAppTest.java similarity index 75% rename from pebble-spring/pebble-spring-boot-starter/src/test/java/com/mitchellbosecke/pebble/boot/ControllerTest.java rename to pebble-spring/pebble-spring-boot-starter/src/test/java/com/mitchellbosecke/pebble/boot/autoconfigure/ServletAppTest.java index 672b3782a..7ac516011 100644 --- a/pebble-spring/pebble-spring-boot-starter/src/test/java/com/mitchellbosecke/pebble/boot/ControllerTest.java +++ b/pebble-spring/pebble-spring-boot-starter/src/test/java/com/mitchellbosecke/pebble/boot/autoconfigure/ServletAppTest.java @@ -1,27 +1,30 @@ -package com.mitchellbosecke.pebble.boot; +package com.mitchellbosecke.pebble.boot.autoconfigure; -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.Assert; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.http.MediaType; -import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.junit4.SpringRunner; 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; -@RunWith(SpringJUnit4ClassRunner.class) +import java.util.Locale; + +import static org.junit.Assert.assertEquals; +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; + +@RunWith(SpringRunner.class) @SpringBootTest(classes = Application.class, webEnvironment = WebEnvironment.RANDOM_PORT) -public class ControllerTest { +public class ServletAppTest { @Autowired private WebApplicationContext wac; @@ -35,22 +38,22 @@ public void setup() { @Test public void testOk() throws Exception { - mockMvc.perform(get("/index.action")).andExpect(status().isOk()) + this.mockMvc.perform(get("/index.action")).andExpect(status().isOk()) .andExpect(content().contentTypeCompatibleWith(MediaType.TEXT_HTML)) .andExpect(content().string("Hello Pebbleworld!")); } @Test public void testRequestAccess() throws Exception { - MvcResult result = mockMvc.perform(get("/contextPath.action")).andExpect(status().isOk()) + MvcResult result = this.mockMvc.perform(get("/contextPath.action")).andExpect(status().isOk()) .andExpect(content().contentTypeCompatibleWith(MediaType.TEXT_HTML)).andReturn(); - Assert.assertEquals("ctx path:" + result.getRequest().getContextPath(), + assertEquals("ctx path:" + result.getRequest().getContextPath(), result.getResponse().getContentAsString()); } @Test public void testEnglishHello() throws Exception { - mockMvc.perform(get("/hello.action").locale(Locale.forLanguageTag("en"))) + this.mockMvc.perform(get("/hello.action").locale(Locale.forLanguageTag("en"))) .andExpect(status().isOk()) .andExpect(content().contentTypeCompatibleWith(MediaType.TEXT_HTML)) .andExpect(content().string("Hello Boot!")); @@ -58,7 +61,7 @@ public void testEnglishHello() throws Exception { @Test public void testSpanishHello() throws Exception { - mockMvc.perform(get("/hello.action").locale(Locale.forLanguageTag("es"))) + this.mockMvc.perform(get("/hello.action").locale(Locale.forLanguageTag("es"))) .andExpect(status().isOk()) .andExpect(content().contentTypeCompatibleWith(MediaType.TEXT_HTML)) .andExpect(content().string("Hola Boot!")); @@ -66,7 +69,7 @@ public void testSpanishHello() throws Exception { @Test public void testAdditionalExtensions() throws Exception { - mockMvc.perform(get("/extensions.action").locale(Locale.forLanguageTag("es"))) + 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!")); diff --git a/pebble-spring/pebble-spring5/pom.xml b/pebble-spring/pebble-spring5/pom.xml index b917777e9..2633066cd 100644 --- a/pebble-spring/pebble-spring5/pom.xml +++ b/pebble-spring/pebble-spring5/pom.xml @@ -46,6 +46,11 @@ spring-webmvc provided + + org.springframework + spring-webflux + provided + diff --git a/pebble-spring/pebble-spring5/src/main/java/com/mitchellbosecke/pebble/spring/reactive/PebbleReactiveView.java b/pebble-spring/pebble-spring5/src/main/java/com/mitchellbosecke/pebble/spring/reactive/PebbleReactiveView.java new file mode 100644 index 000000000..c0809e716 --- /dev/null +++ b/pebble-spring/pebble-spring5/src/main/java/com/mitchellbosecke/pebble/spring/reactive/PebbleReactiveView.java @@ -0,0 +1,89 @@ +package com.mitchellbosecke.pebble.spring.reactive; + +import com.mitchellbosecke.pebble.PebbleEngine; +import com.mitchellbosecke.pebble.error.PebbleException; +import com.mitchellbosecke.pebble.template.PebbleTemplate; + +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 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 static java.util.Optional.ofNullable; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +public class PebbleReactiveView extends AbstractUrlBasedView { + + 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.evaluateTemplate(renderAttributes, locale, writer); + } catch (Exception ex) { + DataBufferUtils.release(dataBuffer); + return Mono.error(ex); + } + return exchange.getResponse().writeWith(Flux.just(dataBuffer)); + } + + 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-spring5/src/main/java/com/mitchellbosecke/pebble/spring/reactive/PebbleReactiveViewResolver.java b/pebble-spring/pebble-spring5/src/main/java/com/mitchellbosecke/pebble/spring/reactive/PebbleReactiveViewResolver.java new file mode 100644 index 000000000..138e7ae73 --- /dev/null +++ b/pebble-spring/pebble-spring5/src/main/java/com/mitchellbosecke/pebble/spring/reactive/PebbleReactiveViewResolver.java @@ -0,0 +1,34 @@ +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; + } + + public PebbleEngine getPebbleEngine() { + return this.pebbleEngine; + } +} diff --git a/pebble-spring/pebble-spring5/src/main/java/com/mitchellbosecke/pebble/spring/PebbleView.java b/pebble-spring/pebble-spring5/src/main/java/com/mitchellbosecke/pebble/spring/servlet/PebbleView.java similarity index 96% rename from pebble-spring/pebble-spring5/src/main/java/com/mitchellbosecke/pebble/spring/PebbleView.java rename to pebble-spring/pebble-spring5/src/main/java/com/mitchellbosecke/pebble/spring/servlet/PebbleView.java index f9eb6431a..34535f76a 100644 --- a/pebble-spring/pebble-spring5/src/main/java/com/mitchellbosecke/pebble/spring/PebbleView.java +++ b/pebble-spring/pebble-spring5/src/main/java/com/mitchellbosecke/pebble/spring/servlet/PebbleView.java @@ -4,22 +4,25 @@ * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ -package com.mitchellbosecke.pebble.spring; +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 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; + import javax.servlet.http.HttpServletRequest; import javax.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; public class PebbleView extends AbstractTemplateView { @@ -36,7 +39,7 @@ public class PebbleView extends AbstractTemplateView { *

*

* The value of this constant is - * com.mitchellbosecke.pebble.spring.PebbleView.timer. This allows + * 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. *

diff --git a/pebble-spring/pebble-spring5/src/main/java/com/mitchellbosecke/pebble/spring/PebbleViewResolver.java b/pebble-spring/pebble-spring5/src/main/java/com/mitchellbosecke/pebble/spring/servlet/PebbleViewResolver.java similarity index 96% rename from pebble-spring/pebble-spring5/src/main/java/com/mitchellbosecke/pebble/spring/PebbleViewResolver.java rename to pebble-spring/pebble-spring5/src/main/java/com/mitchellbosecke/pebble/spring/servlet/PebbleViewResolver.java index 96f3b7f12..0b7a66f44 100644 --- a/pebble-spring/pebble-spring5/src/main/java/com/mitchellbosecke/pebble/spring/PebbleViewResolver.java +++ b/pebble-spring/pebble-spring5/src/main/java/com/mitchellbosecke/pebble/spring/servlet/PebbleViewResolver.java @@ -4,10 +4,11 @@ * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ -package com.mitchellbosecke.pebble.spring; +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.beans.factory.annotation.Required; import org.springframework.web.servlet.view.AbstractTemplateViewResolver; diff --git a/pebble-spring/pebble-spring5/src/test/java/com/mitchellbosecke/pebble/spring/config/MVCConfig.java b/pebble-spring/pebble-spring5/src/test/java/com/mitchellbosecke/pebble/spring/config/MVCConfig.java index e6d12f311..7d0615e63 100644 --- a/pebble-spring/pebble-spring5/src/test/java/com/mitchellbosecke/pebble/spring/config/MVCConfig.java +++ b/pebble-spring/pebble-spring5/src/test/java/com/mitchellbosecke/pebble/spring/config/MVCConfig.java @@ -9,9 +9,10 @@ import com.mitchellbosecke.pebble.PebbleEngine; import com.mitchellbosecke.pebble.loader.ClasspathLoader; import com.mitchellbosecke.pebble.loader.Loader; -import com.mitchellbosecke.pebble.spring.PebbleViewResolver; 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; diff --git a/pebble/src/main/java/com/mitchellbosecke/pebble/loader/ClasspathLoader.java b/pebble/src/main/java/com/mitchellbosecke/pebble/loader/ClasspathLoader.java index 55021d900..91c363313 100644 --- a/pebble/src/main/java/com/mitchellbosecke/pebble/loader/ClasspathLoader.java +++ b/pebble/src/main/java/com/mitchellbosecke/pebble/loader/ClasspathLoader.java @@ -10,13 +10,15 @@ import com.mitchellbosecke.pebble.error.LoaderException; import com.mitchellbosecke.pebble.utils.PathUtils; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import java.io.BufferedReader; import java.io.InputStream; import java.io.InputStreamReader; import java.io.Reader; import java.io.UnsupportedEncodingException; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; /** * Uses a classloader to find templates located on the classpath. @@ -47,7 +49,26 @@ public ClasspathLoader() { @Override public Reader getReader(String templateName) { + String location = this.getLocation(templateName); + + logger.debug("Looking for template in {}.", location); + + // perform the lookup + InputStream is = this.rcl.getResourceAsStream(location); + if (is == null) { + throw new LoaderException(null, "Could not find template \"" + location + "\""); + } + + try { + return new BufferedReader(new InputStreamReader(is, this.charset)); + } catch (UnsupportedEncodingException e) { + } + + return null; + } + + private String getLocation(String templateName) { // append the prefix and make sure prefix ends with a separator character StringBuilder path = new StringBuilder(128); if (this.getPrefix() != null) { @@ -64,22 +85,7 @@ public Reader getReader(String templateName) { if (this.getSuffix() != null) { path.append(this.getSuffix()); } - String location = path.toString(); - logger.debug("Looking for template in {}.", location); - - // perform the lookup - InputStream is = this.rcl.getResourceAsStream(location); - - if (is == null) { - throw new LoaderException(null, "Could not find template \"" + location + "\""); - } - - try { - return new BufferedReader(new InputStreamReader(is, this.charset)); - } catch (UnsupportedEncodingException e) { - } - - return null; + return path.toString(); } public String getSuffix() { @@ -118,4 +124,9 @@ public String resolveRelativePath(String relativePath, String anchorPath) { public String createCacheKey(String templateName) { return templateName; } + + @Override + public boolean resourceExists(String templateName) { + return this.rcl.getResource(this.getLocation(templateName)) != null; + } } diff --git a/pebble/src/main/java/com/mitchellbosecke/pebble/loader/DelegatingLoader.java b/pebble/src/main/java/com/mitchellbosecke/pebble/loader/DelegatingLoader.java index 9e225bbe7..35385c151 100644 --- a/pebble/src/main/java/com/mitchellbosecke/pebble/loader/DelegatingLoader.java +++ b/pebble/src/main/java/com/mitchellbosecke/pebble/loader/DelegatingLoader.java @@ -9,6 +9,7 @@ package com.mitchellbosecke.pebble.loader; import com.mitchellbosecke.pebble.error.LoaderException; + import java.io.Reader; import java.util.ArrayList; import java.util.Collections; @@ -89,7 +90,7 @@ public String getSuffix() { @Override public void setSuffix(String suffix) { this.suffix = suffix; - for (Loader loader: this.loaders) { + for (Loader loader : this.loaders) { loader.setSuffix(suffix); } } @@ -101,7 +102,7 @@ public String getPrefix() { @Override public void setPrefix(String prefix) { this.prefix = prefix; - for (Loader loader: this.loaders) { + for (Loader loader : this.loaders) { loader.setPrefix(prefix); } } @@ -113,7 +114,7 @@ public String getCharset() { @Override public void setCharset(String charset) { this.charset = charset; - for (Loader loader: this.loaders) { + for (Loader loader : this.loaders) { loader.setCharset(charset); } } @@ -142,4 +143,14 @@ public DelegatingLoaderCacheKey createCacheKey(String templateName) { return new DelegatingLoaderCacheKey(keys, templateName); } + + @Override + public boolean resourceExists(String templateName) { + for (Loader loader : this.loaders) { + if (loader.resourceExists(templateName)) { + return true; + } + } + return false; + } } diff --git a/pebble/src/main/java/com/mitchellbosecke/pebble/loader/FileLoader.java b/pebble/src/main/java/com/mitchellbosecke/pebble/loader/FileLoader.java index dd784f3cd..187e23137 100644 --- a/pebble/src/main/java/com/mitchellbosecke/pebble/loader/FileLoader.java +++ b/pebble/src/main/java/com/mitchellbosecke/pebble/loader/FileLoader.java @@ -10,6 +10,10 @@ import com.mitchellbosecke.pebble.error.LoaderException; import com.mitchellbosecke.pebble.utils.PathUtils; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; @@ -18,8 +22,6 @@ import java.io.InputStreamReader; import java.io.Reader; import java.io.UnsupportedEncodingException; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; /** * This loader searches for a file located anywhere on the filesystem. It uses java.io.File to @@ -39,7 +41,30 @@ public class FileLoader implements Loader { @Override public Reader getReader(String templateName) { + // try to load File + InputStream is = null; + File file = this.getFile(templateName); + if (file.exists() && file.isFile()) { + try { + is = new FileInputStream(file); + } catch (FileNotFoundException e) { + } + } + + if (is == null) { + throw new LoaderException(null, + "Could not find template \"" + templateName + "\""); + } + try { + return new BufferedReader(new InputStreamReader(is, this.charset)); + } catch (UnsupportedEncodingException e) { + } + + return null; + } + + private File getFile(String templateName) { // add the prefix and ensure the prefix ends with a separator character StringBuilder path = new StringBuilder(); if (this.getPrefix() != null) { @@ -71,26 +96,7 @@ public Reader getReader(String templateName) { } // try to load File - InputStream is = null; - File file = new File(path.toString(), templateName); - if (file.exists() && file.isFile()) { - try { - is = new FileInputStream(file); - } catch (FileNotFoundException e) { - } - } - - if (is == null) { - throw new LoaderException(null, - "Could not find template \"" + path.toString() + templateName + "\""); - } - - try { - return new BufferedReader(new InputStreamReader(is, this.charset)); - } catch (UnsupportedEncodingException e) { - } - - return null; + return new File(path.toString(), templateName); } public String getSuffix() { @@ -129,4 +135,9 @@ public String resolveRelativePath(String relativePath, String anchorPath) { public String createCacheKey(String templateName) { return templateName; } + + @Override + public boolean resourceExists(String templateName) { + return this.getFile(templateName).exists(); + } } diff --git a/pebble/src/main/java/com/mitchellbosecke/pebble/loader/Loader.java b/pebble/src/main/java/com/mitchellbosecke/pebble/loader/Loader.java index c8901ddd2..cc7bf0682 100644 --- a/pebble/src/main/java/com/mitchellbosecke/pebble/loader/Loader.java +++ b/pebble/src/main/java/com/mitchellbosecke/pebble/loader/Loader.java @@ -9,6 +9,7 @@ package com.mitchellbosecke.pebble.loader; import com.mitchellbosecke.pebble.PebbleEngine; + import java.io.Reader; /** @@ -101,4 +102,5 @@ public interface Loader { */ T createCacheKey(String templateName); + boolean resourceExists(String templateName); } \ No newline at end of file diff --git a/pebble/src/main/java/com/mitchellbosecke/pebble/loader/ServletLoader.java b/pebble/src/main/java/com/mitchellbosecke/pebble/loader/ServletLoader.java index 13e3bb00e..e6268fde4 100644 --- a/pebble/src/main/java/com/mitchellbosecke/pebble/loader/ServletLoader.java +++ b/pebble/src/main/java/com/mitchellbosecke/pebble/loader/ServletLoader.java @@ -2,14 +2,18 @@ import com.mitchellbosecke.pebble.error.LoaderException; import com.mitchellbosecke.pebble.utils.PathUtils; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import java.io.BufferedReader; import java.io.InputStream; import java.io.InputStreamReader; import java.io.Reader; import java.io.UnsupportedEncodingException; +import java.net.MalformedURLException; + import javax.servlet.ServletContext; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; /** * Loader that uses a servlet context to find templates. @@ -41,34 +45,18 @@ public Reader getReader(String templateName) { Reader reader = null; InputStream is = null; + String location = this.getLocation(templateName); - // Add the prefix and make sure that it ends with a separator character - StringBuilder path = new StringBuilder(128); - if (getPrefix() != null) { - - path.append(getPrefix()); - - // we do NOT use OS dependent separators here; getResourceAsStream - // explicitly requires forward slashes. - if (!getPrefix().endsWith(Character.toString(expectedSeparator))) { - path.append(expectedSeparator); - } - } - path.append(templateName); - if (getSuffix() != null) { - path.append(getSuffix()); - } - String location = path.toString(); logger.debug("Looking for template in {}.", location); - is = context.getResourceAsStream(location); + is = this.context.getResourceAsStream(location); if (is == null) { throw new LoaderException(null, "Could not find template \"" + location + "\""); } try { - isr = new InputStreamReader(is, charset); + isr = new InputStreamReader(is, this.charset); reader = new BufferedReader(isr); } catch (UnsupportedEncodingException e) { } @@ -76,8 +64,28 @@ public Reader getReader(String templateName) { return reader; } + private String getLocation(String templateName) { + // Add the prefix and make sure that it ends with a separator character + StringBuilder path = new StringBuilder(128); + if (this.getPrefix() != null) { + + path.append(this.getPrefix()); + + // we do NOT use OS dependent separators here; getResourceAsStream + // explicitly requires forward slashes. + if (!this.getPrefix().endsWith(Character.toString(this.expectedSeparator))) { + path.append(this.expectedSeparator); + } + } + path.append(templateName); + if (this.getSuffix() != null) { + path.append(this.getSuffix()); + } + return path.toString(); + } + public String getSuffix() { - return suffix; + return this.suffix; } @Override @@ -86,7 +94,7 @@ public void setSuffix(String suffix) { } public String getPrefix() { - return prefix; + return this.prefix; } @Override @@ -95,7 +103,7 @@ public void setPrefix(String prefix) { } public String getCharset() { - return charset; + return this.charset; } @Override @@ -105,7 +113,7 @@ public void setCharset(String charset) { @Override public String resolveRelativePath(String relativePath, String anchorPath) { - return PathUtils.resolveRelativePath(relativePath, anchorPath, expectedSeparator); + return PathUtils.resolveRelativePath(relativePath, anchorPath, this.expectedSeparator); } @Override @@ -113,4 +121,12 @@ public String createCacheKey(String templateName) { return templateName; } + @Override + public boolean resourceExists(String templateName) { + try { + return this.context.getResource(this.getLocation(templateName)) != null; + } catch (MalformedURLException e) { + return false; + } + } } diff --git a/pebble/src/main/java/com/mitchellbosecke/pebble/loader/StringLoader.java b/pebble/src/main/java/com/mitchellbosecke/pebble/loader/StringLoader.java index a49c221da..921fb8d3a 100644 --- a/pebble/src/main/java/com/mitchellbosecke/pebble/loader/StringLoader.java +++ b/pebble/src/main/java/com/mitchellbosecke/pebble/loader/StringLoader.java @@ -48,4 +48,8 @@ public String createCacheKey(String templateName) { return templateName; } + @Override + public boolean resourceExists(String templateName) { + return true; + } } diff --git a/pebble/src/test/java/com/mitchellbosecke/pebble/ConcurrencyTest.java b/pebble/src/test/java/com/mitchellbosecke/pebble/ConcurrencyTest.java index 5f597ac0a..f41c5f9b6 100644 --- a/pebble/src/test/java/com/mitchellbosecke/pebble/ConcurrencyTest.java +++ b/pebble/src/test/java/com/mitchellbosecke/pebble/ConcurrencyTest.java @@ -8,14 +8,14 @@ */ package com.mitchellbosecke.pebble; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.fail; - import com.mitchellbosecke.pebble.error.LoaderException; import com.mitchellbosecke.pebble.error.PebbleException; import com.mitchellbosecke.pebble.loader.Loader; import com.mitchellbosecke.pebble.loader.StringLoader; import com.mitchellbosecke.pebble.template.PebbleTemplate; + +import org.junit.Test; + import java.io.IOException; import java.io.Reader; import java.io.StringReader; @@ -29,7 +29,9 @@ import java.util.concurrent.Executors; import java.util.concurrent.Semaphore; import java.util.concurrent.atomic.AtomicInteger; -import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; public class ConcurrencyTest { @@ -324,6 +326,10 @@ public String createCacheKey(String templateName) { return templateName; } + @Override + public boolean resourceExists(String templateName) { + return true; + } }; PebbleEngine engine = new PebbleEngine.Builder().loader(loader).strictVariables(false).build();