diff --git a/spring-mvc-extension/README.md b/spring-mvc-extension/README.md
new file mode 100644
index 000000000..79774cb8f
--- /dev/null
+++ b/spring-mvc-extension/README.md
@@ -0,0 +1,55 @@
+#Mustache extension for Spring MVC
+
+To enable mustache views in your Spring MVC application add the `@EnableMustache` annotation to your configuration class.
+
+###Support for messages and themes
+
+This extension provides annotations to let you handle message resolving in your model attributes.
+
+
+
+
+ Annotation
+ Description
+
+
+
+ @Message
+ Annotated methods will have their return values resolved to a message.
+
+
+ @Theme
+ Annotated methods will have their return values resolved to a theme message.
+
+
+
+
+Both `@Message` and `@Theme` can resolve embedded values.
+
+ public class MustacheGreetingContext {
+
+ private final String name;
+
+ public MustacheGreetingContext() {
+ this("name");
+ }
+
+ public MustacheGreetingContext(final String name) {
+ this.name = name;
+ }
+
+ @Message
+ public String getGreeting() {
+ return "${my.greeting.property}";
+ }
+
+ @Message
+ public String getName() {
+ return name;
+ }
+
+ @Theme
+ public String getColor() {
+ return "color";
+ }
+ }
diff --git a/spring-mvc-extension/pom.xml b/spring-mvc-extension/pom.xml
new file mode 100644
index 000000000..e095ea39a
--- /dev/null
+++ b/spring-mvc-extension/pom.xml
@@ -0,0 +1,113 @@
+
+
+ 4.0.0
+
+ nl.onedott.mustachejava.extension
+ springmvc
+ 0.9.0
+ Spring MVC extension for mustache.java
+ http://github.com/spullara/mustache.java
+
+
+
+ Bart Tegenbosch
+ bart@onedott.nl
+ Onedott
+ http://www.onedott.nl
+
+
+
+
+ 3.2.0.RELEASE
+
+
+
+
+
+ com.github.spullara.mustache.java
+ compiler
+ 0.8.16
+
+
+
+ javax.servlet
+ javax.servlet-api
+ 3.0.1
+ provided
+
+
+
+ org.springframework
+ spring-webmvc
+ ${spring.version}
+
+
+
+ org.springframework
+ spring-test
+ ${spring.version}
+ compile
+
+
+
+ org.mockito
+ mockito-all
+ 1.9.5
+ test
+
+
+
+ junit
+ junit
+ 4.11
+ test
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+ 2.9
+
+ -Xmx128M
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+ 1.7
+ 1.7
+ UTF-8
+
+
+
+ org.apache.maven.plugins
+ maven-javadoc-plugin
+
+
+ attach-javadocs
+
+ jar
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-source-plugin
+
+
+ attach-sources
+
+ jar
+
+
+
+
+
+
+
diff --git a/spring-mvc-extension/src/main/java/nl/onedott/mustachejava/extension/springmvc/SpringMustacheException.java b/spring-mvc-extension/src/main/java/nl/onedott/mustachejava/extension/springmvc/SpringMustacheException.java
new file mode 100644
index 000000000..4662b3ee9
--- /dev/null
+++ b/spring-mvc-extension/src/main/java/nl/onedott/mustachejava/extension/springmvc/SpringMustacheException.java
@@ -0,0 +1,20 @@
+package nl.onedott.mustachejava.extension.springmvc;
+
+import com.github.mustachejava.MustacheException;
+
+/**
+ * @author Bart Tegenbosch
+ */
+public class SpringMustacheException extends MustacheException {
+ public SpringMustacheException(final String message) {
+ super(message);
+ }
+
+ public SpringMustacheException(final String message, final Throwable cause) {
+ super(message, cause);
+ }
+
+ public SpringMustacheException(final Throwable cause) {
+ super(cause);
+ }
+}
diff --git a/spring-mvc-extension/src/main/java/nl/onedott/mustachejava/extension/springmvc/annotation/EnableMustache.java b/spring-mvc-extension/src/main/java/nl/onedott/mustachejava/extension/springmvc/annotation/EnableMustache.java
new file mode 100644
index 000000000..006ed98a0
--- /dev/null
+++ b/spring-mvc-extension/src/main/java/nl/onedott/mustachejava/extension/springmvc/annotation/EnableMustache.java
@@ -0,0 +1,23 @@
+package nl.onedott.mustachejava.extension.springmvc.annotation;
+
+import nl.onedott.mustachejava.extension.springmvc.config.MustacheConfiguration;
+import org.springframework.context.annotation.Import;
+
+import java.lang.annotation.*;
+
+/**
+ * Enable Mustache views and model processing for Spring MVC.
+ *
+ * @author Bart Tegenbosch
+ */
+@Documented
+@Target(ElementType.TYPE)
+@Retention(RetentionPolicy.RUNTIME)
+@Import(MustacheConfiguration.class)
+public @interface EnableMustache {
+ /*
+ * The resource location for mustache templates.
+ * Supports resource prefixes like {@code file:} and {@code classpath:}
+ */
+ String value() default "mustache/";
+}
diff --git a/spring-mvc-extension/src/main/java/nl/onedott/mustachejava/extension/springmvc/annotation/Message.java b/spring-mvc-extension/src/main/java/nl/onedott/mustachejava/extension/springmvc/annotation/Message.java
new file mode 100644
index 000000000..ef06c5bad
--- /dev/null
+++ b/spring-mvc-extension/src/main/java/nl/onedott/mustachejava/extension/springmvc/annotation/Message.java
@@ -0,0 +1,13 @@
+package nl.onedott.mustachejava.extension.springmvc.annotation;
+
+import java.lang.annotation.*;
+
+/**
+ * @author Bart Tegenbosch
+ */
+@Documented
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface Message {
+
+}
diff --git a/spring-mvc-extension/src/main/java/nl/onedott/mustachejava/extension/springmvc/annotation/Theme.java b/spring-mvc-extension/src/main/java/nl/onedott/mustachejava/extension/springmvc/annotation/Theme.java
new file mode 100644
index 000000000..23906ac1e
--- /dev/null
+++ b/spring-mvc-extension/src/main/java/nl/onedott/mustachejava/extension/springmvc/annotation/Theme.java
@@ -0,0 +1,13 @@
+package nl.onedott.mustachejava.extension.springmvc.annotation;
+
+import java.lang.annotation.*;
+
+/**
+ * @author Bart Tegenbosch
+ */
+@Documented
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface Theme {
+
+}
diff --git a/spring-mvc-extension/src/main/java/nl/onedott/mustachejava/extension/springmvc/config/MustacheConfiguration.java b/spring-mvc-extension/src/main/java/nl/onedott/mustachejava/extension/springmvc/config/MustacheConfiguration.java
new file mode 100644
index 000000000..f374bcd96
--- /dev/null
+++ b/spring-mvc-extension/src/main/java/nl/onedott/mustachejava/extension/springmvc/config/MustacheConfiguration.java
@@ -0,0 +1,59 @@
+package nl.onedott.mustachejava.extension.springmvc.config;
+
+import com.github.mustachejava.DefaultMustacheFactory;
+import com.github.mustachejava.MustacheFactory;
+import nl.onedott.mustachejava.extension.springmvc.annotation.EnableMustache;
+import nl.onedott.mustachejava.extension.springmvc.mustache.MessageSupportReflectionObjectHandler;
+import nl.onedott.mustachejava.extension.springmvc.view.MustacheView;
+import nl.onedott.mustachejava.extension.springmvc.view.MustacheViewResolver;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.ImportAware;
+import org.springframework.core.io.Resource;
+import org.springframework.core.type.AnnotationMetadata;
+import org.springframework.web.servlet.ViewResolver;
+
+import java.util.Map;
+
+/**
+ * Imported by using the {@link nl.onedott.mustachejava.extension.springmvc.annotation.EnableMustache} annotation.
+ *
+ * @author Bart Tegenbosch
+ */
+@Configuration
+public class MustacheConfiguration implements ImportAware {
+
+ @Autowired
+ private ApplicationContext context;
+
+ private Resource resourceLocation;
+
+ @Override
+ public void setImportMetadata(final AnnotationMetadata importMetadata) {
+ String className = EnableMustache.class.getName();
+ setProperties(importMetadata.getAnnotationAttributes(className));
+ }
+
+ @Bean
+ public MustacheFactory mustacheFactory() throws Exception {
+ DefaultMustacheFactory factory = new DefaultMustacheFactory(resourceLocation.getFile());
+ factory.setObjectHandler(new MessageSupportReflectionObjectHandler(context));
+ return factory;
+ }
+
+ @Bean
+ public ViewResolver viewResolver() throws Exception {
+ MustacheViewResolver viewResolver = new MustacheViewResolver(mustacheFactory());
+
+ viewResolver.setViewClass(MustacheView.class);
+ viewResolver.setSuffix(".mustache");
+ return viewResolver;
+ }
+
+ public void setProperties(final Map attributes) {
+ String pattern = (String) attributes.get("value");
+ resourceLocation = context.getResource(pattern);
+ }
+}
diff --git a/spring-mvc-extension/src/main/java/nl/onedott/mustachejava/extension/springmvc/mustache/MessageSupportReflectionObjectHandler.java b/spring-mvc-extension/src/main/java/nl/onedott/mustachejava/extension/springmvc/mustache/MessageSupportReflectionObjectHandler.java
new file mode 100644
index 000000000..c9b7a331a
--- /dev/null
+++ b/spring-mvc-extension/src/main/java/nl/onedott/mustachejava/extension/springmvc/mustache/MessageSupportReflectionObjectHandler.java
@@ -0,0 +1,40 @@
+package nl.onedott.mustachejava.extension.springmvc.mustache;
+
+import com.github.mustachejava.reflect.ReflectionObjectHandler;
+import com.github.mustachejava.reflect.ReflectionWrapper;
+import com.github.mustachejava.util.Wrapper;
+import nl.onedott.mustachejava.extension.springmvc.annotation.Message;
+import nl.onedott.mustachejava.extension.springmvc.annotation.Theme;
+import org.springframework.context.MessageSource;
+import org.springframework.util.Assert;
+
+import java.lang.reflect.Method;
+
+/**
+ * Enables wrapper with message support.
+ * @author Bart Tegenbosch
+ */
+public class MessageSupportReflectionObjectHandler extends ReflectionObjectHandler {
+
+ private final MessageSource messageSource;
+
+ public MessageSupportReflectionObjectHandler(final MessageSource messageSource) {
+ Assert.notNull(messageSource);
+ this.messageSource = messageSource;
+ }
+
+ @Override
+ public Wrapper find(final String name, final Object[] scopes) {
+ Wrapper wrapper = super.find(name, scopes);
+ if (wrapper instanceof ReflectionWrapper) {
+ ReflectionWrapper w = (ReflectionWrapper) wrapper;
+ Method method = w.getMethod();
+
+ if (method.isAnnotationPresent(Message.class) || method.isAnnotationPresent(Theme.class)) {
+ wrapper = new MessageSupportReflectionWrapper(w, messageSource);
+ }
+ }
+
+ return wrapper;
+ }
+}
diff --git a/spring-mvc-extension/src/main/java/nl/onedott/mustachejava/extension/springmvc/mustache/MessageSupportReflectionWrapper.java b/spring-mvc-extension/src/main/java/nl/onedott/mustachejava/extension/springmvc/mustache/MessageSupportReflectionWrapper.java
new file mode 100644
index 000000000..a9acd0c88
--- /dev/null
+++ b/spring-mvc-extension/src/main/java/nl/onedott/mustachejava/extension/springmvc/mustache/MessageSupportReflectionWrapper.java
@@ -0,0 +1,91 @@
+package nl.onedott.mustachejava.extension.springmvc.mustache;
+
+import com.github.mustachejava.MustacheException;
+import com.github.mustachejava.reflect.ReflectionWrapper;
+import com.github.mustachejava.util.GuardException;
+import nl.onedott.mustachejava.extension.springmvc.SpringMustacheException;
+import nl.onedott.mustachejava.extension.springmvc.annotation.Message;
+import nl.onedott.mustachejava.extension.springmvc.annotation.Theme;
+import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.MessageSource;
+import org.springframework.context.NoSuchMessageException;
+import org.springframework.web.context.request.RequestContextHolder;
+import org.springframework.web.context.request.ServletRequestAttributes;
+import org.springframework.web.servlet.support.RequestContextUtils;
+
+import javax.servlet.http.HttpServletRequest;
+import java.lang.reflect.InvocationTargetException;
+import java.util.Locale;
+
+/**
+ * Enables resolving messages using the return values of the method as message codes.
+ * Also supports embedded values e.g. {@code "${my.property}"}.
+ * @author Bart Tegenbosch
+ */
+public class MessageSupportReflectionWrapper extends ReflectionWrapper {
+ private final MessageSource messageSource;
+
+ public MessageSupportReflectionWrapper(final ReflectionWrapper rw, final MessageSource messageSource) {
+ super(rw);
+ this.messageSource = messageSource;
+ }
+
+ @Override
+ public Object call(final Object[] scopes) throws GuardException {
+ if (method != null) {
+ String code = returnValue(scopes).toString();
+ Locale locale = resolveLocale();
+
+ if (messageSource instanceof ApplicationContext) {
+ ConfigurableListableBeanFactory beanFactory =
+ (ConfigurableListableBeanFactory) ((ApplicationContext) messageSource)
+ .getAutowireCapableBeanFactory();
+ code = beanFactory.resolveEmbeddedValue(code);
+ }
+
+ if (method.isAnnotationPresent(Message.class)) {
+ try {
+ return messageSource.getMessage(code, getArguments(), locale);
+ } catch (NoSuchMessageException e) {
+ throw new SpringMustacheException(e);
+ }
+ }
+
+ if (method.isAnnotationPresent(Theme.class)) {
+ org.springframework.ui.context.Theme theme =
+ RequestContextUtils.getTheme(getRequest());
+
+ if (theme == null) {
+ throw new SpringMustacheException("No theme found");
+ }
+
+
+ return theme.getMessageSource().getMessage(code, getArguments(), locale);
+ }
+ }
+ return super.call(scopes);
+ }
+
+ private Object returnValue(Object[] scopes) {
+ guardCall(scopes);
+ Object scope = oh.coerce(unwrap(scopes));
+
+ try {
+ return method.invoke(scope, arguments);
+ } catch (IllegalAccessException e) {
+ throw new MustacheException(e);
+ } catch (InvocationTargetException e) {
+ throw new MustacheException(e);
+ }
+ }
+
+ public Locale resolveLocale() {
+ return RequestContextUtils.getLocale(getRequest());
+ }
+
+ private HttpServletRequest getRequest() {
+ ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.currentRequestAttributes();
+ return attributes.getRequest();
+ }
+}
diff --git a/spring-mvc-extension/src/main/java/nl/onedott/mustachejava/extension/springmvc/view/MustacheView.java b/spring-mvc-extension/src/main/java/nl/onedott/mustachejava/extension/springmvc/view/MustacheView.java
new file mode 100644
index 000000000..9310985f9
--- /dev/null
+++ b/spring-mvc-extension/src/main/java/nl/onedott/mustachejava/extension/springmvc/view/MustacheView.java
@@ -0,0 +1,27 @@
+package nl.onedott.mustachejava.extension.springmvc.view;
+
+import com.github.mustachejava.MustacheFactory;
+import org.springframework.util.Assert;
+import org.springframework.web.servlet.view.AbstractTemplateView;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.util.Map;
+
+/**
+ * @author Bart Tegenbosch
+ */
+public class MustacheView extends AbstractTemplateView {
+
+ private MustacheFactory mustacheFactory;
+
+ @Override
+ protected void renderMergedTemplateModel(final Map model, final HttpServletRequest request, final HttpServletResponse response) throws Exception {
+ mustacheFactory.compile(getUrl()).execute(response.getWriter(), model);
+ }
+
+ public void setMustacheFactory(final MustacheFactory mustacheFactory) {
+ Assert.notNull(mustacheFactory, "mustacheFactory cannot be null");
+ this.mustacheFactory = mustacheFactory;
+ }
+}
diff --git a/spring-mvc-extension/src/main/java/nl/onedott/mustachejava/extension/springmvc/view/MustacheViewResolver.java b/spring-mvc-extension/src/main/java/nl/onedott/mustachejava/extension/springmvc/view/MustacheViewResolver.java
new file mode 100644
index 000000000..5f0371f41
--- /dev/null
+++ b/spring-mvc-extension/src/main/java/nl/onedott/mustachejava/extension/springmvc/view/MustacheViewResolver.java
@@ -0,0 +1,29 @@
+package nl.onedott.mustachejava.extension.springmvc.view;
+
+import com.github.mustachejava.MustacheFactory;
+import org.springframework.web.servlet.view.AbstractTemplateViewResolver;
+import org.springframework.web.servlet.view.AbstractUrlBasedView;
+
+/**
+ * @author Bart Tegenbosch
+ */
+public class MustacheViewResolver extends AbstractTemplateViewResolver {
+
+ private final MustacheFactory mustacheFactory;
+
+ public MustacheViewResolver(final MustacheFactory mustacheFactory) {
+ this.mustacheFactory = mustacheFactory;
+ }
+
+ @Override
+ protected Class> requiredViewClass() {
+ return MustacheView.class;
+ }
+
+ @Override
+ protected AbstractUrlBasedView buildView(final String viewName) throws Exception {
+ MustacheView view = (MustacheView) super.buildView(viewName);
+ view.setMustacheFactory(mustacheFactory);
+ return view;
+ }
+}
diff --git a/spring-mvc-extension/src/test/java/nl/onedott/mustachejava/extension/springmvc/SpringRequestContextSupport.java b/spring-mvc-extension/src/test/java/nl/onedott/mustachejava/extension/springmvc/SpringRequestContextSupport.java
new file mode 100644
index 000000000..c15dab053
--- /dev/null
+++ b/spring-mvc-extension/src/test/java/nl/onedott/mustachejava/extension/springmvc/SpringRequestContextSupport.java
@@ -0,0 +1,67 @@
+package nl.onedott.mustachejava.extension.springmvc;
+
+import org.springframework.mock.web.MockHttpServletRequest;
+import org.springframework.mock.web.MockHttpServletResponse;
+import org.springframework.mock.web.MockServletContext;
+import org.springframework.ui.context.ThemeSource;
+import org.springframework.ui.context.support.SimpleTheme;
+import org.springframework.web.context.request.RequestAttributes;
+import org.springframework.web.context.request.RequestContextHolder;
+import org.springframework.web.context.request.ServletRequestAttributes;
+import org.springframework.web.context.support.StaticWebApplicationContext;
+import org.springframework.web.servlet.DispatcherServlet;
+import org.springframework.web.servlet.ThemeResolver;
+
+import javax.servlet.http.HttpServletRequest;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+/**
+ * @author Bart Tegenbosch
+ */
+public abstract class SpringRequestContextSupport {
+
+ private MockServletContext servletContext;
+ private MockHttpServletRequest request;
+ private MockHttpServletResponse response;
+ private StaticWebApplicationContext webApplicationContext;
+
+ public void setup() throws Exception {
+ servletContext = new MockServletContext();
+ webApplicationContext = new StaticWebApplicationContext();
+ webApplicationContext.setServletContext(servletContext);
+ response = new MockHttpServletResponse();
+ request = new MockHttpServletRequest();
+ request.setAttribute(DispatcherServlet.WEB_APPLICATION_CONTEXT_ATTRIBUTE, webApplicationContext);
+
+ RequestAttributes attributes = new ServletRequestAttributes(request);
+ RequestContextHolder.setRequestAttributes(attributes);
+
+ setupTheme("default");
+ }
+
+ public StaticWebApplicationContext webApplicationContext() {
+ return webApplicationContext;
+ }
+
+ public MockHttpServletRequest request() {
+ return request;
+ }
+
+ public MockHttpServletResponse response() {
+ return response;
+ }
+
+ public void setupTheme(String themeName) {
+ ThemeResolver themeResolver = mock(ThemeResolver.class);
+ when(themeResolver.resolveThemeName(any(HttpServletRequest.class))).thenReturn(themeName);
+
+ ThemeSource themeSource = mock(ThemeSource.class);
+ when(themeSource.getTheme(any(String.class))).thenReturn(new SimpleTheme(themeName, webApplicationContext));
+
+ request.setAttribute(DispatcherServlet.THEME_RESOLVER_ATTRIBUTE, themeResolver);
+ request.setAttribute(DispatcherServlet.THEME_SOURCE_ATTRIBUTE, themeSource);
+ }
+}
diff --git a/spring-mvc-extension/src/test/java/nl/onedott/mustachejava/extension/springmvc/mustache/MessageSupportReflectionObjectHandlerTest.java b/spring-mvc-extension/src/test/java/nl/onedott/mustachejava/extension/springmvc/mustache/MessageSupportReflectionObjectHandlerTest.java
new file mode 100644
index 000000000..44ef00673
--- /dev/null
+++ b/spring-mvc-extension/src/test/java/nl/onedott/mustachejava/extension/springmvc/mustache/MessageSupportReflectionObjectHandlerTest.java
@@ -0,0 +1,92 @@
+package nl.onedott.mustachejava.extension.springmvc.mustache;
+
+import com.github.mustachejava.util.Wrapper;
+import nl.onedott.mustachejava.extension.springmvc.SpringRequestContextSupport;
+import nl.onedott.mustachejava.extension.springmvc.annotation.Message;
+import nl.onedott.mustachejava.extension.springmvc.annotation.Theme;
+import org.junit.Before;
+import org.junit.Test;
+import org.springframework.web.context.support.StaticWebApplicationContext;
+
+import java.util.Locale;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+public class MessageSupportReflectionObjectHandlerTest extends SpringRequestContextSupport {
+ private StaticWebApplicationContext context;
+
+ @Before
+ public void setup() throws Exception {
+ super.setup();
+ context = webApplicationContext();
+ context.addMessage("greeting", Locale.ENGLISH, "Hello");
+ context.addMessage("name", Locale.ENGLISH, "mustache");
+ context.addMessage("color", Locale.ENGLISH, "red");
+ context.refresh();
+ }
+
+ @Test
+ public void testReturnMessageSupportReflectionWrapper() throws Exception {
+ MessageSupportReflectionObjectHandler h = new MessageSupportReflectionObjectHandler(context);
+
+ // Return MessageSupportReflectionWrapper when annotated
+ Wrapper wrapper1 = h.find("greeting", new Object[] { new MustacheGreetingContext() });
+ assertTrue(wrapper1 instanceof MessageSupportReflectionWrapper);
+
+ // Return default wrappers when not annotated
+ Wrapper wrapper2 = h.find("age", new Object[] { new MustacheGreetingContext() });
+ assertFalse(wrapper2 instanceof MessageSupportReflectionWrapper);
+ }
+
+ @Test
+ public void testResolvedMessage() throws Exception {
+ Object[] scopes = new Object[] { new MustacheGreetingContext() };
+ MessageSupportReflectionObjectHandler h = new MessageSupportReflectionObjectHandler(context);
+
+ // Message
+ Wrapper wrapper1 = h.find("greeting", scopes);
+ assertEquals("Hello", wrapper1.call(scopes));
+
+ // Message
+ Wrapper wrapper2 = h.find("name", scopes);
+ assertEquals("mustache", wrapper2.call(scopes));
+
+ // Theme message
+ Wrapper wrapper3 = h.find("color", scopes);
+ assertEquals("red", wrapper3.call(scopes));
+ }
+
+ public static class MustacheGreetingContext {
+
+ private final String name;
+
+ public MustacheGreetingContext() {
+ this("name");
+ }
+
+ public MustacheGreetingContext(final String name) {
+ this.name = name;
+ }
+
+ public int getAge() {
+ return 9;
+ }
+
+ @Message
+ public String getGreeting() {
+ return "greeting";
+ }
+
+ @Message
+ public String getName() {
+ return name;
+ }
+
+ @Theme
+ public String getColor() {
+ return "color";
+ }
+ }
+}
diff --git a/spring-mvc-extension/src/test/java/nl/onedott/mustachejava/extension/springmvc/view/MustacheViewTest.java b/spring-mvc-extension/src/test/java/nl/onedott/mustachejava/extension/springmvc/view/MustacheViewTest.java
new file mode 100644
index 000000000..b54db3e6a
--- /dev/null
+++ b/spring-mvc-extension/src/test/java/nl/onedott/mustachejava/extension/springmvc/view/MustacheViewTest.java
@@ -0,0 +1,51 @@
+package nl.onedott.mustachejava.extension.springmvc.view;
+
+import com.github.mustachejava.DefaultMustacheFactory;
+import nl.onedott.mustachejava.extension.springmvc.SpringRequestContextSupport;
+import nl.onedott.mustachejava.extension.springmvc.mustache.MessageSupportReflectionObjectHandler;
+import org.junit.Before;
+import org.junit.Test;
+import org.springframework.ui.ModelMap;
+
+import static org.junit.Assert.assertEquals;
+
+public class MustacheViewTest extends SpringRequestContextSupport {
+ private DefaultMustacheFactory mustacheFactory;
+ private MustacheView view;
+
+ @Before
+ public void setup() throws Exception {
+ super.setup();
+
+ mustacheFactory = new DefaultMustacheFactory("templates/");
+ mustacheFactory.setObjectHandler(new MessageSupportReflectionObjectHandler(webApplicationContext()));
+
+ view = new MustacheView();
+ view.setApplicationContext(webApplicationContext());
+ view.setMustacheFactory(mustacheFactory);
+ }
+
+ @Test
+ public void testRenderMergedTemplateModel() throws Exception {
+ ModelMap model = new ModelMap("dog", new Dog("Bello"));
+ view.setUrl("view.mustache");
+ view.render(model, request(), response());
+ assertEquals("My dog's name is Bello and says \"woof!\"", response().getContentAsString().trim());
+ }
+
+ public class Dog {
+ private final String name;
+
+ private Dog(final String name) {
+ this.name = name;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public String speak() {
+ return "woof!";
+ }
+ }
+}
diff --git a/spring-mvc-extension/src/test/resources/templates/view.mustache b/spring-mvc-extension/src/test/resources/templates/view.mustache
new file mode 100644
index 000000000..7c565125d
--- /dev/null
+++ b/spring-mvc-extension/src/test/resources/templates/view.mustache
@@ -0,0 +1 @@
+My dog's name is {{dog.name}} and says "{{dog.speak}}"