Spring Jersey to inject ContainerRequestContext on request scoped class - java

I already looked at a lot of posts and nothing seems to work quite as i liked it to.
I want to inject an object into ContainerRequestContext properties from a filter and retrieve it later in other classes.
here is my filter:
#Priority(Priorities.AUTHENTICATION)
public class AuthorizationFilter implements ContainerRequestFilter {
#Override
public void filter(ContainerRequestContext containerRequestContext) throws IOException {
containerRequestContext.setProperty("myObject", new Object());
}
}
here is the class I want access to ContainerRequestContext:
#Provider
public class SessionContextProvider implements ISessionContextProvider {
#Context
private ContainerRequestContext request;
#Override
public Object getSessionContext() {
return request.getProperty("mySessionContext");
}
}
and my spring config:
#Bean(name="sessionContextProvider")
#Scope(value = "request", proxyMode = ScopedProxyMode.INTERFACES)
public ISessionContextProvider sessionContextProvider() {
return new SessionContextProvider();
}
Everything works as expected if I inject ContainerRequestContext into my web resource. However if call my provider class ContainerRequestContext is always null.
I don't seem why this would not work.
Reagrds
Jonas

The problem is that with the Jersey/Spring integration, it allows us to successfully inject Spring beans into Jersey components, but this is not always true the other way around.
Jersey has it's own DI framework, HK21, and it is responsible for handle the injections with Jersey components. With the Jersey Spring integration, Jersey will lookup the Spring Bean, and take it as is, it won't inject it with any dependencies, I guess assuming Spring should take care of it's own injections.
That being said, if you don't require the ISessionContextProvider to be a Spring bean, then you can just make it an HK2 service. It's pretty simple. If you don't require any special initialization, you can just let HK2 create it. Here a simple configuration
public JerseyConfig extends ResourceConfig {
public JerseyConfig() {
register(new AbstractBinder() {
bind(SessionContextProvider.class)
.to(ISessionContextProvider.class)
.in(RequestScoped.class);
});
}
}
And that's it. You have an injectable ISessionContextProvider2.
If you require the ISessionContextProvider provider to be a Spring bean, then another option is to grab the bean from the Spring ApplicatinContext, and explicitly inject it yourself, using HK2's analogue of the ApplicationContext, its ServiceLocator. To do that we would need to use a Factory to do all the work transparently, so you can still inject the bean without doing any extra work on the outside
import javax.inject.Inject;
import org.glassfish.hk2.api.Factory;
import org.glassfish.hk2.api.ServiceLocator;
import org.springframework.context.ApplicationContext;
public class SessionContextProviderFactory
implements Factory<SessionContextProvider> {
private final ISessionContextProvider provider;
#Inject
public SessionContextProviderFactory(ApplicationContext ctx,
ServiceLocator locator) {
provider = ctx.getBean(ISessionContextProvider.class);
locator.inject(provider);
}
#Override
public ISessionContextProvider provide() {
return provider;
}
#Override
public void dispost(ISessionContextProvider provider) { /* noop */ }
}
Then just register the factory
public JerseyConfig extends ResourceConfig {
public JerseyConfig() {
register(new AbstractBinder() {
bindFactory(SessionContextProviderFactory.class)
.to(ISessionContextProvider.class)
.in(RequestScoped.class);
});
}
}
1 - hk2
2 - See also Dependency injection with Jersey 2.0

I found a workaround. I could inject the Spring HttpServletRequest into my AuthorizationFilter and set the SessionContext on this one.
#Autowired
private HttpServletRequest request;
....
request.setAttribute("mySessionContext", sessionContext);
And then because HttpServletRequest is known to spring and besically represents the same thing in my SessionContextProvider I do the same:
#Autowired
private HttpServletRequest request;
#Override
public SessionContext getSessionContext() {
return (SessionContext) request.getAttribute("mySessionContext");
}
I dont think this is the best solution possible. But it works. Ill wait for peeskillet for any other input if there is a better solution.
Regards
Jonas

Related

jax-rs share information between ContainerRequestFilter and ReaderInterceptor

I'm using Jax-rs, and I'm doing some logic in the Filters, and then I would like to share information between ContainerRequestFilter(Filter) and ReaderInterceptor(Interceptor).
I can see that between Filters and Interceptors is possible through the set/getProperties but between Filter and Interceptors is not possible.
Any idea if there's any other mechanism?.
Regards.
You can use a request scoped service that you inject into both the filter and the interceptor. For instance
public interface RequestScopedService {
void setSomething(Object something);
Object getSomething();
}
public class RequestScopedServiceImpl implements RequestScopedService {
#Override
public void setSomething(Object something) {}
#Override
public Object getSomething() {}
}
It's best practice to use interfaces, that's why I did that here. To configure it, you register an AbstractBinder with your ResourceConfig 1.
public class JerseyConfig extends ResourceConfig {
public JerseyConfig() {
register(new AbstractBinder() {
#Override
public void configure() {
bind(RequestScopedServiceImpl.class)
.to(RequestScopedService.class)
.proxy(true)
.proxyForSameScope(false)
.in(RequestScoped.class);
}
});
}
}
Now you can inject it into both the filter and the interceptor.
public class MyFilter implements ContainerRequestFilter {
private final RequestScopedService service;
#Inject
public MyFilter(RequestScopedService service) {
this.service = service;
}
}
public class MyInterceptor implements ReaderInterceptor {
private final RequestScopedService service;
#Inject
public MyInterceptor(RequestScopedService service) {
this.service = service;
}
}
We use the proxy() method to configure it because the service is a request scoped service (meaning a new one is created for each request) and the filter and writer interceptor are singletons. So we need it to be a proxy that forwards calls to the real service under the hood 2.
1. If you are not using a ResourceConfig for your configuration (maybe you are using web.xml), see both my answer in "Dependency Injection with Jersey 2.0 and What exactly is the ResourceConfig class in Jersey 2?.
2. You can learn some more about this in this article
So any easier way than using a separate service for this is to simply inject the ContainerRequestContext into the ReaderInterceptor. We need to inject it as a javax.inject.Provider so that we can lazy retrieve it. If we don't do this, we will run into scoping issues, as the interceptor is inherently a singleton and the request context is request scoped (meaning a new one is created for each request).
public static class MyInterceptor implements ReaderInterceptor {
private final javax.inject.Provider<ContainerRequestContext> requestProvider;
#Inject
public MyInterceptor(javax.inject.Provider<ContainerRequestContext> requestProvider) {
this.requestProvider = requestProvider;
}
#Override
public Object aroundReadFrom(ReaderInterceptorContext readerInterceptorContext) throws IOException, WebApplicationException {
ContainerRequestContext request = requestProvider.get();
String prop = (String) request.getProperty("SomeProp");
}
}
With the javax.inject.Provider, we get the actual service by calling get(). Because we are using Provider, the service will be retrieved from a request scoped context, meaning that the instance will be tied to the request.
1. See Request Scoped Injection into a Singleton with Jersey for more information.

How to inject spring bean into ContainerRequestFilter using AutoWired?

I'm using RESTEasy 3 and Spring 4 and I'm trying to inject #Autowired an service bean into my interceptor as follow below:
But running this code it's returning Null Pointer Exception when access my access service:
#Provider
#MyAnnotationToIntercept
public class MyInterceptor implements ContainerRequestFilter {
private MyAccessService accessService;
#Autowired
public MyInterceptor(MyAccessService accessService) {
this.accessService = accessService;
}
public MyInterceptor() {
}
#Override
public void filter(ContainerRequestContext requestContext) {
// DO SOME STUFF Using accessService
}
}
#Component
public class MyAccessService {
private MyDep1 dep1;
#Autowired
public MyAccessService(Mydep1 dep1) {
this.dep1= dep1;
}
}
Is there any way to achieve this? It's really possible?
You will need to use WebApplicationContextUtils's method to get a bean inside filter which is not managed by spring. Here is the example
MyAccessService myAccessService = (MyAccessService) WebApplicationContextUtils.getRequiredWebApplicationContext(httpServletRequest .getServletContext()).getBean(MyAccessService.class);
And to get HttpServletRequest instance you can use #context injection
#Context
private HttpServletRequest httpServletRequest ;
Looks like you have placed #Autowired annotation at the wrong place. It should be above the declaration of accessService. And depending on how you have configured application context, you may/may not need a setter method for accessService instance variable.

Why #EnableWs removed aop proxy from spring bean

I am trying to add custom interceptor in my spring boot web service project. I follow this example and created this config:
package org.example;
import java.util.List;
import org.aspect.PersistentAspect;
import org.springframework.aop.support.AopUtils;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.oxm.jaxb.Jaxb2Marshaller;
import org.springframework.ws.config.annotation.EnableWs;
import org.springframework.ws.config.annotation.WsConfigurerAdapter;
import org.springframework.ws.server.EndpointInterceptor;
import org.springframework.ws.transport.http.MessageDispatcherServlet;
import org.springframework.xml.xsd.SimpleXsdSchema;
import org.springframework.xml.xsd.XsdSchema;
#EnableWs
#Configuration
public class WsConfig extends WsConfigurerAdapter {
#Bean
public ServletRegistrationBean messageDispatcherServlet(ApplicationContext applicationContext) {
final MessageDispatcherServlet servlet = new MessageDispatcherServlet();
servlet.setApplicationContext(applicationContext);
servlet.setTransformWsdlLocations(true);
return new ServletRegistrationBean(servlet, "/v1/*");
}
#Bean
public XsdSchema schema() {
return new SimpleXsdSchema(new ClassPathResource("country.xsd"));
}
#Bean
public Jaxb2Marshaller marshaller() {
Jaxb2Marshaller marshaller = new Jaxb2Marshaller();
String[] jaxbContext = new String[] { "io.spring.guides.gs_producing_web_service" };
marshaller.setContextPaths(jaxbContext);
return marshaller;
}
#Override
public void addInterceptors(List<EndpointInterceptor> interceptors) {
// aop not working
interceptors.add(new CustomValidatingInterceptor(schema(), config()));
// aop working
// interceptors.add(new CustomValidatingInterceptor(schema(), null));
}
#Bean
public AppConfig config() {
return new AppConfig();
}
#Bean
public PersistentAspect persistentAspect() {
PersistentAspect persistentAspect = new PersistentAspect();
return persistentAspect;
}
#Bean
public Object testAop() {
System.out.println("is config aop proxy: " + AopUtils.isAopProxy(config()));
return null;
}
}
however when I am adding new interceptor in addInterceptors method I have problem with removed aop proxy in my config class. Any idea why ? Whole project is on git.
The problem is the initialization sequence in Spring. Technically, because there is a BeanPostProcessor for the WS Endpoint (AnnotationActionEndpointMapping in spring-ws), it will force the early initialization of any dependencies this needs - especially any EndpointInterceptor beans.
One way to counter this is to rearrange the BeanPostProcessor's, or even create one of your own, but usually its simpler to stay with the default configuration in Spring - to avoid similar surprises elsewhere in the initialization sequence.
Perhaps a simpler way to avoid the problem is to use an ObjectFactory in the EndpointInterceptor bean. This will delay instantiating the AppConfig bean until it is referenced, by which time the Aop weaving will also have taken place.
#Component
public class CustomValidatingInterceptor extends PayloadValidatingInterceptor {
#Autowired
private ObjectFactory<AppConfig> konfigurace;
#Override
public boolean handleRequest(MessageContext messageContext, Object endpoint)
throws IOException, SAXException, TransformerException {
System.out.println("is config aop proxy in interceptor: " +
AopUtils.isAopProxy(konfigurace.getObject()));
return super.handleRequest(messageContext, endpoint);
}
Clearly, this then means the CustomValidatingInterceptor must be referenced from WsConfig as an injected (autowired) bean.
Thanks for the example - there's a fork here that uses the ObjectFactory technique. This showed the config bean as an Aop proxy in all of WsConfig.testAop(), the CountryEndpoint and the CustomValidatingInterceptor when I sent a request in from SoapUI.
Here's another possibility to solve this issue. This relates to following Stack overflow question: Spring WS interceptors with injected DAO's #Transactional not working. In short the issue comes from the fact that the method
#Override
public void addInterceptors(List<EndpointInterceptor> interceptors) {
gets called before Spring dependency injection has time to register Spring AOP beans. In my case it was #Transactional that was ignored by Spring-WS but it could be anything.
Fortunately for us Spring-WS uses mutable collections instead of immutable. When the addInterceptors() method
gets called, we can just save the collection and thus we have a reference to the same collection instance that is used by Spring-WS. Later on you can initialize your interceptor bean properly and add it to the collection.
You also have to get around the fact that if you use #Autowired the bean gets prepared before the annotations can take place. Thus you have to create it manually by calling ApplicationContext.getBean() method.
#EnableWs
#Configuration
// The magic is to implement both ApplicationContextAware
// that injects the applicationContext for us
// and BeanPostProcessor that gives us postProcessBeforeInitialization()
// where we initialize our interceptor correctly
// and add it to the collection
public class WebServiceConfig extends WsConfigurerAdapter implements ApplicationContextAware, BeanPostProcessor {
// This is the interceptor that uses dependencies with #Transactional annotation.
// It will not work with #Autowired
private MyInterceptorThatHasTransactionalDependencies myInterceptorThatHasTransactionalDependencies;
// Fortunately Spring WS uses mutable collections so we can fill
// this list later on as long as we just initialize it with
private List<EndpointInterceptor> interceptors;
// This is our application context where all the beans are defined
private ApplicationContext context;
#Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
// save application context for later use
this.context = applicationContext;
}
#Nullable
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
// This method gets called multiple times so initialize interceptor just once
if(myInterceptorThatHasTransactionalDependencies == null){
myInterceptorThatHasTransactionalDependencies = context.getBean(MyInterceptorThatHasTransactionalDependencies.class);
interceptors.add(myInterceptorThatHasTransactionalDependencies);
}
return bean;
}
#Override
public void addInterceptors(List<EndpointInterceptor> interceptors) {
// Save the list of interceptors so we can modify it later on
this.interceptors = interceptors;
if (myInterceptorThatHasTransactionalDependencies == null) {
System.out.println("myInterceptorThatHasTransactionalDependencies was null like we expected");
} else {
interceptors.add(myInterceptorThatHasTransactionalDependencies);
}
}
}
Just to let you know that I am not Spring bean lifecycle expert, so there might be a better place to situate the interceptor initialization than postProcessBeforeInitialization(). That said, this works for me.

ResourceInfo not injected into spring managed jersey filter

I am having problems injecting the ResourceInfo into my jersey filter. The annotation #Context doesn't seem to work properly when having a spring managed filter instance.
What I found is this:
Jersey Request Filter only on certain URI
The second goal is quite similar to my situation.
DynamicFeature:
#Provider
public class HttpCacheConfigDynamicFeature implements DynamicFeature {
private final DefaultHttpCacheFilter defaultHttpCacheFilter;
private final HttpCacheFilter httpCacheFilter;
#Autowired
public HttpCacheConfigDynamicFeature(DefaultHttpCacheFilter defaultHttpCacheFilter, HttpCacheFilter httpCacheFilter) {
this.defaultHttpCacheFilter = defaultHttpCacheFilter;
this.httpCacheFilter = httpCacheFilter;
}
...
Filter:
#Component
public class HttpCacheFilter implements ContainerResponseFilter {
private Log logger = LogFactory.getLog(HttpCacheFilter.class);
private final ConfigurationController configurationController;
#Context
private ResourceInfo resourceInfo;
#Autowired
public HttpCacheFilter(ConfigurationController configurationController) {
this.configurationController = configurationController;
}
...
The resource info is always null.
What I do now is to change my dynamicFeature a little to that:
#Provider
public class HttpCacheConfigDynamicFeature implements DynamicFeature {
private final DefaultHttpCacheFilter defaultHttpCacheFilter;
private final ConfigurationController configurationController;
#Autowired
public HttpCacheConfigDynamicFeature(DefaultHttpCacheFilter defaultHttpCacheFilter, ConfigurationController configurationController) {
this.defaultHttpCacheFilter = defaultHttpCacheFilter;
this.configurationController = configurationController;
}
#Override
public void configure(ResourceInfo resourceInfo, FeatureContext featureContext) {
if (hasHttpCacheConfigAnnotation(resourceInfo)) {
featureContext.register(new HttpCacheFilter(configurationController, resourceInfo), 50);
} else {
featureContext.register(defaultHttpCacheFilter, 51);
}
}
Maybe you know a more elegant way to achieve my goals.
Thanks!
Christoph
In normal cases it would work, as Jersey would still inject it. But there is a known issue with the DynamicFeature and registering instances. If you register it as a class (i.e. register(HttpCacheFilter.class)), then injections will work.
In your case, I don't see any reason you need to manually pass the ConfigurationController to the HttpCacheFilter that you instantiate. If you just register the class like in the previous example, it will automatically be injected.
Also (not tested) but I think you can even clean it up and just use #Inject on the constructor of the filter. Then you can have both the ResourceInfo and the ConfigurationController both as constructor arguments. Not a big deal, but to me it's looks cleaner just having one injection point. Like I said, I didn't test it, but I'm pretty sure it will work. #Inject and #Context are pretty much interchangeable with Jersey, and you can also use #Inject for Spring components in Jersey, so I would imagine that it just works.

Injection to ContainerRequestFilter in a java REST API

I guess I am dealing with a bug in Glassfish 4; but I am not sure. Basically I am trying to inject a service into a ContainerRequestFilter; but I am getting an exception while trying. I can do injection in resources where I make rest calls, but I can't use injection for filters. There is a filed bug in glassfish jira: https://java.net/jira/browse/GLASSFISH-20597. I tried the workaround mentioned in there but it didn't solve my problem.
Here is the exception I get:
org.glassfish.hk2.api.UnsatisfiedDependencyException: There was no object available for injection at Injectee(requiredType=Authenticator,parent=SecurityInterceptor,qualifiers
Here is the code that I am working with, do you have any idea what I am missing here?
#Provider
#PreMatching
public class SecurityInterceptor implements ContainerRequestFilter {
#Inject
private Authenticator authenticator; // can't inject this service here, but I can inject this to RequestScoped Resources
private static final Logger LOG = Logger.getLogger(SecurityInterceptor.class.getName());
#Override
public void filter(ContainerRequestContext requestContext) throws IOException {
if (!requestContext.getMethod().equals("OPTIONS")) {
String path = OyumBuFunctions.normalizeString(requestContext.getUriInfo().getPath());
String authToken = requestContext.getHeaderString(OyumbuRestHeaders.AUTH_TOKEN_HEADER);
if (!authenticator.isAuthTokenValid(authToken)) {
requestContext.abortWith(Response.status(Response.Status.UNAUTHORIZED).build());
}
else{
LOG.log(Level.INFO, "authenticated");
}
}
}
}
I ran into a similar issue and came across this posting while searching for a solution. Fortunately I found a solution and thought to post it here. Note that this only works in JAX-RS 2.0 as it makes use of the DynamicFeature class
#Provider
public class AuthenticatorFeature implements DynamicFeature {
#Inject
private Autheticator authenticator;
public void configure(ResourceInfo resourceInfo, FeatureContext context) {
context.register(new SecurityInterceptor(authenticator);
}
}
Binding the authenticator to the ContainerRequestFilter.
public class SecurityInterceptor implements ContainerRequestFilter {
Authenticator authenticator;
public SecurityInterceptor(Authenticator authenticator){
this.authenticator = authenticator;
}
#Override
public void filter(ContainerRequestContext requestContext) throws IOException {
authenticator.doSomething(...);
}
}
Some hopefully helpful readings
creating resource filters with jersey and jaxrs 2.0
Securing REST Resources with JAX-RS 2.0
As mentioned in this answer https://stackoverflow.com/a/23996225/1045081, giving #Path annotation to the service to be injected saves the situation. Still didn't understand how to solve it properly, but just writing it here so that it could be useful for someone who encounters a similar problem.

Categories

Resources