Using Spring Property Placeholders with Jersey @Path and @ApplicationPath

I use Jersey and Spring in my project. 'dersey-spring3' is used to integrate between them. I would like to make my resource classes more flexible and use properties inside @Path annotations, for example:

 @Path("${some.property}/abc/def") 

But Spring cannot insert some.property into Jersey @Path and @ApplicationPath .

Is there a way to have some kind of configurable value (using property files) inside the @Path value of a @Path resource?

(I understand that it would be easier to replace Jersey with Spring MVC, but in my case, unfortunately, I have no such choice.)

+5
source share
1 answer

So here is half the answer (or maybe the full answer, depending on how important it is to solve @ApplicationPath ).

To understand the solution below, you must first learn a little about the insides of Jersey. When we download our application, Jersey builds a model of all resources. All information for the resource is encapsulated in this model. Jersey uses this model to process requests, instead of trying to process resources for each request, store all the information about the resource in the model faster and process the model.

With this architecture, Jersey also allows you to create resources programmatically , using the same APIs as internally to hold model properties. Besides creating resource models, we can also modify existing models using ModelProcessor c.

In ModelProcessor we can introduce a Spring PropertyResolver , and then programmatically allow placeholders and replace the old resource model path with a resolved one. for instance

 @Autowired private PropertyResolver propertyResolver; private ResourceModel processResourceModel(ResourceModel resourceModel) { ResourceModel.Builder newResourceModelBuilder = new ResourceModel.Builder(false); for (final Resource resource : resourceModel.getResources()) { final Resource.Builder resourceBuilder = Resource.builder(resource); String resolvedResourcePath = processPropertyPlaceholder(resource); resourceBuilder.path(resolvedResourcePath); // handle child resources for (Resource childResource : resource.getChildResources()) { String resolvedChildPath = processPropertyPlaceholder(childResource); final Resource.Builder childResourceBuilder = Resource.builder(childResource); childResourceBuilder.path(resolvedChildPath); resourceBuilder.addChildResource(childResourceBuilder.build()); } newResourceModelBuilder.addResource(resourceBuilder.build()); } return newResourceModelBuilder.build(); } private String processPropertyPlaceholder(Resource resource) { String ogPath = resource.getPath(); return propertyResolver.resolvePlaceholders(ogPath); } 

Regarding the resource model APIs

  • This is a Resource

     @Path("resource") public class SomeResource { @GET public String get() {} } 

    His resource methods that are not annotated with @Path are ResourceMethod s

  • This is a child of the Resource above Resource , as it is annotated with @Path .

     @GET @Path("child-resource") public String get() {} 

This information should give you some insight into how this implementation works.

The following is a complete test using the Jersey Test Model . The following classpath properties file is used.

app.properties

 resource=resource sub.resource=sub-resource sub.resource.locator=sub-resource-locator 

You can run the following, like any other JUnit test.

 import javax.ws.rs.GET; import javax.ws.rs.Path; import javax.ws.rs.core.Response; import org.glassfish.jersey.filter.LoggingFilter; import org.glassfish.jersey.server.ResourceConfig; import org.glassfish.jersey.server.model.ModelProcessor; import org.glassfish.jersey.server.model.Resource; import org.glassfish.jersey.server.model.ResourceModel; import org.glassfish.jersey.test.JerseyTest; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.PropertySource; import org.springframework.core.env.PropertyResolver; import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.is; import static org.junit.Assert.assertThat; /** * Qaru http://stackoverflow.com/q/34943650/2587435 * * Run it like any other JUnit test. Required dependencies are as follows: * * <dependency> * <groupId>org.glassfish.jersey.test-framework.providers</groupId> * <artifactId>jersey-test-framework-provider-grizzly2</artifactId> * <version>2.22.1</version> * <scope>test</scope> * </dependency> * <dependency> * <groupId>org.glassfish.jersey.ext</groupId> * <artifactId>jersey-spring3</artifactId> * <version>2.22.1</version> * <scope>test</scope> * </dependency> * <dependency> * <groupId>commons-logging</groupId> * <artifactId>commons-logging</artifactId> * <version>1.1</version> * <scope>test</scope> * </dependency> * * @author Paul Samsotha */ public class SpringPathResolverTest extends JerseyTest { @Path("${resource}") public static class TestResource { @GET public String get() { return "Resource Success!"; } @GET @Path("${sub.resource}") public String getSubMethod() { return "Sub-Resource Success!"; } @Path("${sub.resource.locator}") public SubResourceLocator getSubResourceLocator() { return new SubResourceLocator(); } public static class SubResourceLocator { @GET public String get() { return "Sub-Resource-Locator Success!"; } } } @Configuration @PropertySource("classpath:/app.properties") public static class SpringConfig { } public static class PropertyPlaceholderPathResolvingModelProcessor implements ModelProcessor { @Autowired private PropertyResolver propertyResolver; @Override public ResourceModel processResourceModel(ResourceModel resourceModel, javax.ws.rs.core.Configuration configuration) { return processResourceModel(resourceModel); } @Override public ResourceModel processSubResource(ResourceModel subResourceModel, javax.ws.rs.core.Configuration configuration) { return subResourceModel; } private ResourceModel processResourceModel(ResourceModel resourceModel) { ResourceModel.Builder newResourceModelBuilder = new ResourceModel.Builder(false); for (final Resource resource : resourceModel.getResources()) { final Resource.Builder resourceBuilder = Resource.builder(resource); String resolvedResourcePath = processPropertyPlaceholder(resource); resourceBuilder.path(resolvedResourcePath); // handle child resources for (Resource childResource : resource.getChildResources()) { String resolvedChildPath = processPropertyPlaceholder(childResource); final Resource.Builder childResourceBuilder = Resource.builder(childResource); childResourceBuilder.path(resolvedChildPath); resourceBuilder.addChildResource(childResourceBuilder.build()); } newResourceModelBuilder.addResource(resourceBuilder.build()); } return newResourceModelBuilder.build(); } private String processPropertyPlaceholder(Resource resource) { String ogPath = resource.getPath(); return propertyResolver.resolvePlaceholders(ogPath); } } @Override public ResourceConfig configure() { ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class); return new ResourceConfig(TestResource.class) .property("contextConfig", ctx) .register(PropertyPlaceholderPathResolvingModelProcessor.class) .register(new LoggingFilter(Logger.getAnonymousLogger(), true)); } @Test public void pathPlaceholderShouldBeResolved() { Response response = target("resource").request().get(); assertThat(response.getStatus(), is(200)); assertThat(response.readEntity(String.class), is(equalTo("Resource Success!"))); response.close(); response = target("resource/sub-resource").request().get(); assertThat(response.getStatus(), is(200)); assertThat(response.readEntity(String.class), is(equalTo("Sub-Resource Success!"))); response.close(); response = target("resource/sub-resource-locator").request().get(); assertThat(response.getStatus(), is(200)); assertThat(response.readEntity(String.class), is(equalTo("Sub-Resource-Locator Success!"))); response.close(); } } 

And now, when I think about it, I see a way to use the @ApplicationPath solution, but it includes creating a Jersey servlet container programmatically in Spring WebAppInitializer . Honestly, I think it will be more trouble than it's worth it. I would just pull it in and leave @ApplicationPath as a static string.


UDPATE

If you are using Spring boot then the application path is definitely configured through the spring.jersey.applicationPath property. The Spring Jersey boot path is pretty much the idea that I had in mind in the previous paragraph, where you yourself create the Jersey servlet container and set up the servlet mapping. This is how it is configured using Spring Boot.

+4
source

Source: https://habr.com/ru/post/1241209/


All Articles