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:19 UTC

[18/36] incubator-freemarker git commit: FREEMARKER-55: Adding url function

FREEMARKER-55: Adding url function


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

Branch: refs/heads/3
Commit: 8bdc5fdcf6507a00113e8f396cdf9a90c8c3857c
Parents: 6f025c7
Author: Woonsan Ko <wo...@apache.org>
Authored: Mon Sep 11 15:45:46 2017 -0400
Committer: Woonsan Ko <wo...@apache.org>
Committed: Mon Sep 11 15:45:46 2017 -0400

----------------------------------------------------------------------
 .../freemarker/spring/model/UrlFunction.java    | 189 ++++++++++++++++++-
 1 file changed, 179 insertions(+), 10 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/8bdc5fdc/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 402e74d..fe1b1fd 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
@@ -19,6 +19,14 @@
 
 package org.apache.freemarker.spring.model;
 
+import java.io.UnsupportedEncodingException;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
@@ -27,9 +35,16 @@ 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.TemplateHashModelEx2;
+import org.apache.freemarker.core.model.TemplateHashModelEx2.KeyValuePairIterator;
 import org.apache.freemarker.core.model.TemplateModel;
+import org.apache.freemarker.core.model.TemplateStringModel;
+import org.apache.freemarker.core.util.CallableUtils;
 import org.apache.freemarker.core.util.StringToIndexMap;
+import org.apache.freemarker.core.util._KeyValuePair;
 import org.springframework.web.servlet.support.RequestContext;
+import org.springframework.web.servlet.support.RequestDataValueProcessor;
+import org.springframework.web.util.UriUtils;
 
 /**
  * A <code>TemplateFunctionModel</code> providing functionality equivalent to the Spring Framework's
@@ -51,20 +66,21 @@ public 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";
 
+    /**
+     * Absolute URL pattern. e.g, http(s)://example.com, mailto:john@example.com, tel:123-456-7890.
+     */
+    private static final Pattern ABS_URL_PATTERN = Pattern.compile("^((([A-Za-z]+?:)?\\/\\/)|[A-Za-z]+:)[\\w.-]+");
 
-    // TODO: How to deal with parameters (spring:param tags)??
+    private static final String URL_TEMPLATE_DELIMITER_PREFIX = "{";
 
+    private static final String URL_TEMPLATE_DELIMITER_SUFFIX = "}";
 
-    private static final ArgumentArrayLayout ARGS_LAYOUT =
-            ArgumentArrayLayout.create(
-                    1,
-                    false,
-                    StringToIndexMap.of(CONTEXT_PARAM_NAME, CONTEXT_PARAM_IDX),
-                    false
-                    );
+    private static final ArgumentArrayLayout ARGS_LAYOUT = ArgumentArrayLayout.create(1, false,
+            StringToIndexMap.of(CONTEXT_PARAM_NAME, CONTEXT_PARAM_IDX), true);
 
     public UrlFunction(HttpServletRequest request, HttpServletResponse response) {
         super(request, response);
@@ -74,8 +90,75 @@ public class UrlFunction extends AbstractSpringTemplateFunctionModel {
     public TemplateModel executeInternal(TemplateModel[] args, CallPlace callPlace, Environment env,
             ObjectWrapperAndUnwrapper objectWrapperAndUnwrapper, RequestContext requestContext)
                     throws TemplateException {
-        // TODO
-        return null;
+        final String value = CallableUtils.getStringArgument(args, VALUE_PARAM_IDX, this);
+        final String context = CallableUtils.getOptionalStringArgument(args, CONTEXT_PARAM_IDX, this);
+
+        List<_KeyValuePair<String, String>> params = null;
+        final TemplateHashModelEx2 paramsHashModel = (TemplateHashModelEx2) args[PARAMS_PARAM_IDX];
+
+        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();
+
+                if ((paramNameModel instanceof TemplateStringModel)
+                        && (paramValueModel instanceof TemplateStringModel)) {
+                    paramName = ((TemplateStringModel) paramNameModel).getAsString();
+                    paramValue = ((TemplateStringModel) paramValueModel).getAsString();
+
+                    if (paramName.isEmpty()) {
+                        CallableUtils.newArgumentValueException(PARAMS_PARAM_IDX,
+                                "Parameter name must be a non-blank string.", this);
+                    }
+
+                    params.add(new _KeyValuePair<String, String>(paramName, paramValue));
+                } else {
+                    CallableUtils.newArgumentValueException(PARAMS_PARAM_IDX,
+                            "Parameter name and value must be string.", this);
+                }
+            }
+        }
+
+        final UrlType urlType = determineUrlType(value);
+
+        StringBuilder urlBuilder = new StringBuilder();
+
+        if (urlType == UrlType.CONTEXT_RELATIVE) {
+            if (context == null) {
+                urlBuilder.append(getRequest().getContextPath());
+            } else if (context.endsWith("/")) {
+                urlBuilder.append(context.substring(0, context.length() - 1));
+            } else {
+                urlBuilder.append(context);
+            }
+        }
+
+        Set<String> templateParams = new HashSet<>();
+        urlBuilder.append(replaceUriTemplateParams(value, params, templateParams));
+        urlBuilder.append(createQueryString(params, templateParams, (urlBuilder.indexOf("?") == -1)));
+
+        String urlString = urlBuilder.toString();
+
+        if (urlType != UrlType.ABSOLUTE) {
+            urlString = getResponse().encodeURL(urlString);
+        }
+
+        RequestDataValueProcessor processor = requestContext.getRequestDataValueProcessor();
+
+        if ((processor != null) && (getRequest() instanceof HttpServletRequest)) {
+            urlString = processor.processUrl(getRequest(), urlString);
+        }
+
+        return wrapObject(objectWrapperAndUnwrapper, urlString);
     }
 
     @Override
@@ -83,4 +166,90 @@ public class UrlFunction extends AbstractSpringTemplateFunctionModel {
         return ARGS_LAYOUT;
     }
 
+    private UrlType determineUrlType(final String value) {
+        Matcher m = ABS_URL_PATTERN.matcher(value);
+
+        if (m.matches()) {
+            return UrlType.ABSOLUTE;
+        } else if (value.startsWith("/")) {
+            return UrlType.CONTEXT_RELATIVE;
+        } else {
+            return UrlType.RELATIVE;
+        }
+    }
+
+    private String replaceUriTemplateParams(String uri, List<_KeyValuePair<String, String>> params, Set<String> usedParams)
+            throws TemplateException {
+        final String encoding = getResponse().getCharacterEncoding();
+
+        String paramName;
+        String paramValue;
+
+        for (_KeyValuePair<String, String> pair : params) {
+            paramName = pair.getKey();
+            paramValue = pair.getValue();
+
+            String template = URL_TEMPLATE_DELIMITER_PREFIX + paramName + URL_TEMPLATE_DELIMITER_SUFFIX;
+
+            if (uri.contains(template)) {
+                usedParams.add(paramName);
+
+                try {
+                    uri = uri.replace(template, UriUtils.encodePath(paramValue, encoding));
+                } catch (UnsupportedEncodingException e) {
+                    CallableUtils.newGenericExecuteException("Cannot encode URI. " + e, this);
+                }
+            } else {
+                template = URL_TEMPLATE_DELIMITER_PREFIX + '/' + paramName + URL_TEMPLATE_DELIMITER_SUFFIX;
+
+                if (uri.contains(template)) {
+                    usedParams.add(paramName);
+
+                    try {
+                        uri = uri.replace(template, UriUtils.encodePathSegment(paramValue, encoding));
+                    } catch (UnsupportedEncodingException e) {
+                        CallableUtils.newGenericExecuteException("Cannot encode URI. " + e, this);
+                    }
+                }
+            }
+        }
+
+        return uri;
+    }
+
+    private String createQueryString(List<_KeyValuePair<String, String>> params, Set<String> usedParams, boolean includeQueryStringDelimiter)
+            throws TemplateException {
+        final String encoding = getResponse().getCharacterEncoding();
+        final StringBuilder queryStringBuilder = new StringBuilder();
+
+        String paramName;
+        String paramValue;
+
+        for (_KeyValuePair<String, String> pair : params) {
+            paramName = pair.getKey();
+            paramValue = pair.getValue();
+
+            if (!usedParams.contains(paramName)) {
+                queryStringBuilder
+                        .append((includeQueryStringDelimiter && queryStringBuilder.length() == 0) ? "?" : "&");
+
+                try {
+                    queryStringBuilder.append(UriUtils.encodeQueryParam(paramName, encoding));
+
+                    if (paramValue != null) {
+                        queryStringBuilder.append('=');
+                        queryStringBuilder.append(UriUtils.encodeQueryParam(paramValue, encoding));
+                    }
+                } catch (UnsupportedEncodingException e) {
+                    CallableUtils.newGenericExecuteException("Cannot encode query parameter. " + e, this);
+                }
+            }
+        }
+
+        return queryStringBuilder.toString();
+    }
+
+    private enum UrlType {
+        CONTEXT_RELATIVE, RELATIVE, ABSOLUTE
+    }
 }