6 changed files with 923 additions and 2 deletions
@ -0,0 +1,355 @@ |
|||||||
|
<#ftl output_format="HTML" strip_whitespace=true> |
||||||
|
<#-- |
||||||
|
* spring.ftl |
||||||
|
* |
||||||
|
* This file consists of a collection of FreeMarker macros aimed at easing |
||||||
|
* some of the common requirements of web applications - in particular |
||||||
|
* handling of forms. |
||||||
|
* |
||||||
|
* Spring's FreeMarker support will automatically make this file and therefore |
||||||
|
* all macros within it available to any application using Spring's |
||||||
|
* FreeMarkerConfigurer. |
||||||
|
* |
||||||
|
* To take advantage of these macros, the "exposeSpringMacroHelpers" property |
||||||
|
* of the FreeMarker class needs to be set to "true". This will expose a |
||||||
|
* RequestContext under the name "springMacroRequestContext", as needed by |
||||||
|
* the macros in this library. |
||||||
|
* |
||||||
|
* @author Darren Davison |
||||||
|
* @author Juergen Hoeller |
||||||
|
* @author Issam El-atif |
||||||
|
* @since 5.2 |
||||||
|
--> |
||||||
|
|
||||||
|
<#-- |
||||||
|
* message |
||||||
|
* |
||||||
|
* Macro to translate a message code into a message. |
||||||
|
--> |
||||||
|
<#macro message code>${springMacroRequestContext.getMessage(code)?no_esc}</#macro> |
||||||
|
|
||||||
|
<#-- |
||||||
|
* messageText |
||||||
|
* |
||||||
|
* Macro to translate a message code into a message, |
||||||
|
* using the given default text if no message found. |
||||||
|
--> |
||||||
|
<#macro messageText code, text>${springMacroRequestContext.getMessage(code, text)?no_esc}</#macro> |
||||||
|
|
||||||
|
<#-- |
||||||
|
* messageArgs |
||||||
|
* |
||||||
|
* Macro to translate a message code with arguments into a message. |
||||||
|
--> |
||||||
|
<#macro messageArgs code, args>${springMacroRequestContext.getMessage(code, args)?no_esc}</#macro> |
||||||
|
|
||||||
|
<#-- |
||||||
|
* messageArgsText |
||||||
|
* |
||||||
|
* Macro to translate a message code with arguments into a message, |
||||||
|
* using the given default text if no message found. |
||||||
|
--> |
||||||
|
<#macro messageArgsText code, args, text>${springMacroRequestContext.getMessage(code, args, text)?no_esc}</#macro> |
||||||
|
|
||||||
|
<#-- |
||||||
|
* url |
||||||
|
* |
||||||
|
* Takes a relative URL and makes it absolute from the server root by |
||||||
|
* adding the context root for the web application. |
||||||
|
--> |
||||||
|
<#macro url relativeUrl extra...><#if extra?? && extra?size!=0>${springMacroRequestContext.getContextUrl(relativeUrl,extra)?no_esc}<#else>${springMacroRequestContext.getContextUrl(relativeUrl)?no_esc}</#if></#macro> |
||||||
|
|
||||||
|
<#-- |
||||||
|
* bind |
||||||
|
* |
||||||
|
* Exposes a BindStatus object for the given bind path, which can be |
||||||
|
* a bean (e.g. "person") to get global errors, or a bean property |
||||||
|
* (e.g. "person.name") to get field errors. Can be called multiple times |
||||||
|
* within a form to bind to multiple command objects and/or field names. |
||||||
|
* |
||||||
|
* This macro will participate in the default HTML escape setting for the given |
||||||
|
* RequestContext. This can be customized by calling "setDefaultHtmlEscape" |
||||||
|
* on the "springMacroRequestContext" context variable, or via the |
||||||
|
* "defaultHtmlEscape" context-param in web.xml (same as for the JSP bind tag). |
||||||
|
* Also regards a "htmlEscape" variable in the namespace of this library. |
||||||
|
* |
||||||
|
* Producing no output, the following context variable will be available |
||||||
|
* each time this macro is referenced (assuming you import this library in |
||||||
|
* your templates with the namespace 'spring'): |
||||||
|
* |
||||||
|
* spring.status : a BindStatus instance holding the command object name, |
||||||
|
* expression, value, and error messages and codes for the path supplied |
||||||
|
* |
||||||
|
* @param path the path (string value) of the value required to bind to. |
||||||
|
* Spring defaults to a command name of "command" but this can be |
||||||
|
* overridden by user configuration. |
||||||
|
--> |
||||||
|
<#macro bind path> |
||||||
|
<#if htmlEscape?exists> |
||||||
|
<#assign status = springMacroRequestContext.getBindStatus(path, htmlEscape)> |
||||||
|
<#else> |
||||||
|
<#assign status = springMacroRequestContext.getBindStatus(path)> |
||||||
|
</#if> |
||||||
|
<#-- assign a temporary value, forcing a string representation for any |
||||||
|
kind of variable. This temp value is only used in this macro lib --> |
||||||
|
<#if status.value?exists && status.value?is_boolean> |
||||||
|
<#assign stringStatusValue=status.value?string> |
||||||
|
<#else> |
||||||
|
<#assign stringStatusValue=status.value?default("")> |
||||||
|
</#if> |
||||||
|
</#macro> |
||||||
|
|
||||||
|
<#-- |
||||||
|
* bindEscaped |
||||||
|
* |
||||||
|
* Similar to spring:bind, but takes an explicit HTML escape flag rather |
||||||
|
* than relying on the default HTML escape setting. |
||||||
|
--> |
||||||
|
<#macro bindEscaped path, htmlEscape> |
||||||
|
<#assign status = springMacroRequestContext.getBindStatus(path, htmlEscape)> |
||||||
|
<#-- assign a temporary value, forcing a string representation for any |
||||||
|
kind of variable. This temp value is only used in this macro lib --> |
||||||
|
<#if status.value?exists && status.value?is_boolean> |
||||||
|
<#assign stringStatusValue=status.value?string> |
||||||
|
<#else> |
||||||
|
<#assign stringStatusValue=status.value?default("")> |
||||||
|
</#if> |
||||||
|
</#macro> |
||||||
|
|
||||||
|
<#-- |
||||||
|
* formInput |
||||||
|
* |
||||||
|
* Display a form input field of type 'text' and bind it to an attribute |
||||||
|
* of a command or bean. |
||||||
|
* |
||||||
|
* @param path the name of the field to bind to |
||||||
|
* @param attributes any additional attributes for the element |
||||||
|
* (such as class or CSS styles or size) |
||||||
|
--> |
||||||
|
<#macro formInput path attributes="" fieldType="text"> |
||||||
|
<@bind path/> |
||||||
|
<input type="${fieldType}" id="${status.expression?replace('[','')?replace(']','')}" name="${status.expression}" value="<#if fieldType!="password">${stringStatusValue}</#if>" ${attributes?no_esc}<@closeTag/> |
||||||
|
</#macro> |
||||||
|
|
||||||
|
<#-- |
||||||
|
* formPasswordInput |
||||||
|
* |
||||||
|
* Display a form input field of type 'password' and bind it to an attribute |
||||||
|
* of a command or bean. No value will ever be displayed. This functionality |
||||||
|
* can also be obtained by calling the formInput macro with a 'type' parameter |
||||||
|
* of 'password'. |
||||||
|
* |
||||||
|
* @param path the name of the field to bind to |
||||||
|
* @param attributes any additional attributes for the element |
||||||
|
* (such as class or CSS styles or size) |
||||||
|
--> |
||||||
|
<#macro formPasswordInput path attributes=""> |
||||||
|
<@formInput path, attributes, "password"/> |
||||||
|
</#macro> |
||||||
|
|
||||||
|
<#-- |
||||||
|
* formHiddenInput |
||||||
|
* |
||||||
|
* Generate a form input field of type 'hidden' and bind it to an attribute |
||||||
|
* of a command or bean. This functionality can also be obtained by calling |
||||||
|
* the formInput macro with a 'type' parameter of 'hidden'. |
||||||
|
* |
||||||
|
* @param path the name of the field to bind to |
||||||
|
* @param attributes any additional attributes for the element |
||||||
|
* (such as class or CSS styles or size) |
||||||
|
--> |
||||||
|
<#macro formHiddenInput path attributes=""> |
||||||
|
<@formInput path, attributes, "hidden"/> |
||||||
|
</#macro> |
||||||
|
|
||||||
|
<#-- |
||||||
|
* formTextarea |
||||||
|
* |
||||||
|
* Display a text area and bind it to an attribute of a command or bean. |
||||||
|
* |
||||||
|
* @param path the name of the field to bind to |
||||||
|
* @param attributes any additional attributes for the element |
||||||
|
* (such as class or CSS styles or size) |
||||||
|
--> |
||||||
|
<#macro formTextarea path attributes=""> |
||||||
|
<@bind path/> |
||||||
|
<textarea id="${status.expression?replace('[','')?replace(']','')}" name="${status.expression}" ${attributes?no_esc}> |
||||||
|
${stringStatusValue}</textarea> |
||||||
|
</#macro> |
||||||
|
|
||||||
|
<#-- |
||||||
|
* formSingleSelect |
||||||
|
* |
||||||
|
* Show a selectbox (dropdown) input element allowing a single value to be chosen |
||||||
|
* from a list of options. |
||||||
|
* |
||||||
|
* @param path the name of the field to bind to |
||||||
|
* @param options a map (value=label) of all the available options |
||||||
|
* @param attributes any additional attributes for the element |
||||||
|
* (such as class or CSS styles or size) |
||||||
|
--> |
||||||
|
<#macro formSingleSelect path options attributes=""> |
||||||
|
<@bind path/> |
||||||
|
<select id="${status.expression?replace('[','')?replace(']','')}" name="${status.expression}" ${attributes?no_esc}> |
||||||
|
<#if options?is_hash> |
||||||
|
<#list options?keys as value> |
||||||
|
<option value="${value}"<@checkSelected value/>>${options[value]}</option> |
||||||
|
</#list> |
||||||
|
<#else> |
||||||
|
<#list options as value> |
||||||
|
<option value="${value}"<@checkSelected value/>>${value}</option> |
||||||
|
</#list> |
||||||
|
</#if> |
||||||
|
</select> |
||||||
|
</#macro> |
||||||
|
|
||||||
|
<#-- |
||||||
|
* formMultiSelect |
||||||
|
* |
||||||
|
* Show a listbox of options allowing the user to make 0 or more choices from |
||||||
|
* the list of options. |
||||||
|
* |
||||||
|
* @param path the name of the field to bind to |
||||||
|
* @param options a map (value=label) of all the available options |
||||||
|
* @param attributes any additional attributes for the element |
||||||
|
* (such as class or CSS styles or size) |
||||||
|
--> |
||||||
|
<#macro formMultiSelect path options attributes=""> |
||||||
|
<@bind path/> |
||||||
|
<select multiple="multiple" id="${status.expression?replace('[','')?replace(']','')}" name="${status.expression}" ${attributes?no_esc}> |
||||||
|
<#list options?keys as value> |
||||||
|
<#assign isSelected = contains(status.actualValue?default([""]), value)> |
||||||
|
<option value="${value}"<#if isSelected> selected="selected"</#if>>${options[value]}</option> |
||||||
|
</#list> |
||||||
|
</select> |
||||||
|
</#macro> |
||||||
|
|
||||||
|
<#-- |
||||||
|
* formRadioButtons |
||||||
|
* |
||||||
|
* Show radio buttons. |
||||||
|
* |
||||||
|
* @param path the name of the field to bind to |
||||||
|
* @param options a map (value=label) of all the available options |
||||||
|
* @param separator the HTML tag or other character list that should be used to |
||||||
|
* separate each option (typically ' ' or '<br>') |
||||||
|
* @param attributes any additional attributes for the element |
||||||
|
* (such as class or CSS styles or size) |
||||||
|
--> |
||||||
|
<#macro formRadioButtons path options separator attributes=""> |
||||||
|
<@bind path/> |
||||||
|
<#list options?keys as value> |
||||||
|
<#assign id="${status.expression?replace('[','')?replace(']','')}${value_index}"> |
||||||
|
<input type="radio" id="${id}" name="${status.expression}" value="${value}"<#if stringStatusValue == value> checked="checked"</#if> ${attributes?no_esc}<@closeTag/> |
||||||
|
<label for="${id}">${options[value]}</label>${separator?no_esc} |
||||||
|
</#list> |
||||||
|
</#macro> |
||||||
|
|
||||||
|
<#-- |
||||||
|
* formCheckboxes |
||||||
|
* |
||||||
|
* Show checkboxes. |
||||||
|
* |
||||||
|
* @param path the name of the field to bind to |
||||||
|
* @param options a map (value=label) of all the available options |
||||||
|
* @param separator the HTML tag or other character list that should be used to |
||||||
|
* separate each option (typically ' ' or '<br>') |
||||||
|
* @param attributes any additional attributes for the element |
||||||
|
* (such as class or CSS styles or size) |
||||||
|
--> |
||||||
|
<#macro formCheckboxes path options separator attributes=""> |
||||||
|
<@bind path/> |
||||||
|
<#list options?keys as value> |
||||||
|
<#assign id="${status.expression?replace('[','')?replace(']','')}${value_index}"> |
||||||
|
<#assign isSelected = contains(status.actualValue?default([""]), value)> |
||||||
|
<input type="checkbox" id="${id}" name="${status.expression}" value="${value}"<#if isSelected> checked="checked"</#if> ${attributes?no_esc}<@closeTag/> |
||||||
|
<label for="${id}">${options[value]}</label>${separator?no_esc} |
||||||
|
</#list> |
||||||
|
<input type="hidden" name="_${status.expression}" value="on"/> |
||||||
|
</#macro> |
||||||
|
|
||||||
|
<#-- |
||||||
|
* formCheckbox |
||||||
|
* |
||||||
|
* Show a single checkbox. |
||||||
|
* |
||||||
|
* @param path the name of the field to bind to |
||||||
|
* @param attributes any additional attributes for the element |
||||||
|
* (such as class or CSS styles or size) |
||||||
|
--> |
||||||
|
<#macro formCheckbox path attributes=""> |
||||||
|
<@bind path /> |
||||||
|
<#assign id="${status.expression?replace('[','')?replace(']','')}"> |
||||||
|
<#assign isSelected = status.value?? && status.value?string=="true"> |
||||||
|
<input type="hidden" name="_${status.expression}" value="on"/> |
||||||
|
<input type="checkbox" id="${id}" name="${status.expression}"<#if isSelected> checked="checked"</#if> ${attributes?no_esc}/> |
||||||
|
</#macro> |
||||||
|
|
||||||
|
<#-- |
||||||
|
* showErrors |
||||||
|
* |
||||||
|
* Show validation errors for the currently bound field, with |
||||||
|
* optional style attributes. |
||||||
|
* |
||||||
|
* @param separator the HTML tag or other character list that should be used to |
||||||
|
* separate each option (typically ' ' or '<br>') |
||||||
|
* @param classOrStyle either the name of a CSS class element (which is defined in |
||||||
|
* the template or an external CSS file) or an inline style. If the value passed |
||||||
|
* in here contains a colon (:) then a 'style=' attribute will be used, |
||||||
|
* otherwise a 'class=' attribute will be used. |
||||||
|
--> |
||||||
|
<#macro showErrors separator classOrStyle=""> |
||||||
|
<#list status.errorMessages as error> |
||||||
|
<#if classOrStyle == ""> |
||||||
|
<b>${error}</b> |
||||||
|
<#else> |
||||||
|
<#if classOrStyle?index_of(":") == -1><#assign attr="class"><#else><#assign attr="style"></#if> |
||||||
|
<span ${attr}="${classOrStyle}">${error}</span> |
||||||
|
</#if> |
||||||
|
<#if error_has_next>${separator?no_esc}</#if> |
||||||
|
</#list> |
||||||
|
</#macro> |
||||||
|
|
||||||
|
<#-- |
||||||
|
* checkSelected |
||||||
|
* |
||||||
|
* Check a value in a list to see if it is the currently selected value. |
||||||
|
* If so, add the 'selected="selected"' text to the output. |
||||||
|
* Handles values of numeric and string types. |
||||||
|
* This function is used internally but can be accessed by user code if required. |
||||||
|
* |
||||||
|
* @param value the current value in a list iteration |
||||||
|
--> |
||||||
|
<#macro checkSelected value> |
||||||
|
<#if stringStatusValue?is_number && stringStatusValue == value?number>selected="selected"</#if> |
||||||
|
<#if stringStatusValue?is_string && stringStatusValue == value>selected="selected"</#if> |
||||||
|
</#macro> |
||||||
|
|
||||||
|
<#-- |
||||||
|
* contains |
||||||
|
* |
||||||
|
* Macro to return true if the list contains the scalar, false if not. |
||||||
|
* Surprisingly not a FreeMarker builtin. |
||||||
|
* This function is used internally but can be accessed by user code if required. |
||||||
|
* |
||||||
|
* @param list the list to search for the item |
||||||
|
* @param item the item to search for in the list |
||||||
|
* @return true if item is found in the list, false otherwise |
||||||
|
--> |
||||||
|
<#function contains list item> |
||||||
|
<#list list as nextInList> |
||||||
|
<#if nextInList == item><#return true></#if> |
||||||
|
</#list> |
||||||
|
<#return false> |
||||||
|
</#function> |
||||||
|
|
||||||
|
<#-- |
||||||
|
* closeTag |
||||||
|
* |
||||||
|
* Simple macro to close an HTML tag that has no body with '>' or '/>', |
||||||
|
* depending on the value of a 'xhtmlCompliant' variable in the namespace |
||||||
|
* of this library. |
||||||
|
--> |
||||||
|
<#macro closeTag> |
||||||
|
<#if xhtmlCompliant?exists && xhtmlCompliant>/><#else>></#if> |
||||||
|
</#macro> |
||||||
@ -0,0 +1,128 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2002-2019 the original author or authors. |
||||||
|
* |
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
* you may not use this file except in compliance with the License. |
||||||
|
* You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software |
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
* See the License for the specific language governing permissions and |
||||||
|
* limitations under the License. |
||||||
|
*/ |
||||||
|
|
||||||
|
package org.springframework.web.reactive.result.view; |
||||||
|
|
||||||
|
import java.util.List; |
||||||
|
import java.util.Map; |
||||||
|
|
||||||
|
import org.springframework.context.support.GenericApplicationContext; |
||||||
|
import org.springframework.ui.ModelMap; |
||||||
|
import org.springframework.web.server.ServerWebExchange; |
||||||
|
import org.springframework.web.util.UriTemplate; |
||||||
|
|
||||||
|
/** |
||||||
|
* Dummy request context used for VTL and FTL macro tests. |
||||||
|
* |
||||||
|
* @author Darren Davison |
||||||
|
* @author Juergen Hoeller |
||||||
|
* @author Issam El-atif |
||||||
|
* |
||||||
|
* @see org.springframework.web.reactive.result.view.RequestContext |
||||||
|
*/ |
||||||
|
public class DummyMacroRequestContext { |
||||||
|
|
||||||
|
private final ServerWebExchange exchange; |
||||||
|
|
||||||
|
private final ModelMap model; |
||||||
|
|
||||||
|
private final GenericApplicationContext context; |
||||||
|
|
||||||
|
private Map<String, String> messageMap; |
||||||
|
|
||||||
|
private String contextPath; |
||||||
|
|
||||||
|
public DummyMacroRequestContext(ServerWebExchange exchange, ModelMap model, GenericApplicationContext context) { |
||||||
|
this.exchange = exchange; |
||||||
|
this.model = model; |
||||||
|
this.context = context; |
||||||
|
} |
||||||
|
|
||||||
|
public void setMessageMap(Map<String, String> messageMap) { |
||||||
|
this.messageMap = messageMap; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @see org.springframework.web.reactive.result.view.RequestContext#getMessage(String) |
||||||
|
*/ |
||||||
|
public String getMessage(String code) { |
||||||
|
return this.messageMap.get(code); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @see org.springframework.web.reactive.result.view.RequestContext#getMessage(String, String) |
||||||
|
*/ |
||||||
|
public String getMessage(String code, String defaultMsg) { |
||||||
|
String msg = this.messageMap.get(code); |
||||||
|
return (msg != null ? msg : defaultMsg); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @see org.springframework.web.reactive.result.view.RequestContext#getMessage(String, List) |
||||||
|
*/ |
||||||
|
public String getMessage(String code, List<?> args) { |
||||||
|
return this.messageMap.get(code) + args; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @see org.springframework.web.reactive.result.view.RequestContext#getMessage(String, List, String) |
||||||
|
*/ |
||||||
|
public String getMessage(String code, List<?> args, String defaultMsg) { |
||||||
|
String msg = this.messageMap.get(code); |
||||||
|
return (msg != null ? msg + args : defaultMsg); |
||||||
|
} |
||||||
|
|
||||||
|
public void setContextPath(String contextPath) { |
||||||
|
this.contextPath = contextPath; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @see org.springframework.web.reactive.result.view.RequestContext#getContextPath() |
||||||
|
*/ |
||||||
|
public String getContextPath() { |
||||||
|
return this.contextPath; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @see org.springframework.web.reactive.result.view.RequestContext#getContextUrl(String) |
||||||
|
*/ |
||||||
|
public String getContextUrl(String relativeUrl) { |
||||||
|
return getContextPath() + relativeUrl; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @see org.springframework.web.reactive.result.view.RequestContext#getContextUrl(String, Map) |
||||||
|
*/ |
||||||
|
public String getContextUrl(String relativeUrl, Map<String,String> params) { |
||||||
|
UriTemplate template = new UriTemplate(relativeUrl); |
||||||
|
return getContextPath() + template.expand(params).toASCIIString(); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @see org.springframework.web.reactive.result.view.RequestContext#getBindStatus(String) |
||||||
|
*/ |
||||||
|
public BindStatus getBindStatus(String path) throws IllegalStateException { |
||||||
|
return new BindStatus(new RequestContext(this.exchange, this.model, this.context), path, false); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @see org.springframework.web.reactive.result.view.RequestContext#getBindStatus(String, boolean) |
||||||
|
*/ |
||||||
|
public BindStatus getBindStatus(String path, boolean htmlEscape) throws IllegalStateException { |
||||||
|
return new BindStatus(new RequestContext(this.exchange, this.model, this.context), path, true); |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
@ -0,0 +1,120 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2002-2019 the original author or authors. |
||||||
|
* |
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
* you may not use this file except in compliance with the License. |
||||||
|
* You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software |
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
* See the License for the specific language governing permissions and |
||||||
|
* limitations under the License. |
||||||
|
*/ |
||||||
|
|
||||||
|
package org.springframework.web.reactive.result.view.freemarker; |
||||||
|
|
||||||
|
import java.io.IOException; |
||||||
|
import java.util.HashMap; |
||||||
|
import java.util.Properties; |
||||||
|
|
||||||
|
import freemarker.cache.ClassTemplateLoader; |
||||||
|
import freemarker.cache.MultiTemplateLoader; |
||||||
|
import freemarker.template.Configuration; |
||||||
|
import freemarker.template.Template; |
||||||
|
import freemarker.template.TemplateException; |
||||||
|
import org.junit.Test; |
||||||
|
|
||||||
|
import org.springframework.beans.factory.support.DefaultListableBeanFactory; |
||||||
|
import org.springframework.beans.factory.support.RootBeanDefinition; |
||||||
|
import org.springframework.core.io.ByteArrayResource; |
||||||
|
import org.springframework.core.io.DefaultResourceLoader; |
||||||
|
import org.springframework.core.io.FileSystemResource; |
||||||
|
import org.springframework.core.io.Resource; |
||||||
|
import org.springframework.core.io.ResourceLoader; |
||||||
|
import org.springframework.ui.freemarker.FreeMarkerTemplateUtils; |
||||||
|
import org.springframework.ui.freemarker.SpringTemplateLoader; |
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat; |
||||||
|
import static org.assertj.core.api.Assertions.assertThatIOException; |
||||||
|
|
||||||
|
/** |
||||||
|
* @author Juergen Hoeller |
||||||
|
* @author Issam El-atif |
||||||
|
*/ |
||||||
|
public class FreeMarkerConfigurerTests { |
||||||
|
|
||||||
|
@Test |
||||||
|
public void freeMarkerConfigurerDefaultEncoding() throws IOException, TemplateException { |
||||||
|
FreeMarkerConfigurer configurer = new FreeMarkerConfigurer(); |
||||||
|
configurer.afterPropertiesSet(); |
||||||
|
Configuration cfg = configurer.getConfiguration(); |
||||||
|
assertThat(cfg.getDefaultEncoding()).isEqualTo("UTF-8"); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void freeMarkerConfigurerWithConfigLocation() { |
||||||
|
FreeMarkerConfigurer configurer = new FreeMarkerConfigurer(); |
||||||
|
configurer.setConfigLocation(new FileSystemResource("myprops.properties")); |
||||||
|
Properties props = new Properties(); |
||||||
|
props.setProperty("myprop", "/mydir"); |
||||||
|
configurer.setFreemarkerSettings(props); |
||||||
|
assertThatIOException().isThrownBy( |
||||||
|
configurer::afterPropertiesSet); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void freeMarkerConfigurerWithResourceLoaderPath() throws Exception { |
||||||
|
FreeMarkerConfigurer configurer = new FreeMarkerConfigurer(); |
||||||
|
configurer.setTemplateLoaderPath("file:/mydir"); |
||||||
|
configurer.afterPropertiesSet(); |
||||||
|
Configuration cfg = configurer.getConfiguration(); |
||||||
|
assertThat(cfg.getTemplateLoader()).isInstanceOf(MultiTemplateLoader.class); |
||||||
|
MultiTemplateLoader multiTemplateLoader = (MultiTemplateLoader)cfg.getTemplateLoader(); |
||||||
|
assertThat(multiTemplateLoader.getTemplateLoader(0)).isInstanceOf(SpringTemplateLoader.class); |
||||||
|
assertThat(multiTemplateLoader.getTemplateLoader(1)).isInstanceOf(ClassTemplateLoader.class); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
@SuppressWarnings("rawtypes") |
||||||
|
public void freeMarkerConfigurerWithNonFileResourceLoaderPath() throws Exception { |
||||||
|
FreeMarkerConfigurer configurer = new FreeMarkerConfigurer(); |
||||||
|
configurer.setTemplateLoaderPath("file:/mydir"); |
||||||
|
Properties settings = new Properties(); |
||||||
|
settings.setProperty("localized_lookup", "false"); |
||||||
|
configurer.setFreemarkerSettings(settings); |
||||||
|
configurer.setResourceLoader(new ResourceLoader() { |
||||||
|
@Override |
||||||
|
public Resource getResource(String location) { |
||||||
|
if (!("file:/mydir".equals(location) || "file:/mydir/test".equals(location))) { |
||||||
|
throw new IllegalArgumentException(location); |
||||||
|
} |
||||||
|
return new ByteArrayResource("test".getBytes(), "test"); |
||||||
|
} |
||||||
|
@Override |
||||||
|
public ClassLoader getClassLoader() { |
||||||
|
return getClass().getClassLoader(); |
||||||
|
} |
||||||
|
}); |
||||||
|
configurer.afterPropertiesSet(); |
||||||
|
assertThat(configurer.getConfiguration()).isInstanceOf(Configuration.class); |
||||||
|
Configuration fc = configurer.getConfiguration(); |
||||||
|
Template ft = fc.getTemplate("test"); |
||||||
|
assertThat(FreeMarkerTemplateUtils.processTemplateIntoString(ft, new HashMap())).isEqualTo("test"); |
||||||
|
} |
||||||
|
|
||||||
|
@Test // SPR-12448
|
||||||
|
public void freeMarkerConfigurationAsBean() { |
||||||
|
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(); |
||||||
|
RootBeanDefinition loaderDef = new RootBeanDefinition(SpringTemplateLoader.class); |
||||||
|
loaderDef.getConstructorArgumentValues().addGenericArgumentValue(new DefaultResourceLoader()); |
||||||
|
loaderDef.getConstructorArgumentValues().addGenericArgumentValue("/freemarker"); |
||||||
|
RootBeanDefinition configDef = new RootBeanDefinition(Configuration.class); |
||||||
|
configDef.getPropertyValues().add("templateLoader", loaderDef); |
||||||
|
beanFactory.registerBeanDefinition("freeMarkerConfig", configDef); |
||||||
|
beanFactory.getBean(Configuration.class); |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
@ -0,0 +1,238 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2002-2019 the original author or authors. |
||||||
|
* |
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
* you may not use this file except in compliance with the License. |
||||||
|
* You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software |
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
* See the License for the specific language governing permissions and |
||||||
|
* limitations under the License. |
||||||
|
*/ |
||||||
|
|
||||||
|
package org.springframework.web.reactive.result.view.freemarker; |
||||||
|
|
||||||
|
import java.io.FileWriter; |
||||||
|
import java.io.InputStreamReader; |
||||||
|
import java.util.HashMap; |
||||||
|
import java.util.Map; |
||||||
|
|
||||||
|
import freemarker.template.Configuration; |
||||||
|
import org.junit.Before; |
||||||
|
import org.junit.Test; |
||||||
|
|
||||||
|
import org.springframework.context.support.GenericApplicationContext; |
||||||
|
import org.springframework.core.io.ClassPathResource; |
||||||
|
import org.springframework.core.io.FileSystemResource; |
||||||
|
import org.springframework.mock.http.server.reactive.test.MockServerHttpRequest; |
||||||
|
import org.springframework.mock.web.test.server.MockServerWebExchange; |
||||||
|
import org.springframework.tests.sample.beans.TestBean; |
||||||
|
import org.springframework.ui.ExtendedModelMap; |
||||||
|
import org.springframework.ui.ModelMap; |
||||||
|
import org.springframework.util.FileCopyUtils; |
||||||
|
import org.springframework.util.StringUtils; |
||||||
|
import org.springframework.web.reactive.result.view.DummyMacroRequestContext; |
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat; |
||||||
|
|
||||||
|
/** |
||||||
|
* @author Darren Davison |
||||||
|
* @author Juergen Hoeller |
||||||
|
* @author Issam El-atif |
||||||
|
*/ |
||||||
|
public class FreeMarkerMacroTests { |
||||||
|
|
||||||
|
private MockServerWebExchange exchange; |
||||||
|
|
||||||
|
private Configuration freeMarkerConfig; |
||||||
|
|
||||||
|
@Before |
||||||
|
public void setUp() throws Exception { |
||||||
|
this.exchange = MockServerWebExchange.from(MockServerHttpRequest.get("/path")); |
||||||
|
FreeMarkerConfigurer configurer = new FreeMarkerConfigurer(); |
||||||
|
configurer.setTemplateLoaderPaths("classpath:/", "file://" + System.getProperty("java.io.tmpdir")); |
||||||
|
this.freeMarkerConfig = configurer.createConfiguration(); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void testName() throws Exception { |
||||||
|
assertThat(getMacroOutput("NAME")).isEqualTo("Darren"); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void testAge() throws Exception { |
||||||
|
assertThat(getMacroOutput("AGE")).isEqualTo("99"); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void testMessage() throws Exception { |
||||||
|
assertThat(getMacroOutput("MESSAGE")).isEqualTo("Howdy Mundo"); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void testDefaultMessage() throws Exception { |
||||||
|
assertThat(getMacroOutput("DEFAULTMESSAGE")).isEqualTo("hi planet"); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void testMessageArgs() throws Exception { |
||||||
|
assertThat(getMacroOutput("MESSAGEARGS")).isEqualTo("Howdy[World]"); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void testMessageArgsWithDefaultMessage() throws Exception { |
||||||
|
assertThat(getMacroOutput("MESSAGEARGSWITHDEFAULTMESSAGE")).isEqualTo("Hi"); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void testUrl() throws Exception { |
||||||
|
assertThat(getMacroOutput("URL")).isEqualTo("/springtest/aftercontext.html"); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void testUrlParams() throws Exception { |
||||||
|
assertThat(getMacroOutput("URLPARAMS")).isEqualTo("/springtest/aftercontext/bar?spam=bucket"); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void testForm1() throws Exception { |
||||||
|
assertThat(getMacroOutput("FORM1")).isEqualTo("<input type=\"text\" id=\"name\" name=\"name\" value=\"Darren\" >"); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void testForm2() throws Exception { |
||||||
|
assertThat(getMacroOutput("FORM2")).isEqualTo("<input type=\"text\" id=\"name\" name=\"name\" value=\"Darren\" class=\"myCssClass\" >"); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void testForm3() throws Exception { |
||||||
|
assertThat(getMacroOutput("FORM3")).isEqualTo("<textarea id=\"name\" name=\"name\" >\nDarren</textarea>"); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void testForm4() throws Exception { |
||||||
|
assertThat(getMacroOutput("FORM4")).isEqualTo("<textarea id=\"name\" name=\"name\" rows=10 cols=30>\nDarren</textarea>"); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void testForm9() throws Exception { |
||||||
|
assertThat(getMacroOutput("FORM9")).isEqualTo("<input type=\"password\" id=\"name\" name=\"name\" value=\"\" >"); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void testForm10() throws Exception { |
||||||
|
assertThat(getMacroOutput("FORM10")).isEqualTo("<input type=\"hidden\" id=\"name\" name=\"name\" value=\"Darren\" >"); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void testForm11() throws Exception { |
||||||
|
assertThat(getMacroOutput("FORM11")).isEqualTo("<input type=\"text\" id=\"name\" name=\"name\" value=\"Darren\" >"); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void testForm12() throws Exception { |
||||||
|
assertThat(getMacroOutput("FORM12")).isEqualTo("<input type=\"hidden\" id=\"name\" name=\"name\" value=\"Darren\" >"); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void testForm13() throws Exception { |
||||||
|
assertThat(getMacroOutput("FORM13")).isEqualTo("<input type=\"password\" id=\"name\" name=\"name\" value=\"\" >"); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void testForm15() throws Exception { |
||||||
|
String output = getMacroOutput("FORM15"); |
||||||
|
assertThat(output.startsWith("<input type=\"hidden\" name=\"_name\" value=\"on\"/>")).as("Wrong output: " + output).isTrue(); |
||||||
|
assertThat(output.contains("<input type=\"checkbox\" id=\"name\" name=\"name\" />")).as("Wrong output: " + output).isTrue(); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void testForm16() throws Exception { |
||||||
|
String output = getMacroOutput("FORM16"); |
||||||
|
assertThat(output.startsWith( |
||||||
|
"<input type=\"hidden\" name=\"_jedi\" value=\"on\"/>")).as("Wrong output: " + output).isTrue(); |
||||||
|
assertThat(output.contains( |
||||||
|
"<input type=\"checkbox\" id=\"jedi\" name=\"jedi\" checked=\"checked\" />")).as("Wrong output: " + output).isTrue(); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void testForm17() throws Exception { |
||||||
|
assertThat(getMacroOutput("FORM17")).isEqualTo("<input type=\"text\" id=\"spouses0.name\" name=\"spouses[0].name\" value=\"Fred\" >"); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void testForm18() throws Exception { |
||||||
|
String output = getMacroOutput("FORM18"); |
||||||
|
assertThat(output.startsWith( |
||||||
|
"<input type=\"hidden\" name=\"_spouses[0].jedi\" value=\"on\"/>")).as("Wrong output: " + output).isTrue(); |
||||||
|
assertThat(output.contains( |
||||||
|
"<input type=\"checkbox\" id=\"spouses0.jedi\" name=\"spouses[0].jedi\" checked=\"checked\" />")).as("Wrong output: " + output).isTrue(); |
||||||
|
} |
||||||
|
|
||||||
|
private String getMacroOutput(String name) throws Exception { |
||||||
|
String macro = fetchMacro(name); |
||||||
|
assertThat(macro).isNotNull(); |
||||||
|
|
||||||
|
FileSystemResource resource = new FileSystemResource(System.getProperty("java.io.tmpdir") + "/tmp.ftl"); |
||||||
|
FileCopyUtils.copy("<#import \"spring.ftl\" as spring />\n" + macro, new FileWriter(resource.getPath())); |
||||||
|
|
||||||
|
Map<String, String> msgMap = new HashMap<>(); |
||||||
|
msgMap.put("hello", "Howdy"); |
||||||
|
msgMap.put("world", "Mundo"); |
||||||
|
|
||||||
|
TestBean darren = new TestBean("Darren", 99); |
||||||
|
TestBean fred = new TestBean("Fred"); |
||||||
|
fred.setJedi(true); |
||||||
|
darren.setSpouse(fred); |
||||||
|
darren.setJedi(true); |
||||||
|
darren.setStringArray(new String[] {"John", "Fred"}); |
||||||
|
|
||||||
|
Map<String, String> names = new HashMap<>(); |
||||||
|
names.put("Darren", "Darren Davison"); |
||||||
|
names.put("John", "John Doe"); |
||||||
|
names.put("Fred", "Fred Bloggs"); |
||||||
|
names.put("Rob&Harrop", "Rob Harrop"); |
||||||
|
|
||||||
|
ModelMap model = new ExtendedModelMap(); |
||||||
|
DummyMacroRequestContext rc = new DummyMacroRequestContext(this.exchange, model, new GenericApplicationContext()); |
||||||
|
rc.setMessageMap(msgMap); |
||||||
|
rc.setContextPath("/springtest"); |
||||||
|
|
||||||
|
model.put("command", darren); |
||||||
|
model.put("springMacroRequestContext", rc); |
||||||
|
model.put("msgArgs", new Object[] { "World" }); |
||||||
|
model.put("nameOptionMap", names); |
||||||
|
model.put("options", names.values()); |
||||||
|
|
||||||
|
FreeMarkerView view = new FreeMarkerView(); |
||||||
|
view.setBeanName("myView"); |
||||||
|
view.setUrl("tmp.ftl"); |
||||||
|
view.setConfiguration(freeMarkerConfig); |
||||||
|
|
||||||
|
view.render(model, null, this.exchange).subscribe(); |
||||||
|
|
||||||
|
// tokenize output and ignore whitespace
|
||||||
|
String output = this.exchange.getResponse().getBodyAsString().block(); |
||||||
|
output = output.replace("\r\n", "\n"); |
||||||
|
return output.trim(); |
||||||
|
} |
||||||
|
|
||||||
|
private String fetchMacro(String name) throws Exception { |
||||||
|
ClassPathResource resource = new ClassPathResource("test-macro.ftl", getClass()); |
||||||
|
assertThat(resource.exists()).isTrue(); |
||||||
|
String all = FileCopyUtils.copyToString(new InputStreamReader(resource.getInputStream())); |
||||||
|
all = all.replace("\r\n", "\n"); |
||||||
|
String[] macros = StringUtils.delimitedListToStringArray(all, "\n\n"); |
||||||
|
for (String macro : macros) { |
||||||
|
if (macro.startsWith(name)) { |
||||||
|
return macro.substring(macro.indexOf("\n")).trim(); |
||||||
|
} |
||||||
|
} |
||||||
|
return null; |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
@ -0,0 +1,82 @@ |
|||||||
|
<#-- |
||||||
|
test template for FreeMarker macro test class |
||||||
|
--> |
||||||
|
<#import "spring.ftl" as spring /> |
||||||
|
|
||||||
|
NAME |
||||||
|
${command.name} |
||||||
|
|
||||||
|
AGE |
||||||
|
${command.age} |
||||||
|
|
||||||
|
MESSAGE |
||||||
|
<@spring.message "hello"/> <@spring.message "world"/> |
||||||
|
|
||||||
|
DEFAULTMESSAGE |
||||||
|
<@spring.messageText "no.such.code", "hi"/> <@spring.messageText "no.such.code", "planet"/> |
||||||
|
|
||||||
|
MESSAGEARGS |
||||||
|
<@spring.messageArgs "hello", msgArgs/> |
||||||
|
|
||||||
|
MESSAGEARGSWITHDEFAULTMESSAGE |
||||||
|
<@spring.messageArgsText "no.such.code", msgArgs, "Hi"/> |
||||||
|
|
||||||
|
URL |
||||||
|
<@spring.url "/aftercontext.html"/> |
||||||
|
|
||||||
|
URLPARAMS |
||||||
|
<@spring.url relativeUrl="/aftercontext/{foo}?spam={spam}" foo="bar" spam="bucket"/> |
||||||
|
|
||||||
|
FORM1 |
||||||
|
<@spring.formInput "command.name", ""/> |
||||||
|
|
||||||
|
FORM2 |
||||||
|
<@spring.formInput "command.name", 'class="myCssClass"'/> |
||||||
|
|
||||||
|
FORM3 |
||||||
|
<@spring.formTextarea "command.name", ""/> |
||||||
|
|
||||||
|
FORM4 |
||||||
|
<@spring.formTextarea "command.name", "rows=10 cols=30"/> |
||||||
|
|
||||||
|
FORM5 |
||||||
|
<@spring.formSingleSelect "command.name", nameOptionMap, ""/> |
||||||
|
|
||||||
|
FORM6 |
||||||
|
<@spring.formMultiSelect "command.spouses", nameOptionMap, ""/> |
||||||
|
|
||||||
|
FORM7 |
||||||
|
<@spring.formRadioButtons "command.name", nameOptionMap, " ", ""/> |
||||||
|
|
||||||
|
FORM8 |
||||||
|
<@spring.formCheckboxes "command.stringArray", nameOptionMap, " ", ""/> |
||||||
|
|
||||||
|
FORM9 |
||||||
|
<@spring.formPasswordInput "command.name", ""/> |
||||||
|
|
||||||
|
FORM10 |
||||||
|
<@spring.formHiddenInput "command.name", ""/> |
||||||
|
|
||||||
|
FORM11 |
||||||
|
<@spring.formInput "command.name", "", "text"/> |
||||||
|
|
||||||
|
FORM12 |
||||||
|
<@spring.formInput "command.name", "", "hidden"/> |
||||||
|
|
||||||
|
FORM13 |
||||||
|
<@spring.formInput "command.name", "", "password"/> |
||||||
|
|
||||||
|
FORM14 |
||||||
|
<@spring.formSingleSelect "command.name", options, ""/> |
||||||
|
|
||||||
|
FORM15 |
||||||
|
<@spring.formCheckbox "command.name"/> |
||||||
|
|
||||||
|
FORM16 |
||||||
|
<@spring.formCheckbox "command.jedi"/> |
||||||
|
|
||||||
|
FORM17 |
||||||
|
<@spring.formInput "command.spouses[0].name", ""/> |
||||||
|
|
||||||
|
FORM18 |
||||||
|
<@spring.formCheckbox "command.spouses[0].jedi" /> |
||||||
Loading…
Reference in new issue