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 ee6141a668a..04eeb3bef91 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.Map.Entry; import javax.annotation.PostConstruct; import javax.servlet.DispatcherType; @@ -40,6 +41,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; import org.springframework.boot.autoconfigure.web.DispatcherServletAutoConfiguration; import org.springframework.boot.context.embedded.FilterRegistrationBean; +import org.springframework.boot.context.embedded.RegistrationBean; import org.springframework.boot.context.embedded.ServletRegistrationBean; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; @@ -69,7 +71,7 @@ public class JerseyAutoConfiguration implements WebApplicationInitializer { @Autowired private JerseyProperties jersey; - + @Autowired private ListableBeanFactory context; @@ -89,7 +91,7 @@ public class JerseyAutoConfiguration implements WebApplicationInitializer { public FilterRegistrationBean requestContextFilter() { FilterRegistrationBean registration = new FilterRegistrationBean(); registration.setFilter(new RequestContextFilter()); - registration.setOrder(jersey.getFilter().getOrder()-1); + registration.setOrder(this.jersey.getFilter().getOrder() - 1); registration.setName("requestContextFilter"); return registration; } @@ -98,33 +100,47 @@ public class JerseyAutoConfiguration implements WebApplicationInitializer { @ConditionalOnMissingBean(name = "jerseyFilterRegistration") @ConditionalOnExpression("'${spring.jersey.type:servlet}' == 'filter'") public FilterRegistrationBean jerseyFilterRegistration() { - Class configType = this.config.getClass(); FilterRegistrationBean registration = new FilterRegistrationBean(); registration.setFilter(new ServletContainer()); registration.setUrlPatterns(Arrays.asList(this.path)); - registration.setOrder(jersey.getFilter().getOrder()); - registration.addInitParameter(ServletProperties.JAXRS_APPLICATION_CLASS, - configType.getName()); - registration.addInitParameter(CommonProperties.METAINF_SERVICES_LOOKUP_DISABLE, "true"); + registration.setOrder(this.jersey.getFilter().getOrder()); + registration.addInitParameter(ServletProperties.FILTER_CONTEXT_PATH, + stripPattern(this.path)); + addInitParameters(registration); registration.setName("jerseyFilter"); - registration.setDispatcherTypes(EnumSet.allOf(DispatcherType.class)); + registration.setDispatcherTypes(EnumSet.allOf(DispatcherType.class)); return registration; } - + + private String stripPattern(String path) { + if (path.endsWith("/*")) { + path = path.substring(0, path.lastIndexOf("/*")); + } + return path; + } + @Bean @ConditionalOnMissingBean(name = "jerseyServletRegistration") @ConditionalOnExpression("'${spring.jersey.type:servlet}' == 'servlet'") public ServletRegistrationBean jerseyServletRegistration() { - Class configType = this.config.getClass(); ServletRegistrationBean registration = new ServletRegistrationBean( new ServletContainer(), this.path); - registration.addInitParameter(ServletProperties.JAXRS_APPLICATION_CLASS, - configType.getName()); - registration.addInitParameter(CommonProperties.METAINF_SERVICES_LOOKUP_DISABLE, "true"); + addInitParameters(registration); registration.setName("jerseyServlet"); return registration; } + private void addInitParameters(RegistrationBean registration) { + Class configType = this.config.getClass(); + registration.addInitParameter(ServletProperties.JAXRS_APPLICATION_CLASS, + configType.getName()); + registration.addInitParameter(CommonProperties.METAINF_SERVICES_LOOKUP_DISABLE, + "true"); + for (Entry entry : this.jersey.getInit().entrySet()) { + registration.addInitParameter(entry.getKey(), entry.getValue()); + } + } + @Override public void onStartup(ServletContext servletContext) throws ServletException { // We need to switch *off* the Jersey WebApplicationInitializer because it @@ -138,7 +154,9 @@ public class JerseyAutoConfiguration implements WebApplicationInitializer { return "/*"; } String path = annotation.value(); - return ((path.isEmpty() || path.equals("/")) ? "/*" : path + "/*"); + if (!path.startsWith("/")) { + path = "/" + path; + } + return path.equals("/") ? "/*" : path + "/*"; } - } diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jersey/JerseyProperties.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jersey/JerseyProperties.java index 7a6daf98d46..c063132973e 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jersey/JerseyProperties.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jersey/JerseyProperties.java @@ -15,6 +15,9 @@ */ package org.springframework.boot.autoconfigure.jersey; +import java.util.HashMap; +import java.util.Map; + import org.springframework.boot.context.properties.ConfigurationProperties; /** @@ -30,10 +33,12 @@ public class JerseyProperties { private Type type = Type.servlet; + private Map init = new HashMap(); + private Filter filter = new Filter(); public Filter getFilter() { - return filter; + return this.filter; } public void setFilter(Filter filter) { @@ -41,19 +46,27 @@ public class JerseyProperties { } public Type getType() { - return type; + return this.type; } public void setType(Type type) { this.type = type; } + public Map getInit() { + return this.init; + } + + public void setInit(Map init) { + this.init = init; + } + public static class Filter { private int order; public int getOrder() { - return order; + return this.order; } public void setOrder(int order) { diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jersey/JerseyAutoConfigurationCustomFilterContextPathTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jersey/JerseyAutoConfigurationCustomFilterContextPathTests.java new file mode 100644 index 00000000000..8de302806b9 --- /dev/null +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jersey/JerseyAutoConfigurationCustomFilterContextPathTests.java @@ -0,0 +1,106 @@ +/* + * Copyright 2012-2014 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.jersey.JerseyAutoConfigurationCustomFilterContextPathTests.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.junit.Assert.assertEquals; + +/** + * Tests for {@link JerseyAutoConfiguration} when using custom servlet paths. + * + * @author Dave Syer + */ +@RunWith(SpringJUnit4ClassRunner.class) +@SpringApplicationConfiguration(classes = Application.class) +@IntegrationTest({ "server.port=0", "spring.jersey.type=filter", + "server.contextPath=/app" }) +@WebAppConfiguration +public class JerseyAutoConfigurationCustomFilterContextPathTests { + + @Value("${local.server.port}") + private int port; + + private RestTemplate restTemplate = new TestRestTemplate(); + + @Test + public void contextLoads() { + ResponseEntity entity = this.restTemplate.getForEntity( + "http://localhost:" + this.port + "/app/rest/hello", String.class); + assertEquals(HttpStatus.OK, entity.getStatusCode()); + } + + @MinimalWebConfiguration + @ApplicationPath("/rest") + @Path("/hello") + public static class Application extends ResourceConfig { + + @Value("${message:World}") + private String msg; + + @GET + public String message() { + return "Hello " + this.msg; + } + + public Application() { + register(Application.class); + } + + public static void main(String[] args) { + SpringApplication.run(Application.class, args); + } + + } + + @Target(ElementType.TYPE) + @Retention(RetentionPolicy.RUNTIME) + @Documented + @Import({ EmbeddedServletContainerAutoConfiguration.class, + ServerPropertiesAutoConfiguration.class, JerseyAutoConfiguration.class, + PropertyPlaceholderAutoConfiguration.class }) + protected static @interface MinimalWebConfiguration { + } + +} diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jersey/JerseyAutoConfigurationFilterTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jersey/JerseyAutoConfigurationCustomFilterPathTests.java similarity index 96% rename from spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jersey/JerseyAutoConfigurationFilterTests.java rename to spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jersey/JerseyAutoConfigurationCustomFilterPathTests.java index bf7374c78b8..560a8026b1e 100644 --- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jersey/JerseyAutoConfigurationFilterTests.java +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jersey/JerseyAutoConfigurationCustomFilterPathTests.java @@ -16,8 +16,6 @@ package org.springframework.boot.autoconfigure.jersey; -import static org.junit.Assert.assertEquals; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; @@ -34,7 +32,7 @@ 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.jersey.JerseyAutoConfigurationFilterTests.Application; +import org.springframework.boot.autoconfigure.jersey.JerseyAutoConfigurationCustomFilterPathTests.Application; import org.springframework.boot.autoconfigure.web.EmbeddedServletContainerAutoConfiguration; import org.springframework.boot.autoconfigure.web.ServerPropertiesAutoConfiguration; import org.springframework.boot.test.IntegrationTest; @@ -47,6 +45,8 @@ import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.test.context.web.WebAppConfiguration; import org.springframework.web.client.RestTemplate; +import static org.junit.Assert.assertEquals; + /** * Tests for {@link JerseyAutoConfiguration} when using custom servlet paths. * @@ -56,7 +56,7 @@ import org.springframework.web.client.RestTemplate; @SpringApplicationConfiguration(classes = Application.class) @IntegrationTest({ "server.port=0", "spring.jersey.type=filter" }) @WebAppConfiguration -public class JerseyAutoConfigurationFilterTests { +public class JerseyAutoConfigurationCustomFilterPathTests { @Value("${local.server.port}") private int port; @@ -71,7 +71,7 @@ public class JerseyAutoConfigurationFilterTests { } @MinimalWebConfiguration - @ApplicationPath("/rest") + @ApplicationPath("rest") @Path("/hello") public static class Application extends ResourceConfig { diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jersey/JerseyAutoConfigurationCustomServletContextPathTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jersey/JerseyAutoConfigurationCustomServletContextPathTests.java new file mode 100644 index 00000000000..af82873d9b3 --- /dev/null +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jersey/JerseyAutoConfigurationCustomServletContextPathTests.java @@ -0,0 +1,105 @@ +/* + * Copyright 2012-2014 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.jersey.JerseyAutoConfigurationCustomServletContextPathTests.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.junit.Assert.assertEquals; + +/** + * Tests for {@link JerseyAutoConfiguration} when using custom servlet paths. + * + * @author Dave Syer + */ +@RunWith(SpringJUnit4ClassRunner.class) +@SpringApplicationConfiguration(classes = Application.class) +@IntegrationTest({ "server.port=0", "server.contextPath=/app" }) +@WebAppConfiguration +public class JerseyAutoConfigurationCustomServletContextPathTests { + + @Value("${local.server.port}") + private int port; + + private RestTemplate restTemplate = new TestRestTemplate(); + + @Test + public void contextLoads() { + ResponseEntity entity = this.restTemplate.getForEntity( + "http://localhost:" + this.port + "/app/rest/hello", String.class); + assertEquals(HttpStatus.OK, entity.getStatusCode()); + } + + @MinimalWebConfiguration + @ApplicationPath("/rest") + @Path("/hello") + public static class Application extends ResourceConfig { + + @Value("${message:World}") + private String msg; + + @GET + public String message() { + return "Hello " + this.msg; + } + + public Application() { + register(Application.class); + } + + public static void main(String[] args) { + SpringApplication.run(Application.class, args); + } + + } + + @Target(ElementType.TYPE) + @Retention(RetentionPolicy.RUNTIME) + @Documented + @Import({ EmbeddedServletContainerAutoConfiguration.class, + ServerPropertiesAutoConfiguration.class, JerseyAutoConfiguration.class, + PropertyPlaceholderAutoConfiguration.class }) + protected static @interface MinimalWebConfiguration { + } + +} diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jersey/JerseyAutoConfigurationDefaultFilterPathTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jersey/JerseyAutoConfigurationDefaultFilterPathTests.java new file mode 100644 index 00000000000..bbe052c63c2 --- /dev/null +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jersey/JerseyAutoConfigurationDefaultFilterPathTests.java @@ -0,0 +1,103 @@ +/* + * Copyright 2012-2014 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.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.jersey.JerseyAutoConfigurationDefaultFilterPathTests.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.junit.Assert.assertEquals; + +/** + * Tests for {@link JerseyAutoConfiguration} when using custom servlet paths. + * + * @author Dave Syer + */ +@RunWith(SpringJUnit4ClassRunner.class) +@SpringApplicationConfiguration(classes = Application.class) +@IntegrationTest({ "server.port=0", "spring.jersey.type=filter" }) +@WebAppConfiguration +public class JerseyAutoConfigurationDefaultFilterPathTests { + + @Value("${local.server.port}") + private int port; + + private RestTemplate restTemplate = new TestRestTemplate(); + + @Test + public void contextLoads() { + ResponseEntity entity = this.restTemplate.getForEntity( + "http://localhost:" + this.port + "/hello", String.class); + assertEquals(HttpStatus.OK, entity.getStatusCode()); + } + + @MinimalWebConfiguration + @Path("/hello") + public static class Application extends ResourceConfig { + + @Value("${message:World}") + private String msg; + + @GET + public String message() { + return "Hello " + this.msg; + } + + public Application() { + register(Application.class); + } + + public static void main(String[] args) { + SpringApplication.run(Application.class, args); + } + + } + + @Target(ElementType.TYPE) + @Retention(RetentionPolicy.RUNTIME) + @Documented + @Import({ EmbeddedServletContainerAutoConfiguration.class, + ServerPropertiesAutoConfiguration.class, JerseyAutoConfiguration.class, + PropertyPlaceholderAutoConfiguration.class }) + protected static @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 13d1a9e915a..5f4fa432cf2 100644 --- a/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc +++ b/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc @@ -1083,11 +1083,22 @@ can `@Autowired` dependencies and inject external configuration with `@Value`. T servlet will be registered and mapped to '`+/*+`' by default. You can change the mapping by adding `@ApplicationPath` to your `ResourceConfig`. +By default Jersey will be set up as a Servlet in a `@Bean` of type +`ServletRegistrationBean` named "jerseyServletRegistration". You can +disable or override that bean by creating one of your own with the +same name. You can also use a Filter instead of a Servlet by setting +`spring.jersey.type=filter` (in which case the `@Bean` to replace or +override is "jerseyFilterRegistration"). The servlet has an `@Order` +which you can set with `spring.jersey.filter.order`. Both the Servlet +and the Filter registrations can be given init parameters using +`spring.jersey.init.*` to specify a map of properties. + There is a {github-code}/spring-boot-samples/spring-boot-sample-jersey[Jersey sample] so you can see how to set things up. There is also a {github-code}/spring-boot-samples/spring-boot-sample-jersey1[Jersey 1.x sample]. Note that in the Jersey 1.x sample that the spring-boot maven plugin has been configured to -unpack some Jersey jars so they can be scanned by the JAX-RS implementation (the sample -asks for them to be scanned in its `Filter` registration). +unpack some Jersey jars so they can be scanned by the JAX-RS implementation (because the sample +asks for them to be scanned in its `Filter` registration). You may need to do the same +if any of your JAX-RS resources are packages as nested jars.