FREEMARKER-55: Adding initial message 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/3b83475f Tree: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/tree/3b83475f Diff: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/diff/3b83475f Branch: refs/heads/3 Commit: 3b83475f111019842b6070581ca310890ba199d1 Parents: f1e8a4d Author: Woonsan Ko <[email protected]> Authored: Sat Sep 2 23:49:38 2017 -0400 Committer: Woonsan Ko <[email protected]> Committed: Sat Sep 2 23:49:38 2017 -0400 ---------------------------------------------------------------------- .../AbstractSpringTemplateCallableModel.java | 106 +++++++++++++++++++ .../AbstractSpringTemplateDirectiveModel.java | 70 +----------- .../AbstractSpringTemplateFunctionModel.java | 69 ++++++++++++ .../freemarker/spring/model/BindDirective.java | 10 +- .../spring/model/MessageFunction.java | 98 +++++++++++++++++ .../spring/web/view/FreeMarkerView.java | 2 + 6 files changed, 282 insertions(+), 73 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3b83475f/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 new file mode 100644 index 0000000..431c066 --- /dev/null +++ b/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/AbstractSpringTemplateCallableModel.java @@ -0,0 +1,106 @@ +/* + * 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.Environment; +import org.apache.freemarker.core.model.ObjectWrapperAndUnwrapper; +import org.apache.freemarker.core.model.ObjectWrappingException; +import org.apache.freemarker.core.model.TemplateCallableModel; +import org.apache.freemarker.core.model.TemplateModel; +import org.apache.freemarker.core.model.impl.DefaultObjectWrapper; +import org.springframework.web.servlet.support.BindStatus; +import org.springframework.web.servlet.support.RequestContext; + +/** + * Abstract TemplateCallableModel for derived classes to support Spring MVC based templating environment. + */ +public abstract class AbstractSpringTemplateCallableModel implements TemplateCallableModel { + + // TODO: namespace this into 'spring.nestedPath'?? + /** + * @see <code>org.springframework.web.servlet.tags.NestedPathTag#NESTED_PATH_VARIABLE_NAME</code> + */ + private static final String NESTED_PATH_VARIABLE_NAME = "nestedPath"; + + private final HttpServletRequest request; + private final HttpServletResponse response; + + public AbstractSpringTemplateCallableModel(HttpServletRequest request, HttpServletResponse response) { + this.request = request; + this.response = response; + } + + protected final HttpServletRequest getRequest() { + return request; + } + + protected final HttpServletResponse getResponse() { + return response; + } + + /** + * Find {@link BindStatus} with no {@code htmlEscape} option from {@link RequestContext} by the {@code path} + * and wrap it as a {@link TemplateModel}. + * <P> + * <EM>NOTE:</EM> In FreeMarker, there is no need to depend on <code>BindStatus#htmlEscape</code> option + * as FreeMarker template expressions can easily set escape option by themselves. + * Therefore, this method always get a {@link BindStatus} with {@code htmlEscape} option set to {@code false}. + * @param env Environment + * @param objectWrapperAndUnwrapper ObjectWrapperAndUnwrapper + * @param requestContext Spring RequestContext + * @param path bind path + * @param ignoreNestedPath flag whether or not to ignore the nested path + * @return {@link TemplateModel} wrapping a {@link BindStatus} with no {@code htmlEscape} option from {@link RequestContext} + * by the {@code path} + * @throws ObjectWrappingException if fails to wrap the <code>BindStatus</code> object + */ + protected final TemplateModel getBindStatusTemplateModel(Environment env, ObjectWrapperAndUnwrapper objectWrapperAndUnwrapper, + RequestContext requestContext, String path, boolean ignoreNestedPath) throws ObjectWrappingException { + final String resolvedPath = (ignoreNestedPath) ? path : resolveNestedPath(env, objectWrapperAndUnwrapper, path); + BindStatus status = requestContext.getBindStatus(resolvedPath, false); + + if (status != null) { + if (!(objectWrapperAndUnwrapper instanceof DefaultObjectWrapper)) { + throw new IllegalArgumentException("objectWrapperAndUnwrapper is not a DefaultObjectWrapper."); + } + + return ((DefaultObjectWrapper) objectWrapperAndUnwrapper).wrap(status); + } + + return null; + } + + private String resolveNestedPath(final Environment env, ObjectWrapperAndUnwrapper objectWrapperAndUnwrapper, + final String path) { + // TODO: should read it from request or env?? + // or read spring.nestedPath first and read request attribute next?? + String nestedPath = (String) request.getAttribute(NESTED_PATH_VARIABLE_NAME); + + if (nestedPath != null && !path.startsWith(nestedPath) + && !path.equals(nestedPath.substring(0, nestedPath.length() - 1))) { + return nestedPath + path; + } + + return path; + } +} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3b83475f/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/AbstractSpringTemplateDirectiveModel.java ---------------------------------------------------------------------- diff --git a/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/AbstractSpringTemplateDirectiveModel.java b/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/AbstractSpringTemplateDirectiveModel.java index e1b34b4..c4ed76c 100644 --- a/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/AbstractSpringTemplateDirectiveModel.java +++ b/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/AbstractSpringTemplateDirectiveModel.java @@ -30,31 +30,18 @@ import org.apache.freemarker.core.Environment; import org.apache.freemarker.core.TemplateException; import org.apache.freemarker.core.model.ObjectWrapper; import org.apache.freemarker.core.model.ObjectWrapperAndUnwrapper; -import org.apache.freemarker.core.model.ObjectWrappingException; import org.apache.freemarker.core.model.TemplateDirectiveModel; import org.apache.freemarker.core.model.TemplateModel; -import org.apache.freemarker.core.model.impl.DefaultObjectWrapper; -import org.springframework.web.servlet.support.BindStatus; import org.springframework.web.servlet.support.RequestContext; import org.springframework.web.servlet.view.AbstractTemplateView; /** * Abstract TemplateDirectiveModel for derived classes to support Spring MVC based templating environment. */ -public abstract class AbstractSpringTemplateDirectiveModel implements TemplateDirectiveModel { - - // TODO: namespace this into 'spring.nestedPath'?? - /** - * @see <code>org.springframework.web.servlet.tags.NestedPathTag#NESTED_PATH_VARIABLE_NAME</code> - */ - private static final String NESTED_PATH_VARIABLE_NAME = "nestedPath"; - - private final HttpServletRequest request; - private final HttpServletResponse response; +public abstract class AbstractSpringTemplateDirectiveModel extends AbstractSpringTemplateCallableModel implements TemplateDirectiveModel { public AbstractSpringTemplateDirectiveModel(HttpServletRequest request, HttpServletResponse response) { - this.request = request; - this.response = response; + super(request, response); } @Override @@ -82,57 +69,4 @@ public abstract class AbstractSpringTemplateDirectiveModel implements TemplateDi ObjectWrapperAndUnwrapper objectWrapperAndUnwrapper, RequestContext requestContext) throws TemplateException, IOException; - protected final HttpServletRequest getRequest() { - return request; - } - - protected final HttpServletResponse getResponse() { - return response; - } - - /** - * Find {@link BindStatus} with no {@code htmlEscape} option from {@link RequestContext} by the {@code path} - * and wrap it as a {@link TemplateModel}. - * <P> - * <EM>NOTE:</EM> In FreeMarker, there is no need to depend on <code>BindStatus#htmlEscape</code> option - * as FreeMarker template expressions can easily set escape option by themselves. - * Therefore, this method always get a {@link BindStatus} with {@code htmlEscape} option set to {@code false}. - * @param env Environment - * @param objectWrapperAndUnwrapper ObjectWrapperAndUnwrapper - * @param requestContext Spring RequestContext - * @param path bind path - * @param ignoreNestedPath flag whether or not to ignore the nested path - * @return {@link TemplateModel} wrapping a {@link BindStatus} with no {@code htmlEscape} option from {@link RequestContext} - * by the {@code path} - * @throws ObjectWrappingException if fails to wrap the <code>BindStatus</code> object - */ - protected final TemplateModel getBindStatusTemplateModel(Environment env, ObjectWrapperAndUnwrapper objectWrapperAndUnwrapper, - RequestContext requestContext, String path, boolean ignoreNestedPath) throws ObjectWrappingException { - final String resolvedPath = (ignoreNestedPath) ? path : resolveNestedPath(env, objectWrapperAndUnwrapper, path); - BindStatus status = requestContext.getBindStatus(resolvedPath, false); - - if (status != null) { - if (!(objectWrapperAndUnwrapper instanceof DefaultObjectWrapper)) { - throw new IllegalArgumentException("objectWrapperAndUnwrapper is not a DefaultObjectWrapper."); - } - - return ((DefaultObjectWrapper) objectWrapperAndUnwrapper).wrap(status); - } - - return null; - } - - private String resolveNestedPath(final Environment env, ObjectWrapperAndUnwrapper objectWrapperAndUnwrapper, - final String path) { - // TODO: should read it from request or env?? - // or read spring.nestedPath first and read request attribute next?? - String nestedPath = (String) request.getAttribute(NESTED_PATH_VARIABLE_NAME); - - if (nestedPath != null && !path.startsWith(nestedPath) - && !path.equals(nestedPath.substring(0, nestedPath.length() - 1))) { - return nestedPath + path; - } - - return path; - } } http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3b83475f/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/AbstractSpringTemplateFunctionModel.java ---------------------------------------------------------------------- diff --git a/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/AbstractSpringTemplateFunctionModel.java b/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/AbstractSpringTemplateFunctionModel.java new file mode 100644 index 0000000..c0fe502 --- /dev/null +++ b/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/AbstractSpringTemplateFunctionModel.java @@ -0,0 +1,69 @@ +/* + * 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.ObjectWrapper; +import org.apache.freemarker.core.model.ObjectWrapperAndUnwrapper; +import org.apache.freemarker.core.model.TemplateFunctionModel; +import org.apache.freemarker.core.model.TemplateModel; +import org.springframework.web.servlet.support.RequestContext; +import org.springframework.web.servlet.view.AbstractTemplateView; + +/** + * Abstract TemplateFunctionModel for derived classes to support Spring MVC based templating environment. + */ +public abstract class AbstractSpringTemplateFunctionModel extends AbstractSpringTemplateCallableModel + implements TemplateFunctionModel { + + public AbstractSpringTemplateFunctionModel(HttpServletRequest request, HttpServletResponse response) { + super(request, response); + } + + @Override + public TemplateModel execute(TemplateModel[] args, CallPlace callPlace, Environment env) throws TemplateException { + final ObjectWrapper objectWrapper = env.getObjectWrapper(); + + if (!(objectWrapper instanceof ObjectWrapperAndUnwrapper)) { + throw new TemplateException( + "The ObjectWrapper of environment wasn't instance of ObjectWrapperAndUnwrapper."); + } + + TemplateModel rcModel = env.getVariable(AbstractTemplateView.SPRING_MACRO_REQUEST_CONTEXT_ATTRIBUTE); + + if (rcModel == null) { + throw new TemplateException(AbstractTemplateView.SPRING_MACRO_REQUEST_CONTEXT_ATTRIBUTE + " not found."); + } + + RequestContext requestContext = (RequestContext) ((ObjectWrapperAndUnwrapper) objectWrapper).unwrap(rcModel); + + return executeInternal(args, callPlace, env, (ObjectWrapperAndUnwrapper) objectWrapper, requestContext); + } + + protected abstract TemplateModel executeInternal(TemplateModel[] args, CallPlace callPlace, Environment env, + ObjectWrapperAndUnwrapper objectWrapperAndUnwrapper, RequestContext requestContext) + throws TemplateException; + +} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3b83475f/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/BindDirective.java ---------------------------------------------------------------------- diff --git a/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/BindDirective.java b/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/BindDirective.java index b4b8ad9..0fbbe2f 100644 --- a/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/BindDirective.java +++ b/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/BindDirective.java @@ -75,12 +75,12 @@ public class BindDirective extends AbstractSpringTemplateDirectiveModel { ObjectWrapperAndUnwrapper objectWrapperAndUnwrapper, RequestContext requestContext) throws TemplateException, IOException { final String path = CallableUtils.getStringArgument(args, PATH_PARAM_IDX, this); - boolean ignoreNestedPath = CallableUtils.getOptionalBooleanArgument(args, IGNORE_NESTED_PATH_PARAM_IDX, this, - false); + final boolean ignoreNestedPath = CallableUtils.getOptionalBooleanArgument(args, IGNORE_NESTED_PATH_PARAM_IDX, + this, false); - TemplateModel statusModel = getBindStatusTemplateModel(env, objectWrapperAndUnwrapper, requestContext, path, - ignoreNestedPath); - TemplateModel[] nestedContentArgs = new TemplateModel[] { statusModel }; + final TemplateModel statusModel = getBindStatusTemplateModel(env, objectWrapperAndUnwrapper, requestContext, + path, ignoreNestedPath); + final TemplateModel[] nestedContentArgs = new TemplateModel[] { statusModel }; callPlace.executeNestedContent(nestedContentArgs, out, env); } http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3b83475f/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 new file mode 100644 index 0000000..d440963 --- /dev/null +++ b/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/MessageFunction.java @@ -0,0 +1,98 @@ +/* + * 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.util.List; + +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.model.impl.SimpleString; +import org.apache.freemarker.core.util.CallableUtils; +import org.apache.freemarker.core.util.StringToIndexMap; +import org.springframework.context.MessageSource; +import org.springframework.context.MessageSourceResolvable; +import org.springframework.web.servlet.support.RequestContext; + +public class MessageFunction extends AbstractSpringTemplateFunctionModel { + + private static final int CODE_PARAM_IDX = 0; + private static final int MESSAGE_PARAM_IDX = 1; + + private static final String MESSAGE_PARAM_NAME = "message"; + + private static final ArgumentArrayLayout ARGS_LAYOUT = + ArgumentArrayLayout.create( + 1, + true, + StringToIndexMap.of( + MESSAGE_PARAM_NAME, MESSAGE_PARAM_IDX), + false); + + public MessageFunction(HttpServletRequest request, HttpServletResponse response) { + super(request, response); + } + + @Override + public TemplateModel executeInternal(TemplateModel[] args, CallPlace callPlace, Environment env, + ObjectWrapperAndUnwrapper objectWrapperAndUnwrapper, RequestContext requestContext) + throws TemplateException { + final MessageSource messageSource = requestContext.getMessageSource(); + + if (messageSource == null) { + throw new TemplateException("MessageSource not found."); + } + + String message = null; + + final String code = CallableUtils.getStringArgument(args, CODE_PARAM_IDX, this); + + if (code != null && !code.isEmpty()) { + List<Object> msgArgumentList = null; + // TODO: How to read message arguments from the varags? + + // TODO: Is it okay to set the default value to null to avoid NoSuchMessageException from Spring MessageSource? + message = messageSource.getMessage(code, (msgArgumentList == null) ? null : msgArgumentList.toArray(), + null, requestContext.getLocale()); + } else { + final TemplateModel messageModel = CallableUtils.getOptionalArgument(args, MESSAGE_PARAM_IDX, + TemplateModel.class, this); + if (messageModel != null) { + MessageSourceResolvable messageResolvable = (MessageSourceResolvable) objectWrapperAndUnwrapper + .unwrap(messageModel); + message = messageSource.getMessage(messageResolvable, requestContext.getLocale()); + } + } + + return (message != null) ? new SimpleString(message) : null; + } + + @Override + public ArgumentArrayLayout getFunctionArgumentArrayLayout() { + return ARGS_LAYOUT; + } + +} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3b83475f/freemarker-spring/src/main/java/org/apache/freemarker/spring/web/view/FreeMarkerView.java ---------------------------------------------------------------------- diff --git a/freemarker-spring/src/main/java/org/apache/freemarker/spring/web/view/FreeMarkerView.java b/freemarker-spring/src/main/java/org/apache/freemarker/spring/web/view/FreeMarkerView.java index 1e94a97..f34fd4b 100644 --- a/freemarker-spring/src/main/java/org/apache/freemarker/spring/web/view/FreeMarkerView.java +++ b/freemarker-spring/src/main/java/org/apache/freemarker/spring/web/view/FreeMarkerView.java @@ -39,6 +39,7 @@ import org.apache.freemarker.servlet.IncludePage; import org.apache.freemarker.servlet.ServletContextHashModel; import org.apache.freemarker.servlet.jsp.TaglibFactory; import org.apache.freemarker.spring.model.BindDirective; +import org.apache.freemarker.spring.model.MessageFunction; /** * FreeMarker template based view implementation, with being able to provide a {@link ServletContextHashModel} @@ -178,6 +179,7 @@ public class FreeMarkerView extends AbstractFreeMarkerView { final HttpServletRequest request, final HttpServletResponse response) { final SimpleHash springCallableHash = new SimpleHash(objectWrapper); springCallableHash.put("bind", new BindDirective(request, response)); + springCallableHash.put("message", new MessageFunction(request, response)); return springCallableHash; } }
