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
+ }
}