diff --git a/spring-boot-autoconfigure/pom.xml b/spring-boot-autoconfigure/pom.xml index 1493a6a1a60..4500ac787c9 100755 --- a/spring-boot-autoconfigure/pom.xml +++ b/spring-boot-autoconfigure/pom.xml @@ -130,6 +130,11 @@ jersey-spring3 true + + org.glassfish.jersey.media + jersey-media-json-jackson + true + commons-dbcp commons-dbcp diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jersey/JerseyAutoConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jersey/JerseyAutoConfiguration.java index 3c30ecdb71d..252ea2c2e84 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jersey/JerseyAutoConfiguration.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jersey/JerseyAutoConfiguration.java @@ -18,6 +18,7 @@ package org.springframework.boot.autoconfigure.jersey; import java.util.Arrays; import java.util.EnumSet; +import java.util.List; import java.util.Map.Entry; import javax.annotation.PostConstruct; @@ -26,15 +27,21 @@ import javax.servlet.ServletContext; import javax.servlet.ServletException; import javax.servlet.ServletRegistration; import javax.ws.rs.ApplicationPath; +import javax.ws.rs.ext.ContextResolver; +import javax.ws.rs.ext.Provider; + +import com.fasterxml.jackson.databind.ObjectMapper; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.glassfish.jersey.CommonProperties; +import org.glassfish.jersey.jackson.JacksonFeature; import org.glassfish.jersey.server.ResourceConfig; import org.glassfish.jersey.servlet.ServletContainer; import org.glassfish.jersey.servlet.ServletProperties; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.AutoConfigureBefore; import org.springframework.boot.autoconfigure.AutoConfigureOrder; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; @@ -42,7 +49,9 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate; import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; +import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration; import org.springframework.boot.autoconfigure.web.DispatcherServletAutoConfiguration; import org.springframework.boot.context.embedded.FilterRegistrationBean; import org.springframework.boot.context.embedded.RegistrationBean; @@ -51,6 +60,7 @@ import org.springframework.boot.context.properties.EnableConfigurationProperties import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.Ordered; +import org.springframework.core.annotation.AnnotationAwareOrderComparator; import org.springframework.core.annotation.AnnotationUtils; import org.springframework.core.annotation.Order; import org.springframework.util.ClassUtils; @@ -73,6 +83,7 @@ import org.springframework.web.filter.RequestContextFilter; @ConditionalOnWebApplication @AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE) @AutoConfigureBefore(DispatcherServletAutoConfiguration.class) +@AutoConfigureAfter(JacksonAutoConfiguration.class) @EnableConfigurationProperties(JerseyProperties.class) public class JerseyAutoConfiguration implements ServletContextAware { @@ -84,10 +95,18 @@ public class JerseyAutoConfiguration implements ServletContextAware { @Autowired private ResourceConfig config; + @Autowired(required = false) + private List customizers; + private String path; @PostConstruct public void path() { + resolveApplicationPath(); + customize(); + } + + private void resolveApplicationPath() { if (StringUtils.hasLength(this.jersey.getApplicationPath())) { this.path = parseApplicationPath(this.jersey.getApplicationPath()); } @@ -97,6 +116,15 @@ public class JerseyAutoConfiguration implements ServletContextAware { } } + private void customize() { + if (this.customizers != null) { + AnnotationAwareOrderComparator.sort(this.customizers); + for (ResourceConfigCustomizer customizer : this.customizers) { + customizer.customize(this.config); + } + } + } + @Bean @ConditionalOnMissingBean public FilterRegistrationBean requestContextFilter() { @@ -193,6 +221,37 @@ public class JerseyAutoConfiguration implements ServletContextAware { // will try and register a ContextLoaderListener which we don't need servletContext.setInitParameter("contextConfigLocation", ""); } + } + + @ConditionalOnClass(JacksonFeature.class) + @ConditionalOnSingleCandidate(ObjectMapper.class) + @Configuration + static class JacksonResourceConfigCustomizer { + + @Bean + public ResourceConfigCustomizer resourceConfigCustomizer() { + return new ResourceConfigCustomizer() { + @Override + public void customize(ResourceConfig config) { + config.register(JacksonFeature.class); + config.register(ObjectMapperContextResolver.class); + } + }; + } + + @Provider + static class ObjectMapperContextResolver + implements ContextResolver { + + @Autowired + private ObjectMapper objectMapper; + + @Override + public ObjectMapper getContext(Class type) { + return this.objectMapper; + } + + } } diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jersey/ResourceConfigCustomizer.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jersey/ResourceConfigCustomizer.java new file mode 100644 index 00000000000..0bb08cc030d --- /dev/null +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jersey/ResourceConfigCustomizer.java @@ -0,0 +1,36 @@ +/* + * Copyright 2012-2016 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.jersey; + +import org.glassfish.jersey.server.ResourceConfig; + +/** + * Callback interface that can be implemented by beans wishing to customize Jersey's + * {@link ResourceConfig} before it is used. + * + * @author Eddú Meléndez + * @since 1.4.0 + */ +public interface ResourceConfigCustomizer { + + /** + * Customize the resource config. + * @param config the {@link ResourceConfig} to customize + */ + void customize(ResourceConfig config); + +} diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jersey/JerseyAutoConfigurationCustomObjectMapperProviderTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jersey/JerseyAutoConfigurationCustomObjectMapperProviderTests.java new file mode 100644 index 00000000000..ec2b55812a8 --- /dev/null +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jersey/JerseyAutoConfigurationCustomObjectMapperProviderTests.java @@ -0,0 +1,136 @@ +/* + * Copyright 2012-2016 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.jersey; + +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 javax.ws.rs.ApplicationPath; +import javax.ws.rs.GET; +import javax.ws.rs.Path; + +import org.glassfish.jersey.server.ResourceConfig; +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration; +import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration; +import org.springframework.boot.autoconfigure.jersey.JerseyAutoConfigurationObjectMapperProviderTests.Application; +import org.springframework.boot.autoconfigure.web.EmbeddedServletContainerAutoConfiguration; +import org.springframework.boot.autoconfigure.web.ServerPropertiesAutoConfiguration; +import org.springframework.boot.test.IntegrationTest; +import org.springframework.boot.test.SpringApplicationConfiguration; +import org.springframework.boot.test.TestRestTemplate; +import org.springframework.context.annotation.Import; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.web.WebAppConfiguration; +import org.springframework.web.client.RestTemplate; + +import static org.assertj.core.api.Assertions.assertThat; + + +/** + * @author Eddú Meléndez + */ +@RunWith(SpringJUnit4ClassRunner.class) +@SpringApplicationConfiguration(Application.class) +@IntegrationTest({ "server.port=0", "spring.jackson.serialization-inclusion=non_null" }) +@WebAppConfiguration +public class JerseyAutoConfigurationCustomObjectMapperProviderTests { + + @Value("${local.server.port}") + private int port; + + private RestTemplate restTemplate = new TestRestTemplate(); + + @Test + public void contextLoads() { + ResponseEntity response = this.restTemplate.getForEntity( + "http://localhost:" + this.port + "/rest/message", String.class); + assertThat(HttpStatus.OK).isEqualTo(response.getStatusCode()); + assertThat("{\"subject\":\"Jersey\"}").isEqualTo(response.getBody()); + } + + @MinimalWebConfiguration + @ApplicationPath("/rest") + @Path("/message") + public static class Application extends ResourceConfig { + + @GET + public Message message() { + return new Message("Jersey", null); + } + + public Application() { + register(Application.class); + } + + public static void main(String[] args) { + SpringApplication.run(Application.class, args); + } + + } + + public static class Message { + + private String subject; + + private String body; + + public Message() { + + } + + public Message(String subject, String body) { + this.subject = subject; + this.body = body; + } + + public String getSubject() { + return this.subject; + } + + public void setSubject(String subject) { + this.subject = subject; + } + + public String getBody() { + return this.body; + } + + public void setBody(String body) { + this.body = body; + } + } + + @Target(ElementType.TYPE) + @Retention(RetentionPolicy.RUNTIME) + @Documented + @Import({ EmbeddedServletContainerAutoConfiguration.class, + JacksonAutoConfiguration.class, ServerPropertiesAutoConfiguration.class, + JerseyAutoConfiguration.class, PropertyPlaceholderAutoConfiguration.class }) + protected @interface MinimalWebConfiguration { + } + +} diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jersey/JerseyAutoConfigurationObjectMapperProviderTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jersey/JerseyAutoConfigurationObjectMapperProviderTests.java new file mode 100644 index 00000000000..87234ee25a7 --- /dev/null +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jersey/JerseyAutoConfigurationObjectMapperProviderTests.java @@ -0,0 +1,135 @@ +/* + * Copyright 2012-2016 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.jersey; + +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 javax.ws.rs.ApplicationPath; +import javax.ws.rs.GET; +import javax.ws.rs.Path; + +import org.glassfish.jersey.server.ResourceConfig; +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration; +import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration; +import org.springframework.boot.autoconfigure.jersey.JerseyAutoConfigurationObjectMapperProviderTests.Application; +import org.springframework.boot.autoconfigure.web.EmbeddedServletContainerAutoConfiguration; +import org.springframework.boot.autoconfigure.web.ServerPropertiesAutoConfiguration; +import org.springframework.boot.test.IntegrationTest; +import org.springframework.boot.test.SpringApplicationConfiguration; +import org.springframework.boot.test.TestRestTemplate; +import org.springframework.context.annotation.Import; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.web.WebAppConfiguration; +import org.springframework.web.client.RestTemplate; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Eddú Meléndez + */ +@RunWith(SpringJUnit4ClassRunner.class) +@SpringApplicationConfiguration(Application.class) +@IntegrationTest("server.port=0") +@WebAppConfiguration +public class JerseyAutoConfigurationObjectMapperProviderTests { + + @Value("${local.server.port}") + private int port; + + private RestTemplate restTemplate = new TestRestTemplate(); + + @Test + public void contextLoads() { + ResponseEntity response = this.restTemplate.getForEntity( + "http://localhost:" + this.port + "/rest/message", String.class); + assertThat(HttpStatus.OK).isEqualTo(response.getStatusCode()); + assertThat("{\"subject\":\"Jersey\",\"body\":null}").isEqualTo(response.getBody()); + } + + @MinimalWebConfiguration + @ApplicationPath("/rest") + @Path("/message") + public static class Application extends ResourceConfig { + + @GET + public Message message() { + return new Message("Jersey", null); + } + + public Application() { + register(Application.class); + } + + public static void main(String[] args) { + SpringApplication.run(Application.class, args); + } + + } + + public static class Message { + + private String subject; + + private String body; + + public Message() { + + } + + public Message(String subject, String body) { + this.subject = subject; + this.body = body; + } + + public String getSubject() { + return this.subject; + } + + public void setSubject(String subject) { + this.subject = subject; + } + + public String getBody() { + return this.body; + } + + public void setBody(String body) { + this.body = body; + } + } + + @Target(ElementType.TYPE) + @Retention(RetentionPolicy.RUNTIME) + @Documented + @Import({ EmbeddedServletContainerAutoConfiguration.class, + JacksonAutoConfiguration.class, ServerPropertiesAutoConfiguration.class, + JerseyAutoConfiguration.class, PropertyPlaceholderAutoConfiguration.class }) + protected @interface MinimalWebConfiguration { + } + +} diff --git a/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc b/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc index ab4dac8ff70..57c77b3bc8b 100644 --- a/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc +++ b/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc @@ -1754,6 +1754,9 @@ all the endpoints: } ---- +You can also register an arbitrary number of beans implementing `ResourceConfigCustomizer` +for more advanced customizations. + All the registered endpoints should be `@Components` with HTTP resource annotations (`@GET` etc.), e.g.