Issue injecting #Context in resource class - spring

I'm having difficulties injecting HttpHeaders into my rest service class.
#Path("/account")
#Produces({ MediaType.APPLICATION_JSON })
#Consumes({ MediaType.APPLICATION_JSON })
#Transactional
#Service
public class UserServiceImpl implements UserService {
#Context
private HttpHeaders headers;
#Override
#POST #Path("/{username}/")
public User get(#PathParam(value = "username") String username)
throws UnknownUserException {
String requestId = headers.getHeaderString("requestId");
return new User();
}
}
When I try running the application I get the following exception during the spring initialisation:
Caused by: java.lang.IllegalArgumentException: Can not set javax.ws.rs.core.HttpHeaders field com.acme.service.UserServiceImpl.headers to com.sun.proxy.$Proxy50
at sun.reflect.UnsafeFieldAccessorImpl.throwSetIllegalArgumentException(UnsafeFieldAccessorImpl.java:167)
at sun.reflect.UnsafeFieldAccessorImpl.throwSetIllegalArgumentException(UnsafeFieldAccessorImpl.java:171)
at sun.reflect.UnsafeFieldAccessorImpl.ensureObj(UnsafeFieldAccessorImpl.java:58)
at sun.reflect.UnsafeObjectFieldAccessorImpl.set(UnsafeObjectFieldAccessorImpl.java:75)
at java.lang.reflect.Field.set(Field.java:758)
at org.apache.cxf.jaxrs.utils.InjectionUtils$1.run(InjectionUtils.java:192)
at java.security.AccessController.doPrivileged(Native Method)
at org.apache.cxf.jaxrs.utils.InjectionUtils.injectFieldValue(InjectionUtils.java:188)
at org.apache.cxf.jaxrs.utils.InjectionUtils.injectContextProxiesAndApplication(InjectionUtils.java:1058)
at org.apache.cxf.jaxrs.JAXRSServerFactoryBean.injectContexts(JAXRSServerFactoryBean.java:405)
at org.apache.cxf.jaxrs.JAXRSServerFactoryBean.updateClassResourceProviders(JAXRSServerFactoryBean.java:429)
at org.apache.cxf.jaxrs.JAXRSServerFactoryBean.create(JAXRSServerFactoryBean.java:162)
A bit of experimenting shows that I get the same error when trying to inject anything. I have an exception mapper that has the http headers injected in do problem what so ever, so I created a custom provider to obtain the headers but get the same problem injecting that in.
I'm pretty sure I must be missing something fundamental here.
I know that I could add what I need out of the headers as params to the method but I can't change the interface.
Adding the context to the operation
#Override
#POST #Path("/{username}/")
public User get(#Context HttpHeaders httpHeaders, #PathParam(value = "username") String username)
Gives me the following error.
java.lang.IllegalArgumentException: object is not an instance of declaring class
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:483)
at org.apache.cxf.service.invoker.AbstractInvoker.performInvocation(AbstractInvoker.java:181)
at org.apache.cxf.service.invoker.AbstractInvoker.invoke(AbstractInvoker.java:97)
I've discovered that if I drop the implements interface then the application actually starts, however nothing is actually injected in.

I've found a workaround (or possibly the expected solution). It appears that spring is using a proxy based on the interface. I created an intermediate interface and stuck on a setHttpHeaders operation on the interface and annotated the implementation with #Context. All seems fine with that.
public interface MyService {
void doStuff();
}
public interface MyServiceInt extends MyService {
void setHttpHeaders(HttpHeaders headers);
}
#Path("/")
#Produces({ MediaType.APPLICATION_JSON })
#Consumes({ MediaType.APPLICATION_JSON })
#Transactional
#Service
public class MyServiceImpl implements MyServiceInt {
private HttpHeaders httpHeaders;
#Context
void setHttpHeaders(HttpHeaders headers) {
this.httpHeaders = httpHeaders;
}
#Override
#POST #Path("/doStuff")
public void doStuff() {
}
}
Would still like to hear of a better solution if anyone knows.

Related

How can I use RequestHeader with actuator endpoint?

I have customized my actuator/info endpoint and I want to use information from the header to authorize a RestTemplate call to another service.
I am implementing the InfoContributor as here:
https://www.baeldung.com/spring-boot-info-actuator-custom
I want to accept request headers in the contribute() method. For any user defined REST endpoint, I can define a #RequestHeader parameter and access headers.
But unfortunately, the InfoContributor's contribute() method takes only one parameter.
How can I access a request header inside the contribute() method?
You can autowire HttpServletRequest into your InfoContributor
import javax.servlet.http.HttpServletRequest;
#Component
public class Custom implements InfoContributor {
#Autowired
private HttpServletRequest request;
#Override
public void contribute(Info.Builder builder) {
...
request.getHeader("your header");
...
}
}
Or you can use RequestContextHolder to get hold of it
#Component
public class Custom implements InfoContributor {
#Override
public void contribute(Info.Builder builder) {
...
HttpServletRequest request =
((ServletRequestAttributes)RequestContextHolder.getRequestAttributes())
.getRequest();
request.getHeader("your header");
...
}
}

ConversationScoped in Quarkus

I am migrating an application from Thorntail to Quarkus. It uses the the conversation scope annotation in a bean that provides the token information during all the rest api request to any service interested in it. But in Quarkus documentation it says the conversation scope is not implemented. Is there a similar feature I can use?
Here is what I want to do:
#Path
#ApplicationScoped
public class FruitsResource {
#Inject FruitsService fruitsService;
#POST
public int post (Fruit fruit) {
return fruitsService.post(fruit);
}
}
#Provider
#ApplicationScoped
private class AuthorizationFilter implements ContainerRequestFilter {
#Inject AuthorizationHolder authorizationHolder;
#Override
public void filter (ContainerRequestContext request) {
String token = request.getHeaderString(HttpHeaders.AUTHORIZATION);
Authorization authorization = createAuthorizationFromToken(token);
authorizationHolder.setAuthorization(authorization);
}
}
#ConversationScoped
private class AuthorizationHolder {
private Authorization authorization;
#Produces
public Authorization getAuthorization () {
return authorization;
}
public void setAuthorization (Authorization authorization) {
this.authorization = authorization;
}
}
#ApplicationScoped
private class FruitsService {
#Inject Authorization authorization;
#Inject EntityManager entityManager;
#Transactional
public void post (Fruit fruit) {
// do some complex validation with the authorization object
...
// persist object
entityManager.persist(fruit);
entityManager.flush();
return fruit.getId();
}
}
Is the Authorization header present in each request? I suppose it is (or should be), in which case just using #RequestScoped instead of #ConversationScoped should work. This is probably the best thing to do, anyway.
In case the header is only present in "first" request and subsequent requests in the same session can reuse the token, then you can just replace #ConversationScoped with #SessionScoped. I think enforcing the header to be present in all requests would be better, though.
Finally, if you'd really like to emulate conversations, you can do something like this (not tested, not even written in an IDE, just from the top of my head):
#SessionScoped
private class AuthorizationHolder {
private ConcurrentMap<String, Authorization> authorizations = new ConcurrentHashMap<>();
public Authorization getAuthorization(ContainerRequestContext request) {
return authorizations.get(getConversationId(request));
}
public void setAuthorization(ContainerRequestContext request, Authorization authorization) {
this.authorizations.put(getConversationId(request), authorization);
}
private String getConversationId(ContainerRequestContext request) {
MultivaluedMap<String, String> query = request.getUriInfo().getQueryParameters();
return query.getFirst("cid");
}
}
However, as I said above, I really think you should make the bean #RequestScoped and force the clients to send the Authorization header with each request.

Spring Data Rest: #Autowire in Custom JsonDeserializer

I am trying to autowire a component into a custom JsonDeserializer but cannot get it right even with the following suggestions I found:
Autowiring in JsonDeserializer: SpringBeanAutowiringSupport vs HandlerInstantiator
Right way to write JSON deserializer in Spring or extend it
How to customise the Jackson JSON mapper implicitly used by Spring Boot?
Spring Boot Autowiring of JsonDeserializer in Integration test
My final goal is to accept URLs to resources in different microservices and store only the ID of the resource locally. But I don't want to just extract the ID from the URL but also verify that the rest of the URL is correct.
I have tried many things and lost track a bit of what I tried but I believe I tried everything mentioned in the links above. I created tons of beans for SpringHandlerInstantiator, Jackson2ObjectMapperBuilder, MappingJackson2HttpMessageConverter, RestTemplate and others and also tried with setting the SpringHandlerInstantiator in RepositoryRestConfigurer#configureJacksonObjectMapper.
I am using Spring Boot 2.1.6.RELEASE which makes me think something might have changed since some of the linked threads are quite old.
Here's my last attempt:
#Configuration
public class JacksonConfig {
#Bean
public HandlerInstantiator handlerInstantiator(ApplicationContext applicationContext) {
return new SpringHandlerInstantiator(applicationContext.getAutowireCapableBeanFactory());
}
}
#Configuration
public class RestConfiguration implements RepositoryRestConfigurer {
#Autowired
private Validator validator;
#Autowired
private HandlerInstantiator handlerInstantiator;
#Override
public void configureValidatingRepositoryEventListener(ValidatingRepositoryEventListener validatingListener) {
validatingListener.addValidator("beforeCreate", validator);
validatingListener.addValidator("beforeSave", validator);
}
#Override
public void configureJacksonObjectMapper(ObjectMapper objectMapper) {
objectMapper.setHandlerInstantiator(handlerInstantiator);
}
}
#Component
public class RestResourceURLSerializer extends JsonDeserializer<Long> {
#Autowired
private MyConfig config;
#Override
public Long deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
ServiceConfig serviceConfig = config.getServices().get("identity");
URI serviceUri = serviceConfig.getExternalUrl();
String servicePath = serviceUri.getPath();
URL givenUrl = p.readValueAs(URL.class);
String givenPath = givenUrl.getPath();
if (servicePath.equals(givenPath)) {
return Long.parseLong(givenPath.substring(givenPath.lastIndexOf('/') + 1));
}
return null;
}
}
I keep getting a NullPointerException POSTing something to the API endpoint that is deserialized with the JsonDeserializer above.
I was able to solve a similar problem by marking my deserializer constructor accept a parameter (and therefore removing the empty constructor) and marking constructor as #Autowired.
public class MyDeserializer extends JsonDeserializer<MyEntity> {
private final MyBean bean;
// no default constructor
#Autowired
public MyDeserializer(MyBean bean){
this.bean = bean
}
...
}
#JsonDeserialize(using = MyDeserializer.class)
public class MyEntity{...}
My entity is marked with annotation #JsonDeserialize so I don't have to explicitly register it with ObjectMapper.

Error creating bean with name 'scopedTarget.oauth2ClientContext': Scope 'request' is not active for the current thread for feign client

I am calling another microservice once my current microservice is up and ready using feign client in my current microservice built using Jhipster.
So my Feign Interface is
package com.persistent.integration.client;
import java.util.List;
import org.springframework.data.domain.Pageable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import com.persistent.integration.service.dto.DataPipelineDTO;
#AuthorizedFeignClient(name = "Integrationconfiguration")
public interface DataPipelinesResourceFeign {
#RequestMapping(value = "/api/data-pipelines", method = RequestMethod.GET)
List<DataPipelineDTO> getAllDataPipelines(#RequestParam(value = "pageable") Pageable pageable );
}
}
And I have implemented ApplicationRunner where I have called feign client method.
#Component
public class ApplicationInitializer implements ApplicationRunner {
#Autowired
private DataPipelinesResourceFeign dataPipelinesResourceFeign;
#Autowired
private ActiveMQListener activeMqListener;
#Override
public void run(ApplicationArguments args) throws Exception {
// TODO Auto-generated method stub
Pageable pageable = PageRequest.of(0, 20);
try {
List <DataPipelineDTO> allStartedDataPipeLines = dataPipelinesResourceFeign.getAllDataPipelines(pageable); //.stream().filter(p->p.getState().equals(State.STARTED)).collect(Collectors.toList());
allStartedDataPipeLines.forEach(datapipe ->
{
try {
activeMqListener.consume(datapipe);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
});
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
But after running this, it gives below exception at dataPipelinesResourceFeign.getAllDataPipelines :
com.netflix.hystrix.exception.HystrixRuntimeException: DataPipelinesResourceFeign#getAllDataPipelines(Pageable) failed and no fallback available.
at com.netflix.hystrix.AbstractCommand$22.call(AbstractCommand.java:819)
at com.netflix.hystrix.AbstractCommand$22.call(AbstractCommand.java:804)
at rx.internal.operators.OperatorOnErrorResumeNextViaFunction$4.onError(OperatorOnErrorResumeNextViaFunction.java:140)
at rx.internal.operators.OnSubscribeDoOnEach$DoOnEachSubscriber.onError(OnSubscribeDoOnEach.java:87)
at rx.internal.operators.OnSubscribeDoOnEach$DoOnEachSubscriber.onError(OnSubscribeDoOnEach.java:87)
at com.netflix.hystrix.AbstractCommand$DeprecatedOnFallbackHookApplication$1.onError(AbstractCommand.java:1472)
Caused by: org.springframework.beans.factory.BeanCreationException:
Error creating bean with name 'scopedTarget.oauth2ClientContext':
Scope 'request' is not active for the current thread; consider
defining a scoped proxy for this bean if you intend to refer to it
from a singleton; nested exception is java.lang.IllegalStateException:
No thread-bound request found: Are you referring to request attributes
outside of an actual web request, or processing a request outside of
the originally receiving thread? If you are actually operating within
a web request and still receive this message, your code is probably
running outside of DispatcherServlet/DispatcherPortlet: In this case,
use RequestContextListener or RequestContextFilter to expose the
current request. at
org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(Abstrac>tBeanFactory.java:362)
at
org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractB>eanFactory.java:199)
at
org.springframework.aop.target.SimpleBeanTargetSource.getTarget(SimpleBeanTarge>tSource.java:35)
at
org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.>java:193)
at com.sun.proxy.$Proxy147.getAccessToken(Unknown Source) at
com.persistent.integration.security.oauth2.AuthorizationHeaderUtil.getAuthoriza>tionHeaderFromOAuth2Context(AuthorizationHeaderUtil.java:28)
at
com.persistent.integration.client.TokenRelayRequestInterceptor.apply(TokenRelay>RequestInterceptor.java:23)
at
feign.SynchronousMethodHandler.targetRequest(SynchronousMethodHandler.java:158)
at
feign.SynchronousMethodHandler.executeAndDecode(SynchronousMethodHandler.java:88)
at
feign.SynchronousMethodHandler.invoke(SynchronousMethodHandler.java:76)
at
feign.hystrix.HystrixInvocationHandler$1.run(HystrixInvocationHandler.java:108)
at com.netflix.hystrix.HystrixCommand$2.call(HystrixCommand.java:302)
at com.netflix.hystrix.HystrixCommand$2.call(HystrixCommand.java:298)
at
rx.internal.operators.OnSubscribeDefer.call(OnSubscribeDefer.java:46)
... 68 more Caused by: java.lang.IllegalStateException: No
thread-bound request found: Are you referring to request attributes
outside of an actual web request, or processing a request outside of
the originally receiving thread? If you are actually operating within
a web request and still receive this message, your code is probably
running outside of DispatcherServlet/DispatcherPortlet: In this case,
use RequestContextListener or RequestContextFilter to expose the
current request. at
org.springframework.web.context.request.RequestContextHolder.currentRequestAttr>ibutes(RequestContextHolder.java:131)
at
org.springframework.web.context.request.AbstractRequestAttributesScope.get(Abst>ractRequestAttributesScope.java:42)
at
org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(Abstrac>tBeanFactory.java:350)
many suggestions on internet were to add listerner RequestContextListener. But problem persisted even if I added listener in webConfigurer.java in onStartup method.
{
servletContext.addListener(RequestContextListener.class);
}
But of no use.
Any leads would be appreciated.
I found a workaround for this. I don't know why TokenRelayRequestIntercepton isn't working but you can use your own RequestInterceptor based on Spring's SecurityContext.
First, define a RequestInterceptor :
public class MyRequestInterceptor implements RequestInterceptor {
public static final String AUTHORIZATION = "Authorization";
public static final String BEARER = "Bearer";
public MyRequestInterceptor() {
super();
}
#Override
public void apply(RequestTemplate template) {
// demander un token à keycloak et le joindre à la request
Optional<String> header = getAuthorizationHeader();
if (header.isPresent()) {
template.header(AUTHORIZATION, header.get());
}
}
public static Optional<String> getAuthorizationHeader() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication != null && authentication.getDetails() != null && authentication.getDetails() instanceof OAuth2AuthenticationDetails) {
OAuth2AuthenticationDetails oAuth2AuthenticationDetails =
(OAuth2AuthenticationDetails) authentication.getDetails();
return Optional.of(String.format("%s %s", oAuth2AuthenticationDetails.getTokenType(),
oAuth2AuthenticationDetails.getTokenValue()));
} else {
return Optional.empty();
}
}
}
and then, declare a config class for your feign client using your RequestInterceptor, it should contains something like this :
#Bean(name = "myRequestInterceptor")
public RequestInterceptor getMyRequestInterceptor() throws IOException {
return new MyRequestInterceptor();
}
Your Feign client shoud look like this:
#FeignClient(name = "SERVICE_NAME", configuration = MyFeignConfiguration.class)
public interface MyRestClient {
I had the same issue with Feign Client running on startup using ApplicationRunner and I came up with following solution.
I defined my FeignClientsConfiguration with OAuth2FeignRequestInterceptor, which accepts predefined bean DefaultOAuth2ClientContext and OAuth2 configuration OAuth2ProtectedResourceDetails:
#Configuration
public class MyConfig extends FeignClientsConfiguration {
#Bean
public RequestInterceptor oauth2FeignRequestInterceptor( DefaultOAuth2ClientContext oAuth2ClientContext, MyOauth2Properties properties) {
return new OAuth2FeignRequestInterceptor(oAuth2ClientContext, resourceDetails(properties));
}
#Bean
public DefaultOAuth2ClientContext oAuth2ClientContext() {
return new DefaultOAuth2ClientContext();
}
private OAuth2ProtectedResourceDetails resourceDetails(MyOauth2Properties oauth2Properties) {
ResourceOwnerPasswordResourceDetails resourceDetails = new ResourceOwnerPasswordResourceDetails();
resourceDetails.setAccessTokenUri(oauth2Properties.getAccessTokenUri());
resourceDetails.setUsername(oauth2Properties.getUsername());
resourceDetails.setPassword(oauth2Properties.getPassword());
resourceDetails.setClientId(oauth2Properties.getClientId());
return resourceDetails;
}
}
Your feign client will look something like this:
#FeignClient(url = "http://localhost:8080/api/v1")
public interface FeignClient {
}
After all this, calling FeignClient from ApplicationRunner.run() works fine.
Spring Boot 2.2.6

Spring Data Rest - How to receive Headers in #RepositoryEventHandler

I'm using the latest Spring Data Rest and I'm handling the event "before create". The requirement I have is to capture also the HTTP Headers submitted to the POST endpoint for the model "Client". However, the interface for the RepositoryEventHandler does not expose that.
#Component
#RepositoryEventHandler
public class ClientEventHandler {
#Autowired
private ClientService clientService;
#HandleBeforeCreate
public void handleClientSave(Client client) {
...
...
}
}
How can we handle events and capture the HTTP Headers? I'd like to have access to the parameter like Spring MVC that uses the #RequestHeader HttpHeaders headers.
You can simply autowire the request to a field of your EventHandler
#Component
#RepositoryEventHandler
public class ClientEventHandler {
private HttpServletRequest request;
public ClientEventHandler(HttpServletRequest request) {
this.request = request;
}
#HandleBeforeCreate
public void handleClientSave(Client client) {
System.out.println("handling events like a pro");
Enumeration<String> names = request.getHeaderNames();
while (names.hasMoreElements())
System.out.println(names.nextElement());
}
}
In the code given I used Constructor Injection, which I think is the cleanest, but Field or Setter injection should work just as well.
I actually found the solution on stackoverflow: Spring: how do I inject an HttpServletRequest into a request-scoped bean?
Oh, and I just noticed #Marc proposed this in thecomments ... but I actually tried it :)

Resources