diff --git a/spring-boot-autoconfigure/pom.xml b/spring-boot-autoconfigure/pom.xml index 60c5e42855..3c7159feb0 100644 --- a/spring-boot-autoconfigure/pom.xml +++ b/spring-boot-autoconfigure/pom.xml @@ -121,6 +121,11 @@ spring-rabbit true + + org.springframework.mobile + spring-mobile-device + true + org.thymeleaf thymeleaf diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/mobile/DeviceResolverAutoConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/mobile/DeviceResolverAutoConfiguration.java new file mode 100644 index 0000000000..b5c211e48d --- /dev/null +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/mobile/DeviceResolverAutoConfiguration.java @@ -0,0 +1,77 @@ +/* + * Copyright 2013 the original author or authors. + * + * Licensed 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.springframework.boot.autoconfigure.mobile; + +import java.util.List; + +import org.springframework.boot.autoconfigure.AutoConfigureAfter; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; +import org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.mobile.device.DeviceHandlerMethodArgumentResolver; +import org.springframework.mobile.device.DeviceResolver; +import org.springframework.mobile.device.DeviceResolverHandlerInterceptor; +import org.springframework.web.method.support.HandlerMethodArgumentResolver; +import org.springframework.web.servlet.config.annotation.InterceptorRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; + +/** + * {@link EnableAutoConfiguration Auto-configuration} for Spring Mobile's + * {@link DeviceResolver}. + * + * @author Roy Clarkson + */ +@Configuration +@ConditionalOnClass({ DeviceResolverHandlerInterceptor.class, + DeviceHandlerMethodArgumentResolver.class }) +@AutoConfigureAfter(WebMvcAutoConfiguration.class) +public class DeviceResolverAutoConfiguration { + + @Configuration + @ConditionalOnWebApplication + protected static class DeviceResolverAutoConfigurationAdapter extends + WebMvcConfigurerAdapter { + + @Bean + @ConditionalOnMissingBean(DeviceResolverHandlerInterceptor.class) + public DeviceResolverHandlerInterceptor deviceResolverHandlerInterceptor() { + return new DeviceResolverHandlerInterceptor(); + } + + @Bean + public DeviceHandlerMethodArgumentResolver deviceHandlerMethodArgumentResolver() { + return new DeviceHandlerMethodArgumentResolver(); + } + + @Override + public void addInterceptors(InterceptorRegistry registry) { + registry.addInterceptor(deviceResolverHandlerInterceptor()); + } + + @Override + public void addArgumentResolvers( + List argumentResolvers) { + argumentResolvers.add(deviceHandlerMethodArgumentResolver()); + } + + } + +} diff --git a/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories b/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories index ab86b5042b..84aa853580 100644 --- a/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories +++ b/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories @@ -10,6 +10,7 @@ org.springframework.boot.autoconfigure.data.MongoRepositoriesAutoConfiguration,\ org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration,\ org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration,\ org.springframework.boot.autoconfigure.jms.JmsTemplateAutoConfiguration,\ +org.springframework.boot.autoconfigure.mobile.DeviceResolverAutoConfiguration,\ org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration,\ org.springframework.boot.autoconfigure.reactor.ReactorAutoConfiguration,\ org.springframework.boot.autoconfigure.thymeleaf.ThymeleafAutoConfiguration,\ diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/mobile/DeviceResolverAutoConfigurationTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/mobile/DeviceResolverAutoConfigurationTests.java new file mode 100644 index 0000000000..62a1d2efad --- /dev/null +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/mobile/DeviceResolverAutoConfigurationTests.java @@ -0,0 +1,112 @@ +/* + * Copyright 2013 the original author or authors. + * + * Licensed 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.springframework.boot.autoconfigure.mobile; + +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.fail; + +import java.lang.reflect.Field; +import java.util.List; + +import org.junit.After; +import org.junit.Test; +import org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration; +import org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext; +import org.springframework.boot.context.embedded.EmbeddedServletContainerCustomizerBeanPostProcessor; +import org.springframework.boot.context.embedded.EmbeddedServletContainerFactory; +import org.springframework.boot.context.embedded.MockEmbeddedServletContainerFactory; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.mobile.device.DeviceHandlerMethodArgumentResolver; +import org.springframework.mobile.device.DeviceResolverHandlerInterceptor; +import org.springframework.util.ReflectionUtils; +import org.springframework.web.context.support.AnnotationConfigWebApplicationContext; +import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping; + +/** + * Tests for {@link DeviceResolverAutoConfiguration}. + * + * @author Roy Clarkson + */ +public class DeviceResolverAutoConfigurationTests { + + private static final MockEmbeddedServletContainerFactory containerFactory = new MockEmbeddedServletContainerFactory(); + + private AnnotationConfigWebApplicationContext context; + + @After + public void close() { + if (this.context != null) { + this.context.close(); + } + } + + @Test + public void deviceResolverHandlerInterceptorCreated() throws Exception { + this.context = new AnnotationConfigWebApplicationContext(); + this.context.register(DeviceResolverAutoConfiguration.class); + this.context.refresh(); + assertNotNull(this.context.getBean(DeviceResolverHandlerInterceptor.class)); + } + + @Test + public void deviceHandlerMethodArgumentResolverCreated() throws Exception { + this.context = new AnnotationConfigWebApplicationContext(); + this.context.register(DeviceResolverAutoConfiguration.class); + this.context.refresh(); + assertNotNull(this.context.getBean(DeviceHandlerMethodArgumentResolver.class)); + } + + @Test + @SuppressWarnings("unchecked") + public void deviceResolverHandlerInterceptorRegistered() throws Exception { + AnnotationConfigEmbeddedWebApplicationContext context = new AnnotationConfigEmbeddedWebApplicationContext(); + context.register(Config.class, WebMvcAutoConfiguration.class, + DeviceResolverAutoConfiguration.class); + context.refresh(); + RequestMappingHandlerMapping mapping = (RequestMappingHandlerMapping) context + .getBean("requestMappingHandlerMapping"); + Field interceptorsField = ReflectionUtils.findField( + RequestMappingHandlerMapping.class, "interceptors"); + interceptorsField.setAccessible(true); + List interceptors = (List) ReflectionUtils.getField( + interceptorsField, mapping); + context.close(); + for (Object o : interceptors) { + if (o instanceof DeviceResolverHandlerInterceptor) { + return; + } + } + fail("DeviceResolverHandlerInterceptor was not registered."); + } + + @Configuration + protected static class Config { + + @Bean + public EmbeddedServletContainerFactory containerFactory() { + return containerFactory; + } + + @Bean + public EmbeddedServletContainerCustomizerBeanPostProcessor embeddedServletContainerCustomizerBeanPostProcessor() { + return new EmbeddedServletContainerCustomizerBeanPostProcessor(); + } + + } + +} diff --git a/spring-boot-cli/samples/device.groovy b/spring-boot-cli/samples/device.groovy new file mode 100644 index 0000000000..015a566bec --- /dev/null +++ b/spring-boot-cli/samples/device.groovy @@ -0,0 +1,21 @@ +package org.test + +@Controller +@EnableDeviceResolver +class Example { + + @RequestMapping("/") + @ResponseBody + public String helloWorld(Device device) { + if (device.isNormal()) { + return "Hello Normal Device!" + } else if (device.isMobile()) { + return "Hello Mobile Device!" + } else if (device.isTablet()) { + return "Hello Tablet Device!" + } else { + return "Hello Unknown Device!" + } + } + +} diff --git a/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/autoconfigure/SpringMobileCompilerAutoConfiguration.java b/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/autoconfigure/SpringMobileCompilerAutoConfiguration.java new file mode 100644 index 0000000000..fef8cf9606 --- /dev/null +++ b/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/autoconfigure/SpringMobileCompilerAutoConfiguration.java @@ -0,0 +1,64 @@ +/* + * Copyright 2013 the original author or authors. + * + * Licensed 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.springframework.boot.cli.compiler.autoconfigure; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.codehaus.groovy.ast.ClassNode; +import org.codehaus.groovy.control.CompilationFailedException; +import org.codehaus.groovy.control.customizers.ImportCustomizer; +import org.springframework.boot.cli.compiler.AstUtils; +import org.springframework.boot.cli.compiler.CompilerAutoConfiguration; +import org.springframework.boot.cli.compiler.DependencyCustomizer; + +/** + * {@link CompilerAutoConfiguration} for Spring Mobile. + * + * @author Roy Clarkson + */ +public class SpringMobileCompilerAutoConfiguration extends CompilerAutoConfiguration { + + @Override + public boolean matches(ClassNode classNode) { + return AstUtils.hasAtLeastOneAnnotation(classNode, "EnableDeviceResolver"); + } + + @Override + public void applyDependencies(DependencyCustomizer dependencies) + throws CompilationFailedException { + dependencies.add("org.springframework.mobile", "spring-mobile-device", + dependencies.getProperty("spring-mobile.version")); + } + + @Override + public void applyImports(ImportCustomizer imports) throws CompilationFailedException { + imports.addStarImports("org.springframework.mobile.device"); + imports.addImports(EnableDeviceResolver.class.getCanonicalName()); + } + + @Target(ElementType.TYPE) + @Documented + @Retention(RetentionPolicy.RUNTIME) + public static @interface EnableDeviceResolver { + + } + +} diff --git a/spring-boot-cli/src/main/resources/META-INF/services/org.springframework.boot.cli.compiler.CompilerAutoConfiguration b/spring-boot-cli/src/main/resources/META-INF/services/org.springframework.boot.cli.compiler.CompilerAutoConfiguration index 3e389e829c..11fbbf1b8d 100644 --- a/spring-boot-cli/src/main/resources/META-INF/services/org.springframework.boot.cli.compiler.CompilerAutoConfiguration +++ b/spring-boot-cli/src/main/resources/META-INF/services/org.springframework.boot.cli.compiler.CompilerAutoConfiguration @@ -10,3 +10,4 @@ org.springframework.boot.cli.compiler.autoconfigure.SpockCompilerAutoConfigurati org.springframework.boot.cli.compiler.autoconfigure.TransactionManagementCompilerAutoConfiguration org.springframework.boot.cli.compiler.autoconfigure.SpringIntegrationCompilerAutoConfiguration org.springframework.boot.cli.compiler.autoconfigure.SpringSecurityCompilerAutoConfiguration +org.springframework.boot.cli.compiler.autoconfigure.SpringMobileCompilerAutoConfiguration diff --git a/spring-boot-cli/src/test/java/org/springframework/boot/cli/SampleIntegrationTests.java b/spring-boot-cli/src/test/java/org/springframework/boot/cli/SampleIntegrationTests.java index bf1c012e3c..dd50531399 100644 --- a/spring-boot-cli/src/test/java/org/springframework/boot/cli/SampleIntegrationTests.java +++ b/spring-boot-cli/src/test/java/org/springframework/boot/cli/SampleIntegrationTests.java @@ -16,6 +16,9 @@ package org.springframework.boot.cli; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + import java.io.File; import java.net.URL; import java.util.concurrent.Callable; @@ -34,14 +37,12 @@ import org.springframework.boot.OutputCapture; import org.springframework.boot.cli.command.CleanCommand; import org.springframework.boot.cli.command.RunCommand; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; - /** * Integration tests to exercise the samples. * * @author Dave Syer * @author Greg Turnquist + * @author Roy Clarkson */ public class SampleIntegrationTests { @@ -214,4 +215,12 @@ public class SampleIntegrationTests { output.contains("Received Greetings from Spring Boot via RabbitMQ")); } + @Test + public void deviceSample() throws Exception { + start("samples/device.groovy"); + String result = FileUtil.readEntirely(new URL("http://localhost:8080") + .openStream()); + assertEquals("Hello Normal Device!", result); + } + } diff --git a/spring-boot-dependencies/pom.xml b/spring-boot-dependencies/pom.xml index 1de90eb2fe..26eea2d776 100644 --- a/spring-boot-dependencies/pom.xml +++ b/spring-boot-dependencies/pom.xml @@ -41,6 +41,7 @@ 1.4.1.RELEASE 1.3.1.RELEASE 1.2.0.RELEASE + 1.1.0.RELEASE 2.0.16 2.0.0 1.1.1 @@ -439,6 +440,11 @@ spring-rabbit ${spring-rabbit.version} + + org.springframework.mobile + spring-mobile-device + ${spring-mobile.version} + org.thymeleaf thymeleaf