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/13 09:49:16 UTC

[15/36] incubator-freemarker git commit: FREEMARKER-55: Adding more functions.

FREEMARKER-55: Adding more functions.


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

Branch: refs/heads/3
Commit: 234f96ec2c2868c72f797bda42a8d0c854e63b87
Parents: d0bd43c
Author: Woonsan Ko <wo...@apache.org>
Authored: Fri Sep 8 12:21:53 2017 -0400
Committer: Woonsan Ko <wo...@apache.org>
Committed: Fri Sep 8 12:21:53 2017 -0400

----------------------------------------------------------------------
 .../AbstractSpringTemplateCallableModel.java    |  11 +-
 .../spring/model/BindErrorsDirective.java       |   5 +-
 .../freemarker/spring/model/EvalFunction.java   | 153 +++++++++++++++++++
 .../spring/model/MessageFunction.java           |  14 +-
 .../freemarker/spring/model/MvcUrlFunction.java |  74 +++++++++
 .../model/SpringTemplateCallableHashModel.java  |   7 +-
 .../spring/model/TransformFunction.java         | 101 ++++++++++++
 .../freemarker/spring/model/UrlFunction.java    |  86 +++++++++++
 8 files changed, 437 insertions(+), 14 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/234f96ec/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 de95df5..0d5d6ed 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
@@ -73,14 +73,21 @@ public abstract class AbstractSpringTemplateCallableModel implements TemplateCal
             RequestContext requestContext, String path, boolean ignoreNestedPath) throws TemplateException {
         final String resolvedPath = (ignoreNestedPath) ? path : resolveNestedPath(env, objectWrapperAndUnwrapper, path);
         BindStatus status = requestContext.getBindStatus(resolvedPath, false);
+        return wrapObject(objectWrapperAndUnwrapper, status);
+    }
+
+    protected final Object unwrapObject(ObjectWrapperAndUnwrapper objectWrapperAndUnwrapper, TemplateModel model) throws TemplateException {
+        return (model != null) ? objectWrapperAndUnwrapper.unwrap(model) : null;
+    }
 
-        if (status != null) {
+    protected final TemplateModel wrapObject(ObjectWrapperAndUnwrapper objectWrapperAndUnwrapper, Object object) throws TemplateException {
+        if (object != null) {
             if (!(objectWrapperAndUnwrapper instanceof DefaultObjectWrapper)) {
                 CallableUtils.newGenericExecuteException("objectWrapperAndUnwrapper is not a DefaultObjectWrapper.",
                         this, isFunction());
             }
 
-            return ((DefaultObjectWrapper) objectWrapperAndUnwrapper).wrap(status);
+            return ((DefaultObjectWrapper) objectWrapperAndUnwrapper).wrap(object);
         }
 
         return null;

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/234f96ec/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/BindErrorsDirective.java
----------------------------------------------------------------------
diff --git a/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/BindErrorsDirective.java b/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/BindErrorsDirective.java
index c98db67..e35b6ee 100644
--- a/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/BindErrorsDirective.java
+++ b/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/BindErrorsDirective.java
@@ -30,7 +30,6 @@ 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.ObjectWrappingException;
 import org.apache.freemarker.core.model.TemplateModel;
 import org.apache.freemarker.core.model.impl.DefaultObjectWrapper;
 import org.apache.freemarker.core.util.CallableUtils;
@@ -104,7 +103,7 @@ public class BindErrorsDirective extends AbstractSpringTemplateDirectiveModel {
     }
 
     private final TemplateModel getBindErrorsTemplateModel(Environment env, ObjectWrapperAndUnwrapper objectWrapperAndUnwrapper,
-            RequestContext requestContext, String name) throws ObjectWrappingException {
+            RequestContext requestContext, String name) throws TemplateException {
         final Errors errors = requestContext.getErrors(name, false);
 
         if (errors != null && errors.hasErrors()) {
@@ -113,7 +112,7 @@ public class BindErrorsDirective extends AbstractSpringTemplateDirectiveModel {
                         this, isFunction());
             }
 
-            return ((DefaultObjectWrapper) objectWrapperAndUnwrapper).wrap(errors);
+            return wrapObject(objectWrapperAndUnwrapper, errors);
         }
 
         return null;

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/234f96ec/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
new file mode 100644
index 0000000..a380f19
--- /dev/null
+++ b/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/EvalFunction.java
@@ -0,0 +1,153 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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
+ *
+ *   http://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.apache.freemarker.spring.model;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.freemarker.core.CallPlace;
+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.TemplateModel;
+import org.apache.freemarker.core.util.CallableUtils;
+import org.springframework.context.expression.BeanFactoryResolver;
+import org.springframework.context.expression.EnvironmentAccessor;
+import org.springframework.context.expression.MapAccessor;
+import org.springframework.core.convert.ConversionService;
+import org.springframework.expression.AccessException;
+import org.springframework.expression.EvaluationContext;
+import org.springframework.expression.Expression;
+import org.springframework.expression.ExpressionParser;
+import org.springframework.expression.PropertyAccessor;
+import org.springframework.expression.TypedValue;
+import org.springframework.expression.spel.standard.SpelExpressionParser;
+import org.springframework.expression.spel.support.StandardEvaluationContext;
+import org.springframework.expression.spel.support.StandardTypeConverter;
+import org.springframework.web.servlet.support.RequestContext;
+
+/**
+ * A <code>TemplateFunctionModel</code> providing functionality equivalent to the Spring Framework's
+ * <code>&lt;spring:eval /&gt;</code> JSP Tag Library.
+ * <P>
+ * Some valid example(s):
+ * </P>
+ * <PRE>
+ * </PRE>
+ * <P>
+ * <EM>Note:</EM> Unlike Spring Framework's <code>&lt;spring:message /&gt;</code> JSP Tag Library, this function
+ * does not support <code>htmlEscape</code> parameter. It always returns the message not to escape HTML's
+ * because it is much easier to control escaping in FreeMarker Template expressions.
+ * </P>
+ */
+public class EvalFunction extends AbstractSpringTemplateFunctionModel {
+
+    public static final String NAME = "eval";
+
+    private static final int EXPRESSION_PARAM_IDX = 0;
+
+    private static final ArgumentArrayLayout ARGS_LAYOUT = ArgumentArrayLayout.create(1, false, null, false);
+
+    private final ExpressionParser expressionParser = new SpelExpressionParser();
+
+    public EvalFunction(HttpServletRequest request, HttpServletResponse response) {
+        super(request, response);
+    }
+
+    @Override
+    public TemplateModel executeInternal(TemplateModel[] args, CallPlace callPlace, Environment env,
+            ObjectWrapperAndUnwrapper objectWrapperAndUnwrapper, RequestContext requestContext)
+                    throws TemplateException {
+        final String expressionString = CallableUtils.getStringArgument(args, EXPRESSION_PARAM_IDX, this);
+        final Expression expression = expressionParser.parseExpression(expressionString);
+
+        // TODO: cache evaluationContext somewhere in request level....
+        EvaluationContext evaluationContext = createEvaluationContext(env, requestContext);
+
+        final Object result = expression.getValue(evaluationContext);
+        return wrapObject(objectWrapperAndUnwrapper, result);
+    }
+
+    @Override
+    public ArgumentArrayLayout getFunctionArgumentArrayLayout() {
+        return ARGS_LAYOUT;
+    }
+
+    private EvaluationContext createEvaluationContext(final Environment env, final RequestContext requestContext) {
+        StandardEvaluationContext context = new StandardEvaluationContext();
+
+        context.addPropertyAccessor(new EnvironmentVariablesPropertyAccessor(env));
+        context.addPropertyAccessor(new MapAccessor());
+        context.addPropertyAccessor(new EnvironmentAccessor());
+        context.setBeanResolver(new BeanFactoryResolver(requestContext.getWebApplicationContext()));
+
+        ConversionService conversionService = getConversionService();
+
+        if (conversionService != null) {
+            context.setTypeConverter(new StandardTypeConverter(conversionService));
+        }
+
+        return context;
+    }
+
+    private ConversionService getConversionService() {
+        return (ConversionService) getRequest().getAttribute(ConversionService.class.getName());
+    }
+
+    private static class EnvironmentVariablesPropertyAccessor implements PropertyAccessor {
+
+        private final Environment env;
+
+        public EnvironmentVariablesPropertyAccessor(final Environment env) {
+            this.env = env;
+        }
+
+        @Override
+        public Class<?>[] getSpecificTargetClasses() {
+            // TODO Auto-generated method stub
+            return null;
+        }
+
+        @Override
+        public boolean canRead(EvaluationContext context, Object target, String name) throws AccessException {
+            // TODO Auto-generated method stub
+            return false;
+        }
+
+        @Override
+        public TypedValue read(EvaluationContext context, Object target, String name) throws AccessException {
+            // TODO Auto-generated method stub
+            return null;
+        }
+
+        @Override
+        public boolean canWrite(EvaluationContext context, Object target, String name) throws AccessException {
+            // TODO Auto-generated method stub
+            return false;
+        }
+
+        @Override
+        public void write(EvaluationContext context, Object target, String name, Object newValue)
+                throws AccessException {
+            // TODO Auto-generated method stub
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/234f96ec/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 46547a6..94a1e0f 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
@@ -33,7 +33,6 @@ import org.apache.freemarker.core.model.ObjectWrapperAndUnwrapper;
 import org.apache.freemarker.core.model.TemplateCollectionModel;
 import org.apache.freemarker.core.model.TemplateModel;
 import org.apache.freemarker.core.model.TemplateModelIterator;
-import org.apache.freemarker.core.model.impl.SimpleString;
 import org.apache.freemarker.core.util.CallableUtils;
 import org.apache.freemarker.core.util.StringToIndexMap;
 import org.apache.freemarker.core.util._StringUtils;
@@ -111,10 +110,10 @@ public class MessageFunction extends AbstractSpringTemplateFunctionModel {
 
         final TemplateModel messageResolvableModel = CallableUtils.getOptionalArgument(args, MESSAGE_RESOLVABLE_PARAM_IDX,
                 TemplateModel.class, this);
+        final MessageSourceResolvable messageResolvable = (MessageSourceResolvable) unwrapObject(
+                objectWrapperAndUnwrapper, messageResolvableModel);
 
-        if (messageResolvableModel != null) {
-            MessageSourceResolvable messageResolvable = (MessageSourceResolvable) objectWrapperAndUnwrapper
-                    .unwrap(messageResolvableModel);
+        if (messageResolvable != null) {
             message = messageSource.getMessage(messageResolvable, requestContext.getLocale());
         } else {
             final String code = _StringUtils
@@ -127,10 +126,9 @@ public class MessageFunction extends AbstractSpringTemplateFunctionModel {
                 if (!messageArgsModel.isEmptyCollection()) {
                     msgArgumentList = new ArrayList<>();
                     TemplateModel msgArgModel;
-                    int i = 0;
-                    for (TemplateModelIterator tit = messageArgsModel.iterator(); tit.hasNext(); i++) {
+                    for (TemplateModelIterator tit = messageArgsModel.iterator(); tit.hasNext(); ) {
                         msgArgModel = tit.next();
-                        msgArgumentList.add(objectWrapperAndUnwrapper.unwrap(msgArgModel));
+                        msgArgumentList.add(unwrapObject(objectWrapperAndUnwrapper, msgArgModel));
                     }
                 }
 
@@ -143,7 +141,7 @@ public class MessageFunction extends AbstractSpringTemplateFunctionModel {
             }
         }
 
-        return (message != null) ? new SimpleString(message) : null;
+        return wrapObject(objectWrapperAndUnwrapper, message);
     }
 
     @Override

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/234f96ec/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/MvcUrlFunction.java
----------------------------------------------------------------------
diff --git a/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/MvcUrlFunction.java b/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/MvcUrlFunction.java
new file mode 100644
index 0000000..047d5c2
--- /dev/null
+++ b/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/MvcUrlFunction.java
@@ -0,0 +1,74 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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
+ *
+ *   http://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.apache.freemarker.spring.model;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.freemarker.core.CallPlace;
+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.TemplateModel;
+import org.apache.freemarker.core.util.StringToIndexMap;
+import org.springframework.web.servlet.support.RequestContext;
+
+/**
+ * A <code>TemplateFunctionModel</code> providing functionality equivalent to the Spring Framework's
+ * <code>spring:mvcUrl</code> JSP Tag Library Function.
+ * <P>
+ * Some valid example(s):
+ * </P>
+ * <PRE>
+ * </PRE>
+ */
+public class MvcUrlFunction extends AbstractSpringTemplateFunctionModel {
+
+    public static final String NAME = "mvcUrl";
+
+    private static final int MAPPING_NAME_PARAM_IDX = 0;
+
+    private static final ArgumentArrayLayout ARGS_LAYOUT =
+            ArgumentArrayLayout.create(
+                    1,
+                    false,
+                    null,
+                    false
+                    );
+
+    public MvcUrlFunction(HttpServletRequest request, HttpServletResponse response) {
+        super(request, response);
+    }
+
+    @Override
+    public TemplateModel executeInternal(TemplateModel[] args, CallPlace callPlace, Environment env,
+            ObjectWrapperAndUnwrapper objectWrapperAndUnwrapper, RequestContext requestContext)
+                    throws TemplateException {
+        // TODO
+        return null;
+    }
+
+    @Override
+    public ArgumentArrayLayout getFunctionArgumentArrayLayout() {
+        return ARGS_LAYOUT;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/234f96ec/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/SpringTemplateCallableHashModel.java
----------------------------------------------------------------------
diff --git a/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/SpringTemplateCallableHashModel.java b/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/SpringTemplateCallableHashModel.java
index dd2f71c..e090cd9 100644
--- a/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/SpringTemplateCallableHashModel.java
+++ b/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/SpringTemplateCallableHashModel.java
@@ -47,11 +47,16 @@ public final class SpringTemplateCallableHashModel implements TemplateHashModel,
     private String nestedPath;
 
     public SpringTemplateCallableHashModel(final HttpServletRequest request, final HttpServletResponse response) {
-        callablesMap.put(BindDirective.NAME, new BindDirective(request, response));
         callablesMap.put(MessageFunction.NAME, new MessageFunction(request, response));
         callablesMap.put(ThemeFunction.NAME, new ThemeFunction(request, response));
         callablesMap.put(BindErrorsDirective.NAME, new BindErrorsDirective(request, response));
         callablesMap.put(NestedPathDirective.NAME, new NestedPathDirective(request, response));
+        callablesMap.put(BindDirective.NAME, new BindDirective(request, response));
+
+        callablesMap.put(TransformFunction.NAME, new TransformFunction(request, response));
+        callablesMap.put(UrlFunction.NAME, new UrlFunction(request, response));
+        callablesMap.put(EvalFunction.NAME, new EvalFunction(request, response));
+        callablesMap.put(MvcUrlFunction.NAME, new MvcUrlFunction(request, response));
     }
 
     public TemplateModel get(String key) throws TemplateException {

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/234f96ec/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/TransformFunction.java
----------------------------------------------------------------------
diff --git a/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/TransformFunction.java b/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/TransformFunction.java
new file mode 100644
index 0000000..ee3cc1c
--- /dev/null
+++ b/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/TransformFunction.java
@@ -0,0 +1,101 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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
+ *
+ *   http://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.apache.freemarker.spring.model;
+
+import java.beans.PropertyEditor;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.freemarker.core.CallPlace;
+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.TemplateModel;
+import org.apache.freemarker.core.util.CallableUtils;
+import org.springframework.web.servlet.support.RequestContext;
+
+/**
+ * A <code>TemplateFunctionModel</code> providing functionality equivalent to the Spring Framework's
+ * <code>&lt;spring:transform /&gt;</code> JSP Tag Library.
+ * <P>
+ * Some valid example(s):
+ * </P>
+ * <PRE>
+ * </PRE>
+ * <P>
+ * Some valid example(s):
+ * </P>
+ * <PRE>
+ *   &lt;@spring.bind "user"; status&gt;
+ *     ${spring.transform(status, user.birthDate)}
+ *   &lt;/@spring.bind&gt;
+ * </PRE>
+ * <P>
+ * <EM>Note:</EM> Unlike Spring Framework's <code>&lt;spring:bind /&gt;</code> JSP Tag Library, this directive
+ * does not support <code>htmlEscape</code> parameter. It always has <code>BindStatus</code> not to escape HTML's
+ * because it is much easier to control escaping in FreeMarker Template expressions.
+ * </P>
+ */
+public class TransformFunction extends AbstractSpringTemplateFunctionModel {
+
+    public static final String NAME = "transform";
+
+    private static final int PROPERTY_EDITOR_PARAM_IDX = 0;
+    private static final int VALUE_PARAM_IDX = 1;
+
+    private static final ArgumentArrayLayout ARGS_LAYOUT = ArgumentArrayLayout.create(2, false, null, false);
+
+    public TransformFunction(HttpServletRequest request, HttpServletResponse response) {
+        super(request, response);
+    }
+
+    @Override
+    public TemplateModel executeInternal(TemplateModel[] args, CallPlace callPlace, Environment env,
+            ObjectWrapperAndUnwrapper objectWrapperAndUnwrapper, RequestContext requestContext)
+                    throws TemplateException {
+        final TemplateModel editorModel = CallableUtils.getOptionalArgument(args, PROPERTY_EDITOR_PARAM_IDX,
+                TemplateModel.class, this);
+        final PropertyEditor editor = (PropertyEditor) unwrapObject(objectWrapperAndUnwrapper, editorModel);
+
+        final TemplateModel valueModel = CallableUtils.getOptionalArgument(args, VALUE_PARAM_IDX, TemplateModel.class, this);
+        final Object value = unwrapObject(objectWrapperAndUnwrapper, valueModel);
+
+        String valueAsString = null;
+
+        if (value != null) {
+            if (editor != null) {
+                editor.setValue(value);
+                valueAsString = editor.getAsText();
+            } else {
+                valueAsString = value.toString();
+            }
+        }
+
+        return wrapObject(objectWrapperAndUnwrapper, valueAsString);
+    }
+
+    @Override
+    public ArgumentArrayLayout getFunctionArgumentArrayLayout() {
+        return ARGS_LAYOUT;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/234f96ec/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
new file mode 100644
index 0000000..402e74d
--- /dev/null
+++ b/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/UrlFunction.java
@@ -0,0 +1,86 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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
+ *
+ *   http://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.apache.freemarker.spring.model;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.freemarker.core.CallPlace;
+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.TemplateModel;
+import org.apache.freemarker.core.util.StringToIndexMap;
+import org.springframework.web.servlet.support.RequestContext;
+
+/**
+ * A <code>TemplateFunctionModel</code> providing functionality equivalent to the Spring Framework's
+ * <code>&lt;spring:url /&gt;</code> JSP Tag Library.
+ * <P>
+ * Some valid example(s):
+ * </P>
+ * <PRE>
+ * </PRE>
+ * <P>
+ * <EM>Note:</EM> Unlike Spring Framework's <code>&lt;spring:message /&gt;</code> JSP Tag Library, this function
+ * does not support <code>htmlEscape</code> parameter. It always returns the message not to escape HTML's
+ * because it is much easier to control escaping in FreeMarker Template expressions.
+ * </P>
+ */
+public class UrlFunction extends AbstractSpringTemplateFunctionModel {
+
+    public static final String NAME = "url";
+
+    private static final int VALUE_PARAM_IDX = 0;
+    private static final int CONTEXT_PARAM_IDX = 1;
+
+    private static final String CONTEXT_PARAM_NAME = "context";
+
+
+    // TODO: How to deal with parameters (spring:param tags)??
+
+
+    private static final ArgumentArrayLayout ARGS_LAYOUT =
+            ArgumentArrayLayout.create(
+                    1,
+                    false,
+                    StringToIndexMap.of(CONTEXT_PARAM_NAME, CONTEXT_PARAM_IDX),
+                    false
+                    );
+
+    public UrlFunction(HttpServletRequest request, HttpServletResponse response) {
+        super(request, response);
+    }
+
+    @Override
+    public TemplateModel executeInternal(TemplateModel[] args, CallPlace callPlace, Environment env,
+            ObjectWrapperAndUnwrapper objectWrapperAndUnwrapper, RequestContext requestContext)
+                    throws TemplateException {
+        // TODO
+        return null;
+    }
+
+    @Override
+    public ArgumentArrayLayout getFunctionArgumentArrayLayout() {
+        return ARGS_LAYOUT;
+    }
+
+}