diff --git a/pom.xml b/pom.xml
index 21f58a2..ea76431 100644
--- a/pom.xml
+++ b/pom.xml
@@ -53,7 +53,8 @@
1.6
${encoding}
${encoding}
- 3.2.6.RELEASE
+ 4.3.4.RELEASE
+ 1.4.2.RELEASE
@@ -118,6 +119,18 @@
${spring.version}
true
+
+ org.springframework.boot
+ spring-boot-autoconfigure
+ ${spring-boot.version}
+ true
+
+
+ org.springframework.boot
+ spring-boot-configuration-processor
+ ${spring-boot.version}
+ provided
+
javax.servlet
javax.servlet-api
@@ -128,7 +141,7 @@
junit
junit
- 4.11
+ 4.12
test
@@ -155,6 +168,12 @@
${spring.version}
test
+
+ org.springframework.boot
+ spring-boot-test
+ ${spring-boot.version}
+ test
+
cglib
cglib
diff --git a/src/main/java/org/mixer2/spring/boot/Mixer2AutoConfiguration.java b/src/main/java/org/mixer2/spring/boot/Mixer2AutoConfiguration.java
new file mode 100644
index 0000000..4416563
--- /dev/null
+++ b/src/main/java/org/mixer2/spring/boot/Mixer2AutoConfiguration.java
@@ -0,0 +1,105 @@
+package org.mixer2.spring.boot;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.mixer2.Mixer2Engine;
+import org.mixer2.spring.webmvc.Mixer2XhtmlViewResolver;
+import org.springframework.beans.factory.BeanFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.autoconfigure.AutoConfigurationPackages;
+import org.springframework.boot.autoconfigure.AutoConfigureAfter;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.servlet.ViewResolver;
+
+import java.util.List;
+
+@Configuration
+@ConditionalOnClass(Mixer2Engine.class)
+@AutoConfigureAfter(WebMvcAutoConfiguration.class)
+@EnableConfigurationProperties(Mixer2Properties.class)
+public class Mixer2AutoConfiguration {
+
+ private static Log log = LogFactory.getLog(Mixer2AutoConfiguration.class);
+
+ @Bean
+ @ConditionalOnMissingBean
+ public Mixer2Engine mixer2Engine() {
+ return new Mixer2Engine();
+ }
+
+ @Configuration
+ @ConditionalOnClass(ViewResolver.class)
+ public static class Mixer2ViewConfiguration {
+
+ private final Mixer2Engine mixer2Engine;
+ private final Mixer2Properties properties;
+ private final BeanFactory beanFactory;
+
+ @Autowired
+ public Mixer2ViewConfiguration(Mixer2Engine mixer2Engine, Mixer2Properties properties, BeanFactory beanFactory) {
+ this.mixer2Engine = mixer2Engine;
+ this.properties = properties;
+ this.beanFactory = beanFactory;
+ }
+
+ @Bean
+ @ConditionalOnMissingBean
+ public Mixer2XhtmlViewResolver mixer2XhtmlViewResolver() {
+ Mixer2XhtmlViewResolver resolver = new Mixer2XhtmlViewResolver();
+ resolver.setOrder(properties.getOrder());
+ resolver.setMixer2Engine(this.mixer2Engine);
+ resolver.setPrefix(properties.getPrefix());
+ resolver.setSuffix(properties.getSuffix());
+ if (properties.getViewBasePackage() != null) {
+ resolver.setBasePackage(properties.getViewBasePackage());
+ } else if (AutoConfigurationPackages.has(beanFactory)) {
+ List packages = AutoConfigurationPackages.get(beanFactory);
+ if (packages.size() == 1) {
+ String viewBasePackage = packages.get(0) + ".web.view";
+ resolver.setBasePackage(viewBasePackage);
+ if (log.isDebugEnabled()) {
+ log.debug("Apply '" + viewBasePackage + "' to the base package of view.");
+ }
+ } else {
+ if (log.isDebugEnabled()) {
+ log.debug("Skip auto-detecting base package of view because found multiple base packages. found base packages : " + packages);
+ }
+ }
+ }
+ if (properties.getViewClassNameSuffix() != null) {
+ resolver.setClassNameSuffix(properties.getViewClassNameSuffix());
+ }
+ if (properties.getContentType() != null) {
+ resolver.setContentType(properties.getContentType());
+ }
+ if (properties.getDocType() != null) {
+ resolver.setDocType(properties.getDocType());
+ }
+ if (properties.getReturnNullIfTemplateFileNotFound() != null) {
+ resolver.setReturnNullIfTemplateFileNotFound(properties.getReturnNullIfTemplateFileNotFound());
+ }
+ if (properties.getRaiseErrorIfViewClassNotFound() != null) {
+ resolver.setRaiseErrorIfViewClassNotFound(properties.getRaiseErrorIfViewClassNotFound());
+ }
+ if (properties.getCache() != null) {
+ resolver.setCache(properties.getCache());
+ }
+ if (resolver.isCache()) {
+ if (properties.getCacheLimit() != null) {
+ resolver.setCacheLimit(properties.getCacheLimit());
+ }
+ if (properties.getCacheUnresolved() != null) {
+ resolver.setCacheUnresolved(properties.getCacheUnresolved());
+ }
+ }
+ return resolver;
+ }
+
+ }
+
+}
diff --git a/src/main/java/org/mixer2/spring/boot/Mixer2Properties.java b/src/main/java/org/mixer2/spring/boot/Mixer2Properties.java
new file mode 100644
index 0000000..66c6cb6
--- /dev/null
+++ b/src/main/java/org/mixer2/spring/boot/Mixer2Properties.java
@@ -0,0 +1,132 @@
+package org.mixer2.spring.boot;
+
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+import java.io.Serializable;
+
+@ConfigurationProperties(prefix = "mixer2")
+public class Mixer2Properties implements Serializable {
+
+ private static final long serialVersionUID = -8282705230257476884L;
+
+ private int order = 1;
+
+ private String prefix = "classpath:/m2mockup/templates/";
+
+ private String suffix = ".html";
+
+ private String viewBasePackage;
+
+ private String viewClassNameSuffix;
+
+ private String docType;
+
+ private String contentType;
+
+ private Boolean returnNullIfTemplateFileNotFound;
+
+ private Boolean raiseErrorIfViewClassNotFound;
+
+ private Integer cacheLimit;
+
+ private Boolean cacheUnresolved;
+
+ private Boolean cache;
+
+ public int getOrder() {
+ return order;
+ }
+
+ public void setOrder(int order) {
+ this.order = order;
+ }
+
+ public String getPrefix() {
+ return prefix;
+ }
+
+ public void setPrefix(String prefix) {
+ this.prefix = prefix;
+ }
+
+ public String getSuffix() {
+ return suffix;
+ }
+
+ public void setSuffix(String suffix) {
+ this.suffix = suffix;
+ }
+
+ public String getViewBasePackage() {
+ return viewBasePackage;
+ }
+
+ public void setViewBasePackage(String viewBasePackage) {
+ this.viewBasePackage = viewBasePackage;
+ }
+
+ public String getViewClassNameSuffix() {
+ return viewClassNameSuffix;
+ }
+
+ public void setViewClassNameSuffix(String viewClassNameSuffix) {
+ this.viewClassNameSuffix = viewClassNameSuffix;
+ }
+
+ public String getDocType() {
+ return docType;
+ }
+
+ public void setDocType(String docType) {
+ this.docType = docType;
+ }
+
+ public String getContentType() {
+ return contentType;
+ }
+
+ public void setContentType(String contentType) {
+ this.contentType = contentType;
+ }
+
+ public Boolean getReturnNullIfTemplateFileNotFound() {
+ return returnNullIfTemplateFileNotFound;
+ }
+
+ public void setReturnNullIfTemplateFileNotFound(Boolean returnNullIfTemplateFileNotFound) {
+ this.returnNullIfTemplateFileNotFound = returnNullIfTemplateFileNotFound;
+ }
+
+ public Boolean getRaiseErrorIfViewClassNotFound() {
+ return raiseErrorIfViewClassNotFound;
+ }
+
+ public void setRaiseErrorIfViewClassNotFound(Boolean raiseErrorIfViewClassNotFound) {
+ this.raiseErrorIfViewClassNotFound = raiseErrorIfViewClassNotFound;
+ }
+
+ public Integer getCacheLimit() {
+ return cacheLimit;
+ }
+
+ public void setCacheLimit(Integer cacheLimit) {
+ this.cacheLimit = cacheLimit;
+ }
+
+ public Boolean getCacheUnresolved() {
+ return cacheUnresolved;
+ }
+
+ public void setCacheUnresolved(Boolean cacheUnresolved) {
+ this.cacheUnresolved = cacheUnresolved;
+ }
+
+ public Boolean getCache() {
+ return cache;
+ }
+
+ public void setCache(Boolean cache) {
+ this.cache = cache;
+ }
+
+}
diff --git a/src/main/resources/META-INF/spring.factories b/src/main/resources/META-INF/spring.factories
new file mode 100644
index 0000000..1d20492
--- /dev/null
+++ b/src/main/resources/META-INF/spring.factories
@@ -0,0 +1,3 @@
+# Auto Configure
+org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
+org.mixer2.spring.boot.Mixer2AutoConfiguration
diff --git a/src/test/java/org/mixer2/spring/boot/Mixer2AutoConfigurationTest.java b/src/test/java/org/mixer2/spring/boot/Mixer2AutoConfigurationTest.java
new file mode 100644
index 0000000..4d701c2
--- /dev/null
+++ b/src/test/java/org/mixer2/spring/boot/Mixer2AutoConfigurationTest.java
@@ -0,0 +1,237 @@
+package org.mixer2.spring.boot;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.mixer2.Mixer2Engine;
+import org.mixer2.jaxb.exception.Mixer2JAXBException;
+import org.mixer2.jaxb.xhtml.Html;
+import org.mixer2.spring.boot.app.view.CustomScreenView;
+import org.mixer2.spring.boot.config.SubPackageAppConfig;
+import org.mixer2.spring.boot.web.view.DefaultView;
+import org.mixer2.spring.webmvc.AbstractMixer2XhtmlView;
+import org.mixer2.spring.webmvc.Mixer2XhtmlViewResolver;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.boot.test.util.EnvironmentTestUtils;
+import org.springframework.context.annotation.AnnotationConfigApplicationContext;
+import org.springframework.context.annotation.Bean;
+import org.springframework.mock.web.MockHttpServletRequest;
+import org.springframework.mock.web.MockHttpServletResponse;
+import org.springframework.web.servlet.View;
+
+import java.util.Locale;
+
+import static org.hamcrest.core.Is.is;
+import static org.hamcrest.core.IsNull.nullValue;
+import static org.junit.Assert.assertThat;
+
+public class Mixer2AutoConfigurationTest {
+
+ @Rule
+ public ExpectedException expectedException = ExpectedException.none();
+
+ private AnnotationConfigApplicationContext context;
+
+ @Before
+ public void init() {
+ this.context = new AnnotationConfigApplicationContext();
+ }
+
+ @After
+ public void closeContext() {
+ if (this.context != null) {
+ this.context.close();
+ }
+ }
+
+ @Test
+ public void testDefaultConfiguration() throws Exception {
+ this.context.register(AppConfig.class, Mixer2AutoConfiguration.class);
+ this.context.refresh();
+
+ assertThat(this.context.getBeanNamesForType(Mixer2Engine.class).length, is(1));
+
+ Mixer2XhtmlViewResolver viewResolver = this.context.getBean(Mixer2XhtmlViewResolver.class);
+ assertThat(viewResolver.getOrder(), is(1));
+ assertThat(viewResolver.isCache(), is(true));
+ assertThat(viewResolver.getCacheLimit(), is(1024));
+ assertThat(viewResolver.isCacheUnresolved(), is(true));
+
+ DefaultView view = (DefaultView) viewResolver.resolveViewName("default", Locale.US);
+ MockHttpServletResponse response = new MockHttpServletResponse();
+ view.render(null, new MockHttpServletRequest(), response);
+ assertThat(response.getContentAsString(), is("\n\ndefault\n"));
+ assertThat(response.getContentType(), is("text/html; charset=UTF-8"));
+
+ }
+
+ @Test
+ public void testCustomUsingProperties() throws Exception {
+ EnvironmentTestUtils.addEnvironment(this.context,
+ "mixer2.order:0",
+ "mixer2.prefix:classpath:/m2mockup/custom-templates/",
+ "mixer2.suffix:.htm",
+ "mixer2.view-base-package:org.mixer2.spring.boot.app.view",
+ "mixer2.view-class-name-suffix:ScreenView",
+ "mixer2.content-type:text/html; charset=UTF-16",
+ "mixer2.doc-type:");
+ this.context.register(AppConfig.class, Mixer2AutoConfiguration.class);
+ this.context.refresh();
+
+ assertThat(this.context.getBeanNamesForType(Mixer2Engine.class).length, is(1));
+ Mixer2XhtmlViewResolver viewResolver = this.context.getBean(Mixer2XhtmlViewResolver.class);
+ assertThat(viewResolver.getOrder(), is(0));
+
+ CustomScreenView view = (CustomScreenView) viewResolver.resolveViewName("custom", Locale.US);
+ MockHttpServletResponse response = new MockHttpServletResponse();
+ view.render(null, new MockHttpServletRequest(), response);
+ assertThat(response.getContentAsString(), is("\n\ncustom default\n"));
+ assertThat(response.getContentType(), is("text/html; charset=UTF-16"));
+ }
+
+ @Test
+ public void testCustomCacheIsFalseUsingProperties() throws Exception {
+ EnvironmentTestUtils.addEnvironment(this.context,
+ "mixer2.cache:false",
+ "mixer2.cache-limit:4000",
+ "mixer2.cache-unresolved:false");
+ this.context.register(AppConfig.class, Mixer2AutoConfiguration.class);
+ this.context.refresh();
+
+ assertThat(this.context.getBeanNamesForType(Mixer2Engine.class).length, is(1));
+ Mixer2XhtmlViewResolver viewResolver = this.context.getBean(Mixer2XhtmlViewResolver.class);
+ assertThat(viewResolver.isCache(), is(false));
+ assertThat(viewResolver.getCacheLimit(), is(0));
+ assertThat(viewResolver.isCacheUnresolved(), is(true));
+
+ }
+
+ @Test
+ public void testCustomCacheParametersUsingProperties() throws Exception {
+ EnvironmentTestUtils.addEnvironment(this.context,
+ "mixer2.cache:true",
+ "mixer2.cache-limit:512",
+ "mixer2.cache-unresolved:false");
+ this.context.register(AppConfig.class, Mixer2AutoConfiguration.class);
+ this.context.refresh();
+
+ assertThat(this.context.getBeanNamesForType(Mixer2Engine.class).length, is(1));
+ Mixer2XhtmlViewResolver viewResolver = this.context.getBean(Mixer2XhtmlViewResolver.class);
+ assertThat(viewResolver.isCache(), is(true));
+ assertThat(viewResolver.getCacheLimit(), is(512));
+ assertThat(viewResolver.isCacheUnresolved(), is(false));
+ }
+
+ @Test
+ public void testNotFoundTemplateFileOnDefaultConfiguration() throws Exception {
+ this.context.register(AppConfig.class, Mixer2AutoConfiguration.class);
+ this.context.refresh();
+
+ Mixer2XhtmlViewResolver viewResolver = this.context.getBean(Mixer2XhtmlViewResolver.class);
+ View view = viewResolver.resolveViewName("foo", Locale.US);
+ assertThat(view, nullValue());
+ }
+
+ @Test
+ public void testNotFoundTemplateFileOnReturnNullIfTemplateFileNotFoundIsFalse() throws Exception {
+ EnvironmentTestUtils.addEnvironment(this.context,
+ "mixer2.return-null-if-template-file-not-found:false");
+ this.context.register(AppConfig.class, Mixer2AutoConfiguration.class);
+ this.context.refresh();
+
+ Mixer2XhtmlViewResolver viewResolver = this.context.getBean(Mixer2XhtmlViewResolver.class);
+ AbstractMixer2XhtmlView view = (AbstractMixer2XhtmlView) viewResolver.resolveViewName("foo", Locale.US);
+ assertThat(view.getUrl(), is("classpath:/m2mockup/templates/foo.html"));
+ }
+
+
+ @Test
+ public void testNotFoundViewClassOnDefaultConfiguration() throws Exception {
+ this.context.register(AppConfig.class, Mixer2AutoConfiguration.class);
+ this.context.refresh();
+
+ Mixer2XhtmlViewResolver viewResolver = this.context.getBean(Mixer2XhtmlViewResolver.class);
+ View view = viewResolver.resolveViewName("bar", Locale.US);
+ MockHttpServletResponse response = new MockHttpServletResponse();
+ view.render(null, new MockHttpServletRequest(), response);
+ assertThat(response.getContentAsString(), is("\n\nbar\n"));
+ assertThat(response.getContentType(), is("text/html; charset=UTF-8"));
+
+ }
+
+ @Test
+ public void testNotFoundViewClassOnRaiseErrorIfViewClassNotFoundIsTrue() throws Exception {
+ EnvironmentTestUtils.addEnvironment(this.context,
+ "mixer2.raise-error-if-view-class-not-found:true");
+ this.context.register(AppConfig.class, Mixer2AutoConfiguration.class);
+ this.context.refresh();
+
+ expectedException.expect(ClassNotFoundException.class);
+ expectedException.expectMessage(is("org.mixer2.spring.boot.web.view.BarView"));
+
+ Mixer2XhtmlViewResolver viewResolver = context.getBean(Mixer2XhtmlViewResolver.class);
+ viewResolver.resolveViewName("bar", Locale.US);
+
+ }
+
+
+ @Test
+ public void testFoundMultipleBasePackage() throws Exception {
+ EnvironmentTestUtils.addEnvironment(this.context,
+ "mixer2.raise-error-if-view-class-not-found:true");
+ this.context.register(AppConfig.class, SubPackageAppConfig.class, Mixer2AutoConfiguration.class);
+ this.context.refresh();
+
+ expectedException.expect(ClassNotFoundException.class);
+ expectedException.expectMessage(is("BarView"));
+
+ Mixer2XhtmlViewResolver viewResolver = this.context.getBean(Mixer2XhtmlViewResolver.class);
+ viewResolver.resolveViewName("bar", Locale.US);
+
+ }
+
+ @Test
+ public void testCustomConfiguration() throws Mixer2JAXBException {
+ this.context.register(CustomConfig.class, Mixer2AutoConfiguration.class);
+ this.context.refresh();
+
+ Mixer2Engine engine = this.context.getBean(Mixer2Engine.class);
+ try {
+ engine.checkAndLoadHtmlTemplate("foo");
+ } catch (UnsupportedOperationException e) {
+ assertThat(e.getMessage(), is("error"));
+ }
+
+ Mixer2XhtmlViewResolver viewResolver = this.context.getBean(Mixer2XhtmlViewResolver.class);
+ assertThat(viewResolver.isCache(), is(false));
+
+ }
+
+ @SpringBootApplication
+ static class AppConfig {
+ }
+
+ static class CustomConfig {
+
+ @Bean
+ Mixer2Engine mixer2Engine() {
+ return new Mixer2Engine() {
+ @Override
+ public Html checkAndLoadHtmlTemplate(String str) throws Mixer2JAXBException {
+ throw new UnsupportedOperationException("error");
+ }
+ };
+ }
+
+ @Bean
+ Mixer2XhtmlViewResolver mixer2XhtmlViewResolver() {
+ Mixer2XhtmlViewResolver viewResolver = new Mixer2XhtmlViewResolver();
+ viewResolver.setCache(false);
+ return viewResolver;
+ }
+
+ }
+
+}
diff --git a/src/test/java/org/mixer2/spring/boot/app/view/CustomScreenView.java b/src/test/java/org/mixer2/spring/boot/app/view/CustomScreenView.java
new file mode 100644
index 0000000..3ad7b07
--- /dev/null
+++ b/src/test/java/org/mixer2/spring/boot/app/view/CustomScreenView.java
@@ -0,0 +1,15 @@
+package org.mixer2.spring.boot.app.view;
+
+import org.mixer2.jaxb.xhtml.Html;
+import org.mixer2.spring.webmvc.AbstractMixer2XhtmlView;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.util.Map;
+
+public class CustomScreenView extends AbstractMixer2XhtmlView {
+ @Override
+ protected Html renderHtml(Html html, Map model, HttpServletRequest request, HttpServletResponse response) throws Exception {
+ return html;
+ }
+}
diff --git a/src/test/java/org/mixer2/spring/boot/config/SubPackageAppConfig.java b/src/test/java/org/mixer2/spring/boot/config/SubPackageAppConfig.java
new file mode 100644
index 0000000..2b08d36
--- /dev/null
+++ b/src/test/java/org/mixer2/spring/boot/config/SubPackageAppConfig.java
@@ -0,0 +1,7 @@
+package org.mixer2.spring.boot.config;
+
+import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
+
+@EnableAutoConfiguration
+public class SubPackageAppConfig {
+}
diff --git a/src/test/java/org/mixer2/spring/boot/web/view/DefaultView.java b/src/test/java/org/mixer2/spring/boot/web/view/DefaultView.java
new file mode 100644
index 0000000..e8e11b4
--- /dev/null
+++ b/src/test/java/org/mixer2/spring/boot/web/view/DefaultView.java
@@ -0,0 +1,15 @@
+package org.mixer2.spring.boot.web.view;
+
+import org.mixer2.jaxb.xhtml.Html;
+import org.mixer2.spring.webmvc.AbstractMixer2XhtmlView;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.util.Map;
+
+public class DefaultView extends AbstractMixer2XhtmlView {
+ @Override
+ protected Html renderHtml(Html html, Map model, HttpServletRequest request, HttpServletResponse response) throws Exception {
+ return html;
+ }
+}
diff --git a/src/test/resources/m2mockup/custom-templates/custom.htm b/src/test/resources/m2mockup/custom-templates/custom.htm
new file mode 100644
index 0000000..c2550ed
--- /dev/null
+++ b/src/test/resources/m2mockup/custom-templates/custom.htm
@@ -0,0 +1 @@
+custom default
\ No newline at end of file
diff --git a/src/test/resources/m2mockup/templates/bar.html b/src/test/resources/m2mockup/templates/bar.html
new file mode 100644
index 0000000..fdc31db
--- /dev/null
+++ b/src/test/resources/m2mockup/templates/bar.html
@@ -0,0 +1 @@
+bar
\ No newline at end of file
diff --git a/src/test/resources/m2mockup/templates/default.html b/src/test/resources/m2mockup/templates/default.html
new file mode 100644
index 0000000..3500b1f
--- /dev/null
+++ b/src/test/resources/m2mockup/templates/default.html
@@ -0,0 +1 @@
+default
\ No newline at end of file