You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@freemarker.apache.org by dd...@apache.org on 2017/09/19 06:03:54 UTC

[1/5] incubator-freemarker git commit: FREEMARKER-55: stringifying string/boolean/date model in url function. otherwise exception

Repository: incubator-freemarker
Updated Branches:
  refs/heads/3 12a2d49d0 -> f5a44238e


FREEMARKER-55: stringifying string/boolean/date model in url function. otherwise exception


Project: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/commit/4779234b
Tree: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/tree/4779234b
Diff: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/diff/4779234b

Branch: refs/heads/3
Commit: 4779234b67bb9c6ab0f0812d72ebe4b2152de04a
Parents: 14e4250
Author: Woonsan Ko <wo...@apache.org>
Authored: Fri Sep 15 10:39:26 2017 -0400
Committer: Woonsan Ko <wo...@apache.org>
Committed: Fri Sep 15 10:39:26 2017 -0400

----------------------------------------------------------------------
 .../AbstractSpringTemplateCallableModel.java    |  4 +-
 .../spring/model/MessageFunction.java           |  4 +-
 .../freemarker/spring/model/UrlFunction.java    | 23 ++++++---
 .../spring/example/mvc/users/User.java          |  6 +--
 .../example/mvc/users/UserController.java       |  4 +-
 .../example/mvc/users/UserRepository.java       | 16 +++----
 .../spring/model/BindDirectiveTest.java         |  2 +-
 .../spring/model/EvalFunctionTest.java          |  2 +-
 .../spring/model/MessageFunctionTest.java       |  4 +-
 .../spring/model/NestedPathDirectiveTest.java   |  2 +-
 .../spring/model/ThemeFunctionTest.java         |  2 +-
 .../spring/model/UrlFunctionTest.java           | 49 ++++++++++++++------
 .../test/model/url-function-basic-usages.ftl    | 29 ++++++++++++
 13 files changed, 104 insertions(+), 43 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/4779234b/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/AbstractSpringTemplateCallableModel.java
----------------------------------------------------------------------
diff --git a/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/AbstractSpringTemplateCallableModel.java b/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/AbstractSpringTemplateCallableModel.java
index 1c3d71b..d6157b0 100644
--- a/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/AbstractSpringTemplateCallableModel.java
+++ b/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/AbstractSpringTemplateCallableModel.java
@@ -81,7 +81,7 @@ abstract class AbstractSpringTemplateCallableModel implements TemplateCallableMo
         final ObjectWrapper objectWrapper = env.getObjectWrapper();
 
         if (!(objectWrapper instanceof ObjectWrapperAndUnwrapper)) {
-            CallableUtils.newGenericExecuteException(
+            throw CallableUtils.newGenericExecuteException(
                     "The ObjectWrapper of environment isn't an instance of ObjectWrapperAndUnwrapper.", this,
                     calledAsFunction);
         }
@@ -101,7 +101,7 @@ abstract class AbstractSpringTemplateCallableModel implements TemplateCallableMo
         TemplateModel rcModel = env.getVariable(AbstractTemplateView.SPRING_MACRO_REQUEST_CONTEXT_ATTRIBUTE);
 
         if (rcModel == null) {
-            CallableUtils.newGenericExecuteException(
+            throw CallableUtils.newGenericExecuteException(
                     AbstractTemplateView.SPRING_MACRO_REQUEST_CONTEXT_ATTRIBUTE + " not found.", this, false);
         }
 

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/4779234b/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/MessageFunction.java
----------------------------------------------------------------------
diff --git a/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/MessageFunction.java b/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/MessageFunction.java
index 3755849..c6ac520 100644
--- a/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/MessageFunction.java
+++ b/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/MessageFunction.java
@@ -103,7 +103,7 @@ class MessageFunction extends AbstractSpringTemplateFunctionModel {
         final MessageSource messageSource = getMessageSource(requestContext);
 
         if (messageSource == null) {
-            CallableUtils.newGenericExecuteException("MessageSource not found.", this);
+            throw CallableUtils.newGenericExecuteException("MessageSource not found.", this);
         }
 
         String message = null;
@@ -135,7 +135,7 @@ class MessageFunction extends AbstractSpringTemplateFunctionModel {
                 message = messageSource.getMessage(code, (msgArgumentList == null) ? null : msgArgumentList.toArray(),
                         null, requestContext.getLocale());
             } else {
-                CallableUtils.newNullOrOmittedArgumentException(CODE_PARAM_IDX, this);
+                throw CallableUtils.newNullOrOmittedArgumentException(CODE_PARAM_IDX, this);
             }
         }
 

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/4779234b/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/UrlFunction.java
----------------------------------------------------------------------
diff --git a/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/UrlFunction.java b/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/UrlFunction.java
index e2986a9..7f180ec 100644
--- a/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/UrlFunction.java
+++ b/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/UrlFunction.java
@@ -36,9 +36,11 @@ import org.apache.freemarker.core.Environment;
 import org.apache.freemarker.core.TemplateException;
 import org.apache.freemarker.core.model.ArgumentArrayLayout;
 import org.apache.freemarker.core.model.ObjectWrapperAndUnwrapper;
+import org.apache.freemarker.core.model.TemplateBooleanModel;
 import org.apache.freemarker.core.model.TemplateHashModelEx2;
 import org.apache.freemarker.core.model.TemplateHashModelEx2.KeyValuePairIterator;
 import org.apache.freemarker.core.model.TemplateModel;
+import org.apache.freemarker.core.model.TemplateNumberModel;
 import org.apache.freemarker.core.model.TemplateStringModel;
 import org.apache.freemarker.core.util.CallableUtils;
 import org.apache.freemarker.core.util.StringToIndexMap;
@@ -127,20 +129,27 @@ class UrlFunction extends AbstractSpringTemplateFunctionModel {
                     paramName = ((TemplateStringModel) paramNameModel).getAsString();
 
                     if (paramName.isEmpty()) {
-                        CallableUtils.newArgumentValueException(PARAMS_PARAM_IDX,
+                        throw CallableUtils.newArgumentValueException(PARAMS_PARAM_IDX,
                                 "Parameter name must be a non-blank string.", this);
                     }
 
                     if (paramValueModel instanceof TemplateStringModel) {
                         paramValue = ((TemplateStringModel) paramValueModel).getAsString();
+                    } else if (paramValueModel instanceof TemplateNumberModel) {
+                        paramValue = ((TemplateNumberModel) paramValueModel).getAsNumber().toString();
+                    } else if (paramValueModel instanceof TemplateBooleanModel) {
+                        paramValue = Boolean.toString(((TemplateBooleanModel) paramValueModel).getAsBoolean());
                     } else {
-                        paramValue = env.formatToPlainText(paramValueModel);
+                        throw CallableUtils.newArgumentValueException(PARAMS_PARAM_IDX,
+                                "Format the parameter manually to properly coerce it to a URL parameter value string. "
+                                        + "e.g, date?string.iso, date?long, list?join('_'), etc.",
+                                this);
                     }
 
                     params.add(new _KeyValuePair<String, String>(paramName, paramValue));
                 } else {
-                    CallableUtils.newArgumentValueException(PARAMS_PARAM_IDX,
-                            "Parameter name must be string.", this);
+                    throw CallableUtils.newArgumentValueException(PARAMS_PARAM_IDX,
+                            "Parameter name must be a string.", this);
                 }
             }
         }
@@ -214,7 +223,7 @@ class UrlFunction extends AbstractSpringTemplateFunctionModel {
                 try {
                     uri = uri.replace(template, UriUtils.encodePath(paramValue, encoding));
                 } catch (UnsupportedEncodingException e) {
-                    CallableUtils.newGenericExecuteException("Cannot encode URI. " + e, this);
+                    throw CallableUtils.newGenericExecuteException("Cannot encode URI. " + e, this);
                 }
             } else {
                 template = URL_TEMPLATE_DELIMITER_PREFIX + '/' + paramName + URL_TEMPLATE_DELIMITER_SUFFIX;
@@ -225,7 +234,7 @@ class UrlFunction extends AbstractSpringTemplateFunctionModel {
                     try {
                         uri = uri.replace(template, UriUtils.encodePathSegment(paramValue, encoding));
                     } catch (UnsupportedEncodingException e) {
-                        CallableUtils.newGenericExecuteException("Cannot encode URI. " + e, this);
+                        throw CallableUtils.newGenericExecuteException("Cannot encode URI. " + e, this);
                     }
                 }
             }
@@ -258,7 +267,7 @@ class UrlFunction extends AbstractSpringTemplateFunctionModel {
                         queryStringBuilder.append(UriUtils.encodeQueryParam(paramValue, encoding));
                     }
                 } catch (UnsupportedEncodingException e) {
-                    CallableUtils.newGenericExecuteException("Cannot encode query parameter. " + e, this);
+                    throw CallableUtils.newGenericExecuteException("Cannot encode query parameter. " + e, this);
                 }
             }
         }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/4779234b/freemarker-spring/src/test/java/org/apache/freemarker/spring/example/mvc/users/User.java
----------------------------------------------------------------------
diff --git a/freemarker-spring/src/test/java/org/apache/freemarker/spring/example/mvc/users/User.java b/freemarker-spring/src/test/java/org/apache/freemarker/spring/example/mvc/users/User.java
index 23d31dc..41b42c4 100644
--- a/freemarker-spring/src/test/java/org/apache/freemarker/spring/example/mvc/users/User.java
+++ b/freemarker-spring/src/test/java/org/apache/freemarker/spring/example/mvc/users/User.java
@@ -23,7 +23,7 @@ import java.util.Date;
 
 public class User {
 
-    private Integer id;
+    private Long id;
     private String password;
     private String email;
     private String firstName;
@@ -33,11 +33,11 @@ public class User {
     public User() {
     }
 
-    public User(final Integer id) {
+    public User(final Long id) {
         this.id = id;
     }
 
-    public Integer getId() {
+    public Long getId() {
         return id;
     }
 

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/4779234b/freemarker-spring/src/test/java/org/apache/freemarker/spring/example/mvc/users/UserController.java
----------------------------------------------------------------------
diff --git a/freemarker-spring/src/test/java/org/apache/freemarker/spring/example/mvc/users/UserController.java b/freemarker-spring/src/test/java/org/apache/freemarker/spring/example/mvc/users/UserController.java
index 09a416d..5b1799e 100644
--- a/freemarker-spring/src/test/java/org/apache/freemarker/spring/example/mvc/users/UserController.java
+++ b/freemarker-spring/src/test/java/org/apache/freemarker/spring/example/mvc/users/UserController.java
@@ -60,7 +60,7 @@ public class UserController {
     public String listUsers(@RequestParam(value = "viewName", required = false) String viewName, Model model) {
         List<User> users = new LinkedList<>();
 
-        for (Integer id : userRepository.getUserIds()) {
+        for (Long id : userRepository.getUserIds()) {
             users.add(userRepository.getUser(id));
         }
 
@@ -70,7 +70,7 @@ public class UserController {
     }
 
     @RequestMapping(value = "/users/{id:\\d+}", method = RequestMethod.GET)
-    public String getUser(@PathVariable("id") Integer id,
+    public String getUser(@PathVariable("id") Long id,
             @RequestParam(value = "viewName", required = false) String viewName, Model model) {
         User user = userRepository.getUser(id);
 

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/4779234b/freemarker-spring/src/test/java/org/apache/freemarker/spring/example/mvc/users/UserRepository.java
----------------------------------------------------------------------
diff --git a/freemarker-spring/src/test/java/org/apache/freemarker/spring/example/mvc/users/UserRepository.java b/freemarker-spring/src/test/java/org/apache/freemarker/spring/example/mvc/users/UserRepository.java
index 5049521..1f1a9e2 100644
--- a/freemarker-spring/src/test/java/org/apache/freemarker/spring/example/mvc/users/UserRepository.java
+++ b/freemarker-spring/src/test/java/org/apache/freemarker/spring/example/mvc/users/UserRepository.java
@@ -30,21 +30,21 @@ import org.springframework.stereotype.Repository;
 @Repository
 public class UserRepository {
 
-    private Map<Integer, User> usersMap = new ConcurrentHashMap<>();
+    private Map<Long, User> usersMap = new ConcurrentHashMap<>();
     {
-        Integer id = 101;
+        Long id = 101L;
         User user = new User(id);
         user.setEmail("john@example.com");
         user.setFirstName("John");
         user.setLastName("Doe");
         Calendar birthDate = Calendar.getInstance();
-        birthDate.set(Calendar.YEAR, 1971);
+        birthDate.set(Calendar.YEAR, 1973);
         birthDate.set(Calendar.MONTH, Calendar.JANUARY);
         birthDate.set(Calendar.DATE, 5);
         user.setBirthDate(birthDate.getTime());
         usersMap.put(id, user);
 
-        id = 102;
+        id = 102L;
         user = new User(id);
         user.setEmail("jane@example.com");
         user.setFirstName("Jane");
@@ -57,11 +57,11 @@ public class UserRepository {
         usersMap.put(id, user);
     }
 
-    public synchronized Set<Integer> getUserIds() {
+    public synchronized Set<Long> getUserIds() {
         return new TreeSet<>(usersMap.keySet());
     }
 
-    public synchronized User getUser(final Integer id) {
+    public synchronized User getUser(final Long id) {
         if (id == null) {
             throw new IllegalArgumentException("ID must be non-null.");
         }
@@ -76,7 +76,7 @@ public class UserRepository {
     }
 
     public synchronized User addOrUpdateUser(final User user) {
-        final Integer id = user.getId();
+        final Long id = user.getId();
         User newUser = cloneUser(user, id);
         usersMap.put(id, newUser);
         return cloneUser(newUser, id);
@@ -91,7 +91,7 @@ public class UserRepository {
         return user != null;
     }
 
-    private User cloneUser(final User source, final Integer id) {
+    private User cloneUser(final User source, final Long id) {
         User clone = new User(id);
         clone.setPassword(source.getPassword());
         clone.setEmail(source.getEmail());

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/4779234b/freemarker-spring/src/test/java/org/apache/freemarker/spring/model/BindDirectiveTest.java
----------------------------------------------------------------------
diff --git a/freemarker-spring/src/test/java/org/apache/freemarker/spring/model/BindDirectiveTest.java b/freemarker-spring/src/test/java/org/apache/freemarker/spring/model/BindDirectiveTest.java
index b2a6cd5..0b21a47 100644
--- a/freemarker-spring/src/test/java/org/apache/freemarker/spring/model/BindDirectiveTest.java
+++ b/freemarker-spring/src/test/java/org/apache/freemarker/spring/model/BindDirectiveTest.java
@@ -59,7 +59,7 @@ public class BindDirectiveTest {
 
     @Test
     public void testBasicUsages() throws Exception {
-        final Integer userId = userRepository.getUserIds().iterator().next();
+        final Long userId = userRepository.getUserIds().iterator().next();
         final User user = userRepository.getUser(userId);
         mockMvc.perform(get("/users/{userId}.", userId).param("viewName", "test/model/bind-directive-basic-usages")
                 .accept(MediaType.parseMediaType("text/html"))).andExpect(status().isOk())

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/4779234b/freemarker-spring/src/test/java/org/apache/freemarker/spring/model/EvalFunctionTest.java
----------------------------------------------------------------------
diff --git a/freemarker-spring/src/test/java/org/apache/freemarker/spring/model/EvalFunctionTest.java b/freemarker-spring/src/test/java/org/apache/freemarker/spring/model/EvalFunctionTest.java
index 45d98b2..1e92292 100644
--- a/freemarker-spring/src/test/java/org/apache/freemarker/spring/model/EvalFunctionTest.java
+++ b/freemarker-spring/src/test/java/org/apache/freemarker/spring/model/EvalFunctionTest.java
@@ -60,7 +60,7 @@ public class EvalFunctionTest {
 
     @Test
     public void testBasicUsages() throws Exception {
-        final Integer userId = userRepository.getUserIds().iterator().next();
+        final Long userId = userRepository.getUserIds().iterator().next();
         final User user = userRepository.getUser(userId);
         mockMvc.perform(get("/users/").param("viewName", "test/model/eval-function-basic-usages")
                 .accept(MediaType.parseMediaType("text/html"))).andExpect(status().isOk())

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/4779234b/freemarker-spring/src/test/java/org/apache/freemarker/spring/model/MessageFunctionTest.java
----------------------------------------------------------------------
diff --git a/freemarker-spring/src/test/java/org/apache/freemarker/spring/model/MessageFunctionTest.java b/freemarker-spring/src/test/java/org/apache/freemarker/spring/model/MessageFunctionTest.java
index d043bfe..37af7eb 100644
--- a/freemarker-spring/src/test/java/org/apache/freemarker/spring/model/MessageFunctionTest.java
+++ b/freemarker-spring/src/test/java/org/apache/freemarker/spring/model/MessageFunctionTest.java
@@ -59,7 +59,7 @@ public class MessageFunctionTest {
 
     @Test
     public void testBasicUsages() throws Exception {
-        final Integer userId = userRepository.getUserIds().iterator().next();
+        final Long userId = userRepository.getUserIds().iterator().next();
         final User user = userRepository.getUser(userId);
         mockMvc.perform(get("/users/{userId}/", userId).param("viewName", "test/model/message-function-basic-usages")
                 .accept(MediaType.parseMediaType("text/html"))).andExpect(status().isOk())
@@ -72,7 +72,7 @@ public class MessageFunctionTest {
 
     @Test
     public void testWithMessageSourceResolvable() throws Exception {
-        final Integer nonExistingUserId = 0;
+        final Long nonExistingUserId = 0L;
         mockMvc.perform(
                 get("/users/{userId}/", nonExistingUserId).param("viewName", "test/model/message-function-basic-usages")
                         .accept(MediaType.parseMediaType("text/html")))

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/4779234b/freemarker-spring/src/test/java/org/apache/freemarker/spring/model/NestedPathDirectiveTest.java
----------------------------------------------------------------------
diff --git a/freemarker-spring/src/test/java/org/apache/freemarker/spring/model/NestedPathDirectiveTest.java b/freemarker-spring/src/test/java/org/apache/freemarker/spring/model/NestedPathDirectiveTest.java
index a4999f5..f5f7d18 100644
--- a/freemarker-spring/src/test/java/org/apache/freemarker/spring/model/NestedPathDirectiveTest.java
+++ b/freemarker-spring/src/test/java/org/apache/freemarker/spring/model/NestedPathDirectiveTest.java
@@ -59,7 +59,7 @@ public class NestedPathDirectiveTest {
 
     @Test
     public void testBasicUsages() throws Exception {
-        final Integer userId = userRepository.getUserIds().iterator().next();
+        final Long userId = userRepository.getUserIds().iterator().next();
         final User user = userRepository.getUser(userId);
         mockMvc.perform(get("/users/{userId}.", userId).param("viewName", "test/model/nestedpath-directive-basic-usages")
                 .accept(MediaType.parseMediaType("text/html"))).andExpect(status().isOk())

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/4779234b/freemarker-spring/src/test/java/org/apache/freemarker/spring/model/ThemeFunctionTest.java
----------------------------------------------------------------------
diff --git a/freemarker-spring/src/test/java/org/apache/freemarker/spring/model/ThemeFunctionTest.java b/freemarker-spring/src/test/java/org/apache/freemarker/spring/model/ThemeFunctionTest.java
index 804b76d..5dd4bdc 100644
--- a/freemarker-spring/src/test/java/org/apache/freemarker/spring/model/ThemeFunctionTest.java
+++ b/freemarker-spring/src/test/java/org/apache/freemarker/spring/model/ThemeFunctionTest.java
@@ -65,7 +65,7 @@ public class ThemeFunctionTest {
     public void testBasicUsages() throws Exception {
         final MessageSource defaultThemeMessageSource = themeSource.getTheme("default").getMessageSource();
 
-        final Integer userId = userRepository.getUserIds().iterator().next();
+        final Long userId = userRepository.getUserIds().iterator().next();
         mockMvc.perform(get("/users/{userId}/", userId).param("viewName", "test/model/theme-function-basic-usages")
                 .accept(MediaType.parseMediaType("text/html"))).andExpect(status().isOk())
                 .andExpect(content().contentTypeCompatibleWith("text/html")).andDo(print())

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/4779234b/freemarker-spring/src/test/java/org/apache/freemarker/spring/model/UrlFunctionTest.java
----------------------------------------------------------------------
diff --git a/freemarker-spring/src/test/java/org/apache/freemarker/spring/model/UrlFunctionTest.java b/freemarker-spring/src/test/java/org/apache/freemarker/spring/model/UrlFunctionTest.java
index 8435bfc..3079556 100644
--- a/freemarker-spring/src/test/java/org/apache/freemarker/spring/model/UrlFunctionTest.java
+++ b/freemarker-spring/src/test/java/org/apache/freemarker/spring/model/UrlFunctionTest.java
@@ -19,12 +19,10 @@
 
 package org.apache.freemarker.spring.model;
 
-import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
-import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
-import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
-import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
-import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.xpath;
+import java.text.DecimalFormat;
+import java.text.SimpleDateFormat;
 
+import org.apache.freemarker.spring.example.mvc.users.User;
 import org.apache.freemarker.spring.example.mvc.users.UserRepository;
 import org.junit.Before;
 import org.junit.Test;
@@ -38,11 +36,21 @@ import org.springframework.test.web.servlet.MockMvc;
 import org.springframework.test.web.servlet.setup.MockMvcBuilders;
 import org.springframework.web.context.WebApplicationContext;
 
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
+import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.xpath;
+
 @RunWith(SpringJUnit4ClassRunner.class)
 @WebAppConfiguration("classpath:META-INF/web-resources")
 @ContextConfiguration(locations = { "classpath:org/apache/freemarker/spring/example/mvc/users/users-mvc-context.xml" })
 public class UrlFunctionTest {
 
+    private static final String TEMPLATE_NUMBER_FORMAT = "00000000";
+
+    private static final String TEMPLATE_DATE_FORMAT = "yyyy-MM-dd";
+
     @Autowired
     private WebApplicationContext wac;
 
@@ -58,19 +66,34 @@ public class UrlFunctionTest {
 
     @Test
     public void testBasicUsages() throws Exception {
-        final Integer userId = userRepository.getUserIds().iterator().next();
+        final Long userId = userRepository.getUserIds().iterator().next();
+        final String formattedUerId = new DecimalFormat(TEMPLATE_NUMBER_FORMAT).format(userId);
+        final User user = userRepository.getUser(userId);
         mockMvc.perform(get("/users/").param("viewName", "test/model/url-function-basic-usages")
                 .accept(MediaType.parseMediaType("text/html"))).andExpect(status().isOk())
                 .andExpect(content().contentTypeCompatibleWith("text/html")).andDo(print())
-                .andExpect(xpath("//h2[@id='usersListHeader']//a/@href", userId).string("/users/"))
-                .andExpect(xpath("//h3[@id='usersListHeaderWithSortParams']//a/@href", userId)
+                .andExpect(xpath("//h2[@id='usersListHeader']//a/@href").string("/users/"))
+                .andExpect(xpath("//h3[@id='usersListHeaderWithSortParams']//a/@href")
                         .string("/users/?sortField=birthDate&sortDirection=descending"))
-                .andExpect(xpath("//h2[@id='otherAppsUsersListHeader']//a/@href", userId).string("/otherapp/users/"))
-                .andExpect(xpath("//h3[@id='otherAppsUsersListHeaderWithSortParams']//a/@href", userId)
+                .andExpect(xpath("//h2[@id='otherAppsUsersListHeader']//a/@href").string("/otherapp/users/"))
+                .andExpect(xpath("//h3[@id='otherAppsUsersListHeaderWithSortParams']//a/@href")
                         .string("/otherapp/users/?sortField=birthDate&sortDirection=descending"))
-                .andExpect(xpath("//div[@id='user-%s']//a[@class='userIdLink']/@href", userId).string("/users/" + userId + "/"))
-                .andExpect(xpath("//div[@id='user-%s']//a[@class='userNameLink']/@href", userId).string("/users/" + userId + "/"))
-                .andExpect(xpath("//div[@id='freeMarkerManualUrl']//a/@href", userId)
+                .andExpect(xpath("//div[@id='user-%s']//a[@class='userIdLink']/@href", formattedUerId)
+                        .string("/users/" + userId + "/"))
+                .andExpect(xpath("//div[@id='user-%s']//a[@class='userNameLink']/@href", formattedUerId)
+                        .string("/users/" + formattedUerId + "/"))
+                .andExpect(xpath("//div[@id='user-%s']//a[@class='badUserBirthDateLink']/@href", formattedUerId)
+                        .doesNotExist())
+                .andExpect(xpath("//div[@id='user-%s']//a[@class='goodUserBirthDateLink']/@href", formattedUerId)
+                        .string("/users/" + userId + "/?birthDate="
+                                + new SimpleDateFormat(TEMPLATE_DATE_FORMAT).format(user.getBirthDate())))
+                .andExpect(xpath("//div[@id='listLinkTest']//a[@class='badListLink']/@href").doesNotExist())
+                .andExpect(xpath("//div[@id='listLinkTest']//a[@class='goodListLink']/@href")
+                        .string("/users/?items=101_102"))
+                .andExpect(xpath("//div[@id='mapLinkTest']//a[@class='badMapLink']/@href").doesNotExist())
+                .andExpect(xpath("//div[@id='mapLinkTest']//a[@class='goodMapLink']/@href")
+                        .string("/users/?items=101_102"))
+                .andExpect(xpath("//div[@id='freeMarkerManualUrl']//a/@href")
                         .string("http://freemarker.org/docs/index.html"));
     }
 }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/4779234b/freemarker-spring/src/test/resources/META-INF/web-resources/views/test/model/url-function-basic-usages.ftl
----------------------------------------------------------------------
diff --git a/freemarker-spring/src/test/resources/META-INF/web-resources/views/test/model/url-function-basic-usages.ftl b/freemarker-spring/src/test/resources/META-INF/web-resources/views/test/model/url-function-basic-usages.ftl
index e25a2d5..8cc7c65 100644
--- a/freemarker-spring/src/test/resources/META-INF/web-resources/views/test/model/url-function-basic-usages.ftl
+++ b/freemarker-spring/src/test/resources/META-INF/web-resources/views/test/model/url-function-basic-usages.ftl
@@ -20,6 +20,9 @@
 <html>
 <body>
 
+<#-- NOTE: spring.url function should not use this number format. -->
+<#setting numberFormat="00000000" />
+
 <h2 id="usersListHeader">
   <a href="${spring.url('/users/')}">Users List</a>
 </h2>
@@ -42,6 +45,13 @@
       <div id="user-${user.id!}">
         <a class="userIdLink" href="${spring.url('/users/{userId}/', userId=user.id)}">${user.id!}</a>
         <a class="userNameLink" href="${spring.url('/users/${user.id}/')}">${user.firstName!} ${user.lastName!}</a>
+
+        <#attempt>
+          <a class="badUserBirthDateLink" href="${spring.url('/users/{userId}/', userId=user.id, birthDate=user.birthDate)}">${user.birthDate?date}</a>
+        <#recover>
+          <a class="goodUserBirthDateLink" href="${spring.url('/users/{userId}/', userId=user.id, birthDate=user.birthDate?string['yyyy-MM-dd'])}">${user.birthDate?date}</a>
+        </#attempt>
+
       </div>
     </li>
   </#list>
@@ -51,5 +61,24 @@
   <a href="${spring.url('http://freemarker.org/docs/index.html')}">Apache FreeMarker Manual</a>
 </div>
 
+<#-- List or Map is not allowed to pass as url parameter directly. -->
+<#assign userIdList = [ '101', '102' ] />
+<#assign userInfoMap = { "101": "John", "102": "Jane" } />
+
+<div id="listLinkTest">
+  <#attempt>
+    <a class="badListLink" href="${spring.url('/users/', items=userIdList)}">User List Link</a>
+  <#recover>
+    <a class="goodListLink" href="${spring.url('/users/', items=userIdList?join('_'))}">User List Link</a>
+  </#attempt>
+</div>
+<div id="mapLinkTest">
+  <#attempt>
+    <a class="badMapLink" href="${spring.url('/users/', items=userInfoMap)}">User List Link</a>
+  <#recover>
+    <a class="goodMapLink" href="${spring.url('/users/', items=userInfoMap?keys?join('_'))}">User List Link</a>
+  </#attempt>
+</div>
+
 </body>
 </html>


[2/5] incubator-freemarker git commit: FREEMARKER-55: narrow local var scope and reduce conditional blocks.

Posted by dd...@apache.org.
FREEMARKER-55: narrow local var scope and reduce conditional blocks.


Project: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/commit/8837a923
Tree: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/tree/8837a923
Diff: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/diff/8837a923

Branch: refs/heads/3
Commit: 8837a923029528474d14edff467a5f1d3bc99904
Parents: 4779234
Author: Woonsan Ko <wo...@apache.org>
Authored: Mon Sep 18 10:03:12 2017 -0400
Committer: Woonsan Ko <wo...@apache.org>
Committed: Mon Sep 18 10:03:12 2017 -0400

----------------------------------------------------------------------
 .../freemarker/spring/model/UrlFunction.java    | 54 +++++++++-----------
 1 file changed, 25 insertions(+), 29 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/8837a923/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/UrlFunction.java
----------------------------------------------------------------------
diff --git a/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/UrlFunction.java b/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/UrlFunction.java
index 7f180ec..a2c8f72 100644
--- a/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/UrlFunction.java
+++ b/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/UrlFunction.java
@@ -114,43 +114,39 @@ class UrlFunction extends AbstractSpringTemplateFunctionModel {
         if (!paramsHashModel.isEmptyHash()) {
             params = new ArrayList<>();
 
-            TemplateHashModelEx2.KeyValuePair pair;
-            TemplateModel paramNameModel;
-            TemplateModel paramValueModel;
-            String paramName;
-            String paramValue;
-
             for (KeyValuePairIterator pairIt = paramsHashModel.keyValuePairIterator(); pairIt.hasNext();) {
-                pair = pairIt.next();
-                paramNameModel = pair.getKey();
-                paramValueModel = pair.getValue();
+                TemplateHashModelEx2.KeyValuePair pair = pairIt.next();
+                TemplateModel paramNameModel = pair.getKey();
+                TemplateModel paramValueModel = pair.getValue();
 
-                if (paramNameModel instanceof TemplateStringModel) {
-                    paramName = ((TemplateStringModel) paramNameModel).getAsString();
+                if (!(paramNameModel instanceof TemplateStringModel)) {
+                    throw CallableUtils.newArgumentValueException(PARAMS_PARAM_IDX,
+                            "Parameter name must be a string.", this);
+                }
 
-                    if (paramName.isEmpty()) {
-                        throw CallableUtils.newArgumentValueException(PARAMS_PARAM_IDX,
-                                "Parameter name must be a non-blank string.", this);
-                    }
+                String paramName = ((TemplateStringModel) paramNameModel).getAsString();
 
-                    if (paramValueModel instanceof TemplateStringModel) {
-                        paramValue = ((TemplateStringModel) paramValueModel).getAsString();
-                    } else if (paramValueModel instanceof TemplateNumberModel) {
-                        paramValue = ((TemplateNumberModel) paramValueModel).getAsNumber().toString();
-                    } else if (paramValueModel instanceof TemplateBooleanModel) {
-                        paramValue = Boolean.toString(((TemplateBooleanModel) paramValueModel).getAsBoolean());
-                    } else {
-                        throw CallableUtils.newArgumentValueException(PARAMS_PARAM_IDX,
-                                "Format the parameter manually to properly coerce it to a URL parameter value string. "
-                                        + "e.g, date?string.iso, date?long, list?join('_'), etc.",
-                                this);
-                    }
+                if (paramName.isEmpty()) {
+                    throw CallableUtils.newArgumentValueException(PARAMS_PARAM_IDX,
+                            "Parameter name must be a non-blank string.", this);
+                }
+
+                String paramValue;
 
-                    params.add(new _KeyValuePair<String, String>(paramName, paramValue));
+                if (paramValueModel instanceof TemplateStringModel) {
+                    paramValue = ((TemplateStringModel) paramValueModel).getAsString();
+                } else if (paramValueModel instanceof TemplateNumberModel) {
+                    paramValue = ((TemplateNumberModel) paramValueModel).getAsNumber().toString();
+                } else if (paramValueModel instanceof TemplateBooleanModel) {
+                    paramValue = Boolean.toString(((TemplateBooleanModel) paramValueModel).getAsBoolean());
                 } else {
                     throw CallableUtils.newArgumentValueException(PARAMS_PARAM_IDX,
-                            "Parameter name must be a string.", this);
+                            "Format the parameter manually to properly coerce it to a URL parameter value string. "
+                                    + "e.g, date?string.iso, date?long, list?join('_'), etc.",
+                            this);
                 }
+
+                params.add(new _KeyValuePair<String, String>(paramName, paramValue));
             }
         }
 


[3/5] incubator-freemarker git commit: FREEMARKER-55: specifying encoding exception

Posted by dd...@apache.org.
FREEMARKER-55: specifying encoding exception


Project: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/commit/b8dcdc0a
Tree: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/tree/b8dcdc0a
Diff: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/diff/b8dcdc0a

Branch: refs/heads/3
Commit: b8dcdc0aaca5112f193cbb78c0e062b05f2f15ed
Parents: 8837a92
Author: Woonsan Ko <wo...@apache.org>
Authored: Mon Sep 18 10:18:48 2017 -0400
Committer: Woonsan Ko <wo...@apache.org>
Committed: Mon Sep 18 10:18:48 2017 -0400

----------------------------------------------------------------------
 .../spring/model/AbstractSpringTemplateCallableModel.java   | 2 +-
 .../org/apache/freemarker/spring/model/MessageFunction.java | 2 +-
 .../org/apache/freemarker/spring/model/UrlFunction.java     | 9 ++++++---
 3 files changed, 8 insertions(+), 5 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/b8dcdc0a/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/AbstractSpringTemplateCallableModel.java
----------------------------------------------------------------------
diff --git a/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/AbstractSpringTemplateCallableModel.java b/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/AbstractSpringTemplateCallableModel.java
index d6157b0..d97e750 100644
--- a/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/AbstractSpringTemplateCallableModel.java
+++ b/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/AbstractSpringTemplateCallableModel.java
@@ -102,7 +102,7 @@ abstract class AbstractSpringTemplateCallableModel implements TemplateCallableMo
 
         if (rcModel == null) {
             throw CallableUtils.newGenericExecuteException(
-                    AbstractTemplateView.SPRING_MACRO_REQUEST_CONTEXT_ATTRIBUTE + " not found.", this, false);
+                    AbstractTemplateView.SPRING_MACRO_REQUEST_CONTEXT_ATTRIBUTE + " is not found.", this, false);
         }
 
         RequestContext requestContext = (RequestContext) getObjectWrapperAndUnwrapper(env, calledAsFunction)

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/b8dcdc0a/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/MessageFunction.java
----------------------------------------------------------------------
diff --git a/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/MessageFunction.java b/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/MessageFunction.java
index c6ac520..e387a20 100644
--- a/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/MessageFunction.java
+++ b/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/MessageFunction.java
@@ -103,7 +103,7 @@ class MessageFunction extends AbstractSpringTemplateFunctionModel {
         final MessageSource messageSource = getMessageSource(requestContext);
 
         if (messageSource == null) {
-            throw CallableUtils.newGenericExecuteException("MessageSource not found.", this);
+            throw CallableUtils.newGenericExecuteException("MessageSource not found from the request context.", this);
         }
 
         String message = null;

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/b8dcdc0a/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/UrlFunction.java
----------------------------------------------------------------------
diff --git a/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/UrlFunction.java b/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/UrlFunction.java
index a2c8f72..0198eaf 100644
--- a/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/UrlFunction.java
+++ b/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/UrlFunction.java
@@ -219,7 +219,8 @@ class UrlFunction extends AbstractSpringTemplateFunctionModel {
                 try {
                     uri = uri.replace(template, UriUtils.encodePath(paramValue, encoding));
                 } catch (UnsupportedEncodingException e) {
-                    throw CallableUtils.newGenericExecuteException("Cannot encode URI. " + e, this);
+                    throw CallableUtils.newGenericExecuteException("Unsupported servlet response encoding: " + encoding,
+                            this);
                 }
             } else {
                 template = URL_TEMPLATE_DELIMITER_PREFIX + '/' + paramName + URL_TEMPLATE_DELIMITER_SUFFIX;
@@ -230,7 +231,8 @@ class UrlFunction extends AbstractSpringTemplateFunctionModel {
                     try {
                         uri = uri.replace(template, UriUtils.encodePathSegment(paramValue, encoding));
                     } catch (UnsupportedEncodingException e) {
-                        throw CallableUtils.newGenericExecuteException("Cannot encode URI. " + e, this);
+                        throw CallableUtils
+                                .newGenericExecuteException("Unsupported servlet response encoding: " + encoding, this);
                     }
                 }
             }
@@ -263,7 +265,8 @@ class UrlFunction extends AbstractSpringTemplateFunctionModel {
                         queryStringBuilder.append(UriUtils.encodeQueryParam(paramValue, encoding));
                     }
                 } catch (UnsupportedEncodingException e) {
-                    throw CallableUtils.newGenericExecuteException("Cannot encode query parameter. " + e, this);
+                    throw CallableUtils.newGenericExecuteException("Unsupported servlet response encoding: " + encoding,
+                            this);
                 }
             }
         }


[5/5] incubator-freemarker git commit: FREEMARKER-55: Merged: Stringifying string/boolean/date model in url function otherwise throw exception. Some code cleanup.

Posted by dd...@apache.org.
FREEMARKER-55: Merged: Stringifying string/boolean/date model in url function otherwise throw exception. Some code cleanup.

Merge commit 'refs/pull/36/head' of https://github.com/apache/incubator-freemarker into 3


Project: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/commit/f5a44238
Tree: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/tree/f5a44238
Diff: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/diff/f5a44238

Branch: refs/heads/3
Commit: f5a44238e002be0be5b9e86b3f7c27214f657a01
Parents: 12a2d49 b0f27aa
Author: ddekany <dd...@apache.org>
Authored: Tue Sep 19 08:01:12 2017 +0200
Committer: ddekany <dd...@apache.org>
Committed: Tue Sep 19 08:03:36 2017 +0200

----------------------------------------------------------------------
 .../AbstractSpringTemplateCallableModel.java    |  6 +-
 .../freemarker/spring/model/EvalFunction.java   |  2 -
 .../spring/model/MessageFunction.java           |  8 +--
 .../freemarker/spring/model/UrlFunction.java    | 64 +++++++++++---------
 .../spring/example/mvc/users/User.java          |  6 +-
 .../example/mvc/users/UserController.java       |  4 +-
 .../example/mvc/users/UserRepository.java       | 16 ++---
 .../spring/model/BindDirectiveTest.java         |  2 +-
 .../spring/model/EvalFunctionTest.java          |  2 +-
 .../spring/model/MessageFunctionTest.java       |  4 +-
 .../spring/model/NestedPathDirectiveTest.java   |  2 +-
 .../spring/model/ThemeFunctionTest.java         |  2 +-
 .../spring/model/UrlFunctionTest.java           | 49 +++++++++++----
 .../test/model/url-function-basic-usages.ftl    | 29 +++++++++
 14 files changed, 127 insertions(+), 69 deletions(-)
----------------------------------------------------------------------



[4/5] incubator-freemarker git commit: FREEMARKER-55: use ArgumentArrayLayout methods to get vargs arg index

Posted by dd...@apache.org.
FREEMARKER-55: use ArgumentArrayLayout methods to get vargs arg index


Project: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/commit/b0f27aa3
Tree: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/tree/b0f27aa3
Diff: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/diff/b0f27aa3

Branch: refs/heads/3
Commit: b0f27aa3d519a7354bb818924b7335998d58041a
Parents: b8dcdc0
Author: Woonsan Ko <wo...@apache.org>
Authored: Mon Sep 18 10:31:24 2017 -0400
Committer: Woonsan Ko <wo...@apache.org>
Committed: Mon Sep 18 10:31:24 2017 -0400

----------------------------------------------------------------------
 .../org/apache/freemarker/spring/model/EvalFunction.java  |  2 --
 .../apache/freemarker/spring/model/MessageFunction.java   |  4 ++--
 .../org/apache/freemarker/spring/model/UrlFunction.java   | 10 +++++-----
 3 files changed, 7 insertions(+), 9 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/b0f27aa3/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/EvalFunction.java
----------------------------------------------------------------------
diff --git a/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/EvalFunction.java b/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/EvalFunction.java
index 4710f6d..ed6e8fa 100644
--- a/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/EvalFunction.java
+++ b/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/EvalFunction.java
@@ -89,8 +89,6 @@ class EvalFunction extends AbstractSpringTemplateFunctionModel {
                     false
                     );
 
-    private static final String EVALUATION_CONTEXT_VAR_NAME = "org.apache.freemarker.spring.model.EVALUATION_CONTEXT";
-
     private final ExpressionParser expressionParser = new SpelExpressionParser();
 
     protected EvalFunction(HttpServletRequest request, HttpServletResponse response) {

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/b0f27aa3/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/MessageFunction.java
----------------------------------------------------------------------
diff --git a/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/MessageFunction.java b/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/MessageFunction.java
index e387a20..693633e 100644
--- a/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/MessageFunction.java
+++ b/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/MessageFunction.java
@@ -80,7 +80,6 @@ class MessageFunction extends AbstractSpringTemplateFunctionModel {
 
     private static final int CODE_PARAM_IDX = 0;
     private static final int MESSAGE_RESOLVABLE_PARAM_IDX = 1;
-    private static final int MESSAGE_ARGS_PARAM_IDX = 2;
 
     private static final String MESSAGE_RESOLVABLE_PARAM_NAME = "message";
 
@@ -119,7 +118,8 @@ class MessageFunction extends AbstractSpringTemplateFunctionModel {
 
             if (code != null) {
                 List<Object> msgArgumentList = null;
-                final TemplateCollectionModel messageArgsModel = (TemplateCollectionModel) args[MESSAGE_ARGS_PARAM_IDX];
+                final TemplateCollectionModel messageArgsModel = (TemplateCollectionModel) args[ARGS_LAYOUT
+                        .getPositionalVarargsArgumentIndex()];
 
                 if (!messageArgsModel.isEmptyCollection()) {
                     msgArgumentList = new ArrayList<>();

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/b0f27aa3/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/UrlFunction.java
----------------------------------------------------------------------
diff --git a/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/UrlFunction.java b/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/UrlFunction.java
index 0198eaf..71220b1 100644
--- a/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/UrlFunction.java
+++ b/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/UrlFunction.java
@@ -76,7 +76,6 @@ class UrlFunction extends AbstractSpringTemplateFunctionModel {
 
     private static final int VALUE_PARAM_IDX = 0;
     private static final int CONTEXT_PARAM_IDX = 1;
-    private static final int PARAMS_PARAM_IDX = 2;
 
     private static final String CONTEXT_PARAM_NAME = "context";
 
@@ -109,7 +108,8 @@ class UrlFunction extends AbstractSpringTemplateFunctionModel {
         final String context = CallableUtils.getOptionalStringArgument(args, CONTEXT_PARAM_IDX, this);
 
         List<_KeyValuePair<String, String>> params = Collections.emptyList();
-        final TemplateHashModelEx2 paramsHashModel = (TemplateHashModelEx2) args[PARAMS_PARAM_IDX];
+        final int paramsVarargsIndex = ARGS_LAYOUT.getNamedVarargsArgumentIndex();
+        final TemplateHashModelEx2 paramsHashModel = (TemplateHashModelEx2) args[paramsVarargsIndex];
 
         if (!paramsHashModel.isEmptyHash()) {
             params = new ArrayList<>();
@@ -120,14 +120,14 @@ class UrlFunction extends AbstractSpringTemplateFunctionModel {
                 TemplateModel paramValueModel = pair.getValue();
 
                 if (!(paramNameModel instanceof TemplateStringModel)) {
-                    throw CallableUtils.newArgumentValueException(PARAMS_PARAM_IDX,
+                    throw CallableUtils.newArgumentValueException(paramsVarargsIndex,
                             "Parameter name must be a string.", this);
                 }
 
                 String paramName = ((TemplateStringModel) paramNameModel).getAsString();
 
                 if (paramName.isEmpty()) {
-                    throw CallableUtils.newArgumentValueException(PARAMS_PARAM_IDX,
+                    throw CallableUtils.newArgumentValueException(paramsVarargsIndex,
                             "Parameter name must be a non-blank string.", this);
                 }
 
@@ -140,7 +140,7 @@ class UrlFunction extends AbstractSpringTemplateFunctionModel {
                 } else if (paramValueModel instanceof TemplateBooleanModel) {
                     paramValue = Boolean.toString(((TemplateBooleanModel) paramValueModel).getAsBoolean());
                 } else {
-                    throw CallableUtils.newArgumentValueException(PARAMS_PARAM_IDX,
+                    throw CallableUtils.newArgumentValueException(paramsVarargsIndex,
                             "Format the parameter manually to properly coerce it to a URL parameter value string. "
                                     + "e.g, date?string.iso, date?long, list?join('_'), etc.",
                             this);