boot automatic configuration freemarker lack freemarkerVariables

Comment From: wilkinsona

What do you mean by "freemarkerVariables"? Can you please show what you're currently having to configure that the auto-configuration should be able to configure for you?

Comment From: ohalo

in freemarker automatic configuration , it lack of freemarkerVariables configuration

Comment From: ohalo

it lack of freemarkerVariables configuration

Comment From: ohalo

  freemarker:
    settings:
      object_wrapper: com.control.back.halo.basic.utils.FreemarkerObjectWrapper
      datetime_format: yyyy-MM-dd HH:mm:ss
      date_format: yyyy-MM-dd
      time_format: HH:mm:ss

Comment From: ohalo

@ConfigurationProperties(prefix = "spring.freemarker")
public class FreeMarkerProperties extends AbstractTemplateViewResolverProperties {   

it is no freemarkerVariables

Comment From: ohalo

but FreeMarkerConfigurationFactory has freemarkerVariables,i need it

Comment From: ohalo

I need a solution

Comment From: wilkinsona

Ok, so you'd like to be able to set the freemarkerVariables property on either the auto-configured FreeMarkerConfigurationFactoryBean or on the auto-configured FreeMarkerConfigurer bean, correct? Both are subclasses of FreeMarkerConfigurationFactory.

What variables do you want to be able to set? Can you please provide some specific examples? Please note that values other than maps, lists and scalar values are not well-suited to being set via application.properties.

Comment From: ohalo

shiro

shiro:
  realm: com.control.back.halo.authorization.shiro.realm.UserRealm
  login-url: /login.html
  success-url: /index.html
  unauthorized-url: /
   #filter chian
  filterChainDefinitions:  
    "/login": anon
    "/login/**": anon
    "/logout": logout
    "/css/**": anon
    "/fonts/**": anon
    "/img/**": anon
    "/js/**": anon
    "/**": authc

i use shiro ,and definition a ShiroProperties,i hope freemarkerVariables use map definition . yes?

@ConfigurationProperties(prefix = "shiro")
public class ShiroProperties {
    /**
     * Custom Realm 
     */
    private String realm;
    /**
     * URL of login
     */
    private String loginUrl;
    /**
     * URL of success
     */
    private String successUrl;
    /**
     * URL of unauthorized
     */
    private String unauthorizedUrl;
    /**
     * filter chain
     */
    private Map<String, String> filterChainDefinitions;

    /**
     * 
     */
    private String ehacheConfig;

Comment From: ohalo

According to the yaml's document,it can configure the map ,Why not do it

Comment From: wilkinsona

@ohalo Please take the time to format your comments as it will make them much easier to read. Also, please keep this issue focused on FreeMarker. Your latest example appears to be solely about Shiro. I still need some examples of the freemarker variables that you are trying to set.

Comment From: ohalo


@ConfigurationProperties(prefix = "freemarker")
public class FreeMarkerProperties extends AbstractTemplateViewResolverProperties {

    public static final String DEFAULT_TEMPLATE_LOADER_PATH = "classpath:/templates/";

    public static final String DEFAULT_PREFIX = "";

    public static final String DEFAULT_SUFFIX = ".ftl";

    /**
     * Well-known FreeMarker keys which will be passed to FreeMarker's Configuration.
     */
    private Map<String, String> settings = new HashMap<String, String>();

        private Map<String, String> freemarkerVariables = new HashMap<String, String>();

and configuration is

#freemarker
freemarker:
  settings:
    object_wrapper: com.control.back.halo.basic.utils.FreemarkerObjectWrapper
    datetime_format: yyyy-MM-dd HH:mm:ss
    date_format: yyyy-MM-dd
    time_format: HH:mm:ss    
  freemarker-variables:
    base: http://127.0.0.1:80

Comment From: ohalo

in FreeMarkerAutoConfiguration ,and join factory.setFreemarkerVariables(this.properties.getFreemarkerVariables());

``` protected static class FreeMarkerConfiguration {

    @Autowired
    protected FreeMarkerProperties properties;

    protected void applyProperties(FreeMarkerConfigurationFactory factory) {
        factory.setTemplateLoaderPaths(this.properties.getTemplateLoaderPath());
        factory.setPreferFileSystemAccess(this.properties.isPreferFileSystemAccess());
        factory.setDefaultEncoding(this.properties.getCharsetName());
        Properties settings = new Properties();
        settings.putAll(this.properties.getSettings());
        factory.setFreemarkerSettings(settings);
        factory.setFreemarkerVariables(this.properties.getFreemarkerVariables());
    }

}

**Comment From: ohalo**

Is that ok?sorry my english

**Comment From: wilkinsona**

The formatting is much better, thank you. Unfortunately, it's not really what it was looking for. 

You've shown how what you're asking for could be implemented in Spring Boot and I already know how to do that. What I don't know is what _values_ you want to set in `freemarkerVariables`. You've shown one example which is setting `base` to `http://127.0.0.1:80`. What other variables would you like to be able to set and what values might they have? I am asking because the values in `freemarkerVariables` can be anything (it's a Map<String, Object>) and `application.properties` or `application.yml` aren't a good way to set some types of `Object`. As I said above, it's only suited to setting maps, lists and scalar values.

If you want to configure something that's more complex, or you just want to do this without an enhancement to Spring Boot, then registering a `BeanPostProcessor` for the `FreeMarkerConfigurer` or `FreeMarkerConfigurationFactoryBean` may be what you need to do.

**Comment From: ohalo**

ok , i understand you,because application.properties or application.yml aren't a good way to set some types of Object,thank you 

**Comment From: wilkinsona**

> because application.properties or application.yml aren't a good way to set some types of Object

Yes, that's right. Can you please share some examples of the values that you would like to set?

**Comment From: ohalo**

i want set String values ,not Object value,like: url(static resource url:img,js,css;and other system's url)

**Comment From: philwebb**

@ohalo It's still not clear what properties you're setting and what you expect to happen. I think the best way to proceed is if you could provide a small focused sample application that shows the problem.

**Comment From: ohalo**

@druidDataSource com.alibaba.druid.pool.DruidDataSource @driverClass com.mysql.jdbc.Driver

freemarker

freemarker: settings: object_wrapper: com.control.back.halo.basic.utils.FreemarkerObjectWrapper datetime_format: yyyy-MM-dd HH:mm:ss date_format: yyyy-MM-dd time_format: HH:mm:ss template_update_delay : 0 freemarker-variables: base: http://127.0.0.1:8080 siteName: speedbuy druidDataSource: druidDataSource driverClass: driverClass




**Comment From: philwebb**

I'm not convinced that we should support this out of the box. AFAICT most of our other template engines don't have properties that we set. Mustache does have a `MustacheEnvironmentCollector` which allows Spring Environment variables to be accessed, but there is no `variables` section in its `@ConfigurationProperties`.

**Comment From: philwebb**

We feel like the properties is a little restrictive and a customizer might be a better option. We can use `FreemarkerConfigurationFactory.setFreemarkerVariables()`.

**Comment From: bclozel**

We could also consider a more general solution, for all `ViewResolvers` extending `UrlBasedViewResolver`. This class has a `UrlBasedViewResolver.setAttributesMap(Map<String, Object)` method that adds those attributes to all views resolved ([see javadoc](https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/servlet/view/UrlBasedViewResolver.html#setAttributesMap-java.util.Map-)).

Those attributes will be available in the model when the template is being rendered.

**Comment From: spt-developer**

I want the customizer. Now I implement like this (no need to custom freemarker).
```java
public class FreeMarkerVariablesInterceptor extends HandlerInterceptorAdapter {

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) {

        modelAndView.addObject("key", new Value());
    }
}

Comment From: ohalo

thanks

Comment From: moqi2011

A solution:

spring:
  freemarker:
    settings:
      auto_import: "/spring.ftl as spring , /fragment/global.ftl as _global_"

global.ftl:

<#assign static_server = "http://127.0.0.1">

Use global variable:

${_global_.static_server}

Comment From: moqi2011

Or put global variable to ServletContext

public class HomeApp extends SpringBootServletInitializer {

    @Override
    protected WebApplicationContext createRootApplicationContext
            (ServletContext servletContext) {
        WebApplicationContext ctx = super.createRootApplicationContext(servletContext);
        HomeAppConfig conf = ctx.getBean(HomeAppConfig.class);
        servletContext.setAttribute("_config_",conf);
        return ctx;
    }

}

Comment From: ohalo

@moqi2011 thanks

Comment From: wo8335224


@Configuration
public class FreemarkerConfiguration extends FreeMarkerAutoConfiguration.FreeMarkerWebConfiguration {
    @Value("${ctx.static}")
    private String ctx;
    @Value("${ctx.custom}")
    private Integer custom;

    @Override
    public FreeMarkerConfigurer freeMarkerConfigurer() {
        FreeMarkerConfigurer configurer = super.freeMarkerConfigurer();

        Map<String, Object> sharedVariables = new HashMap<>();
        if (custom != null && custom == 1) {
            sharedVariables.put("sctx", ctx);
        }
        configurer.setFreemarkerVariables(sharedVariables);
        return configurer;
    }
}

workd for me

@ohalo

Comment From: bianjp

Spring Boot 2.0 breaks the solution provided by @wo8335224, as FreeMarkerWebConfiguration is replaced by FreeMarkerServletWebConfiguration, which is unfortunately package-private and thus cannot be subclassed.

A currently working solution is to configure freemarker.template.Configuration bean:

@Configuration
public class FreemarkerConfig {
    public FreemarkerConfig(freemarker.template.Configuration configuration) throws TemplateModelException {
        configuration.setSharedVariable("name", "whatever type of value");
    }
}

Internally FreeMarkerConfigurer#setFreemarkerVariables delegates its work to freemarker.template.Configuration#setAllSharedVariables.

Comment From: satahippy

Hey @bianjp !

Yes, you can do this, but you'll lose some auto-configuration. So better use BeanPostProcessor.

@Component
class FreeMarkerPostProcessor : BeanPostProcessor {
    override fun postProcessBeforeInitialization(bean: Any, beanName: String): Any {
        if (bean is FreeMarkerConfigurer) {
            bean.setFreemarkerVariables(mapOf(
                    "globalVar" to "globalVarValue"
            ))
        }
        return bean
    }
}

Comment From: kaliatech

Thanks @satahippy. Java version:

@Configuration
public class CustomFreeMarkerConfig implements BeanPostProcessor {

    Object sharedWithAllFreeMarkerTemplatesObj = new Object();

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName)
    throws BeansException {
        if (bean instanceof FreeMarkerConfigurer) {
            FreeMarkerConfigurer configurer = (FreeMarkerConfigurer) bean;
            Map<String, Object> sharedVariables = new HashMap<>();
            sharedVariables.put("globalVar", sharedWithAllFreeMarkerTemplatesObj);
            configurer.setFreemarkerVariables(sharedVariables);
        }
        return bean;
    }
}

[edit: my original comment confused Before/After Initialization)

I feel like there should be some easier, built-in way, of doing this, but I haven't found it yet. Related question on StackOverflow: https://stackoverflow.com/questions/52730368/how-to-make-globally-shared-objects-available-to-freemarker-templates-in-spring

Comment From: snicoll

To be able to use FreeMarkerConfigurationFactory in a customizer, we need a framework change. I've opened https://github.com/spring-projects/spring-framework/issues/23054

Comment From: mauromol

+1 for this. Today I encountered the same need.

Comment From: wilkinsona

I don't think this needs to be blocked on the Framework issue. While allowing the variables to be amended could be useful, being able to set them (overwriting anything already set) would still be a step forwards. As and when there's a Framework API that allows the variables to be amended, that would be an additional benefit of a customizer callback in Boot.