How to modify request body before reaching controller in spring boot - java

I have a spring boot application.
I change the request body of every post request.
Is it possible to modify the request body before the request reaches the controller.
Please include an example.

Another alternative would be adding an attribute to the HttpServletRequest object. And after that you can read that attribute in the Controller class with #RequestAttribute annotation.
In the Interceptor
#Component
public class SimpleInterceptor extends HandlerInterceptorAdapter {
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws ServletException, IOException {
String parameter = request.getParameter("parameter");
if (parameter == "somevalue") {
request.setAttribute("customAttribute", "value");
}
return true;
}
}
In the Controller
#RestController
#RequestMapping("")
public class SampleController {
#RequestMapping(value = "/sample",method = RequestMethod.POST)
public String work(#RequestBody SampleRequest sampleRequest, #RequestAttribute("customAttribute") String customAttribute) {
System.out.println(customAttribute);
return "This works";
}
}
This has advantage of not modifying the request body.

Short Answer
Yes, but not easily.
Details
I know of three options to change the body of a request
"before" it arrives at the handler method in the controller;
Use AOP to change the request before the method is called.
Create an HTTP filter.
Create a custom Spring HandlerInterceptor.
Since you are already using spring-boot,
option 3, custom Spring HandlerInterceptor,
seems like the best option for you.
Here is a link to a Baeldung Article covering spring HandlerInterceptors.
The Baeldung article is not the full answer for your problem
because you can only read the InputStrem returned by HttpServletRequest one time.
You will need to create a wrapper class that extends HttpServletRequest
and wrap every request in your wrapper class within your custom HandlerInterceptor or in a custom Filter (Filter might be the way to go here).
Wrapper Class
Read the HttpServletRequest InputStream in the wrapper class constructor
Modify the body per your requirements.
Write the modified body to a ByteArrayOutputStream.
Use toByteArray to retrieve the actual byte[] from the stream.
Close the ByteArrayOutputStream (try-with-resources is good for this).
Override the getInputStream method.
Wrap the byte[] in a ByteArrayInputStream every time the getInputStream is called. Return this stream.
How To Wrap the Request
In your Filter, instantiate your wrapper class and pass in the original request (which is a parameter to the doFilter method).
Pass the wrapper to the chain.doFilter method (not the original request).

My answer using HTTP Filter.
RequestFilter.java
#Component
public class RequestFilter implements Filter {
#Override
public void init(FilterConfig filterConfig) throws ServletException {
}
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
RequestWrapper wrappedRequest = new RequestWrapper((HttpServletRequest) request);
chain.doFilter(wrappedRequest, response);
}
#Override
public void destroy() {
}
}
RequestWrapper.java
public class RequestWrapper extends HttpServletRequestWrapper {
private final String body;
private ObjectMapper objectMapper = new ObjectMapper();
public RequestWrapper(HttpServletRequest request) throws IOException {
// So that other request method behave just like before
super(request);
StringBuilder stringBuilder = new StringBuilder();
BufferedReader bufferedReader = null;
try {
InputStream inputStream = request.getInputStream();
if (inputStream != null) {
bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
char[] charBuffer = new char[128];
int bytesRead = -1;
while ((bytesRead = bufferedReader.read(charBuffer)) > 0) {
stringBuilder.append(charBuffer, 0, bytesRead);
}
} else {
stringBuilder.append("");
}
} catch (IOException ex) {
throw ex;
} finally {
if (bufferedReader != null) {
try {
bufferedReader.close();
} catch (IOException ex) {
throw ex;
}
}
}
// Store request body content in 'requestBody' variable
String requestBody = stringBuilder.toString();
JsonNode jsonNode = objectMapper.readTree(requestBody);
//TODO -- Update your request body here
//Sample
((ObjectNode) jsonNode).remove("key");
// Finally store updated request body content in 'body' variable
body = jsonNode.toString();
}
#Override
public ServletInputStream getInputStream() throws IOException {
final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(body.getBytes());
ServletInputStream servletInputStream = new ServletInputStream() {
public int read() throws IOException {
return byteArrayInputStream.read();
}
#Override
public boolean isFinished() {
return false;
}
#Override
public boolean isReady() {
return false;
}
#Override
public void setReadListener(ReadListener listener) {
}
};
return servletInputStream;
}
#Override
public BufferedReader getReader() throws IOException {
return new BufferedReader(new InputStreamReader(this.getInputStream()));
}
}

Here's how I achieved it using the RequestBodyAdvice:
Create a class that implements RequestBodyAdvice and annotate it with #ControllerAdvice
#ControllerAdvice
public class CustomRequestBodyAdvice implements RequestBodyAdvice {
You will have to implements 4 methods:
a. support: here, you can control which controller you are targeting, and better which request body by specifying the type of the request body
#Override
public boolean supports(MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) {
log.info("In supports() method of {}", getClass().getSimpleName());
return methodParameter.getContainingClass() == AuthorController.class && type.getTypeName() == AuthorDTO.class.getTypeName();
}
b. beforeBodyReady
<!-- language: lang-js -->
#Override
public HttpInputMessage beforeBodyRead(HttpInputMessage httpInputMessage, MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) throws IOException {
log.info("In beforeBodyRead() method of {}", getClass().getSimpleName());
return httpInputMessage;
}
c. afterBodyRead: here it is where you can modify the request body
<!-- language: lang-js -->
#Override
public Object afterBodyRead(Object body, HttpInputMessage httpInputMessage, MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) {
log.info("In afterBodyRead() method of {}", getClass().getSimpleName());
if (body instanceof AuthorDTO) {
AuthorDTO authorDTO = (AuthorDTO) body;
authorDTO.setName("Test");
return authorDTO;
}
return body;
}
d. handleEmptyBody
<!-- language: lang-js -->
#Override
public Object handleEmptyBody(Object body, HttpInputMessage httpInputMessage, MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) {
log.info("In handleEmptyBody() method of {}", getClass().getSimpleName());
return body;
}
Source: http://www.javabyexamples.com/quick-guide-to-requestbodyadvice-in-spring-mvc

One way to this is by reflection. ProceedingJoinPoint contains the args object passed to method
#Aspect
#Component
public class AopInterceptor {
#Around(value = "#annotation(xyz.rpolnx.spring.web.poc.annotation.AopIntercepExample)")
public Object handler(final ProceedingJoinPoint joinPoint) throws Throwable {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
Object[] args = joinPoint.getArgs();
Class<?> someClass = args[0].getClass();
Field field = someClass.getDeclaredField("custom");
field.setAccessible(true);
field.set(args[0], "custom");
field.setAccessible(false);
return joinPoint.proceed();
}
}
#RestController
public class SimpleController {
#PostMapping("/aop")
#AopIntercepExample
public Person handleAopIntercept(#RequestBody Person nodes) {
return nodes;
}
}
#Target({ElementType.METHOD})
#Retention(RetentionPolicy.RUNTIME)
public #interface AopIntercepExample {
}
public class Person {
private String name;
private String id;
private String custom;
}

I had this this issue too. This thread helped me a bit and I wanted to post a simpler solution than Bijaya Bhaskar Swain.
package com.thebois.inpassering.adapters.merchant.staffrestrepo;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.thebois.inpassering.adapters.merchant.FilterOrders;
import com.thebois.inpassering.adapters.merchant.facilityownerrestrepo.FacilityOwner;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.stream.Collectors;
import javax.servlet.FilterChain;
import javax.servlet.ReadListener;
import javax.servlet.ServletException;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse;
import lombok.AllArgsConstructor;
import org.springframework.core.annotation.Order;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import org.springframework.web.util.ContentCachingRequestWrapper;
#Component
#AllArgsConstructor
#Order(value = FilterOrders.STAFF_REQUEST_ORDER)
public class StaffCreationFilter extends OncePerRequestFilter {
ObjectMapper objectMapper;
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
RequestWrapper modifiedRequest = new RequestWrapper(request);
filterChain.doFilter(modifiedRequest, response);
}
#Override
protected boolean shouldNotFilter(HttpServletRequest request) {
return !(request.getServletPath().contains("/staff") && request.getMethod().equals("POST"));
}
private class RequestWrapper extends HttpServletRequestWrapper {
String body;
public RequestWrapper(HttpServletRequest request) throws IOException {
super(request);
String body = new ContentCachingRequestWrapper(request).getReader().lines().collect(Collectors.joining(System.lineSeparator()));
//get your dto
Staff staff = objectMapper.readValue(body, Staff.class);
//edit your dto
long facilityOwnerId = ((Number) request.getAttribute("facilityOwnerId")).longValue();
FacilityOwner facilityOwner = FacilityOwner.builder()
.facilityOwnerId(facilityOwnerId)
.build();
Staff modifiedStaff = Staff.builder()
.facilityOwner(facilityOwner)
.username(staff.getUsername())
.password(new BCryptPasswordEncoder().encode(staff.getPassword()))
.build();
//save your changes to body
this.body = objectMapper.writeValueAsString(modifiedStaff);
}
#Override
public ServletInputStream getInputStream() {
final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(body.getBytes(StandardCharsets.UTF_8));
ServletInputStream servletInputStream = new ServletInputStream() {
public int read() {
return byteArrayInputStream.read();
}
#Override
public boolean isFinished() {
return false;
}
#Override
public boolean isReady() {
return true;
}
#Override
public void setReadListener(ReadListener listener) {
}
};
return servletInputStream;
}
#Override
public BufferedReader getReader() throws IOException {
return new BufferedReader(new InputStreamReader(this.getInputStream()));
}
}
}

Related

After Using HttpServletRequestWrapper I'm getting HttpMessageNotReadableException Required request body is missing

I'm trying to make an authorization mechanism in spring boot. Where I'm returning the PreAuthenticatedPrincipal as a body from the request. But after using the wrapper I'm getting a body not found error.
This is the authFilter
public class AuthKeyAuthFilter extends AbstractPreAuthenticatedProcessingFilter {
#SneakyThrows
#Override
protected Object getPreAuthenticatedPrincipal(HttpServletRequest request) {
CustomHttpRequestBody wrappedRequest = new CustomHttpRequestBody((HttpServletRequest) request);
return new String(wrappedRequest.getBody());
}
#Override
protected Object getPreAuthenticatedCredentials(HttpServletRequest request) {
return "N/A";
}
}
This is the Custom Wrapper
#Getter
public class CustomHttpRequestBody extends HttpServletRequestWrapper {
private final String body;
public CustomHttpRequestBody(HttpServletRequest request) throws IOException {
super(request);
InputStream requestInputStream = request.getInputStream();
body = new String(StreamUtils.copyToByteArray(requestInputStream));
}
#Override
public ServletInputStream getInputStream() throws IOException {
final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(body.getBytes());
ServletInputStream servletInputStream = new ServletInputStream() {
public int read() throws IOException {
return byteArrayInputStream.read();
}
#Override
public boolean isFinished() {
return byteArrayInputStream.available() == 0;
}
#Override
public boolean isReady() {
return true;
}
#Override
public void setReadListener(ReadListener listener) {
// TODO Auto-generated method stub
}
};
return servletInputStream;
}
}
This is the securityconfig
#Configuration
#EnableWebSecurity
#Order(1)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private AuthorizationServiceHandler authorizationServiceHandler;
#Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
AuthKeyAuthFilter filter = new AuthKeyAuthFilter();
filter.setAuthenticationManager(new AuthenticationManager() {
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String principal = (String) authentication.getPrincipal();
if (null == principal || !authorizationServiceHandler.verify(principal))
{
throw new BadCredentialsException("Authorization Error, refer to Documentation");
}
authentication.setAuthenticated(true);
return authentication;
}
});
httpSecurity.
antMatcher("/api/**").
csrf().disable().
sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).
and().addFilter(filter).authorizeRequests().anyRequest().authenticated();
}
}
Quick help appreciated,
Thanks

Jax-rs alter serialization response based on request with jackson

Currently facing an issue where I would like to alter the response of jax-rs resources based on some information passed into the response. The change that need to be made is the format of some of the json. Currently I am registering an ObjectMapper using jax #Provider and ContextResolver. However the getContext() method is only invoked a single time for each resource class. It is not possible to alter this based on every request.
Is it possible to inject or access the ObjectMapper in a ContainerResponseFilter?
#Provider
public class ObjectMapperResolver implements ContextResolver<ObjectMapper> {
#Context
private HttpServletRequest httpRequest;
private final ObjectMapper mapper;
public ObjectMapperContextResolver() {
mapper = new ObjectMapper();
SimpleModule module = new SimpleModule();
module.addDeserializer(Date.class, new DateDeserializer("some data format"));
mapper.registerModule(module);
}
#Override
public ObjectMapper getContext(Class<?> type) {
return getMapperBasedOnRequest();
}
private ObjectMapper getMapperBasedOnRequest() {
if (true) {
return mapper;
} else {
return //something else;
}
}
private boolean containsLegacyHeader() {
return //get some information from the request headers or body
}
}
I decided to solve this issue by using a Filter which would be invoked based on a parameter provided in the request, in this case a header.
public class DemoFilter implements ContainerResponseFilter {
private final String UTC = "yyyy-MM-dd'T'HH:mm:ss.SSSZ";
private final String OTHER = "yyyy-MM-dd";
public static final String DATE_UTC = "utc";
private String getMapperBasedOnRequest(ContainerRequestContext requestContext) {
if (checkHeader(requestContext)) {
return OTHER;
} else {
return UTC;
}
}
private boolean checkHeader(ContainerRequestContext requestContext) {
return //check header
}
#Override
public void filter(ContainerRequestContext requestContext, ContainerResponseContext responseContext) throws IOException {
ObjectWriterInjector.set(new DateMod(getMapperBasedOnRequest(requestContext)));
}
public static class DateMod extends ObjectWriterModifier {
private String df;
public DateMod(String df) {
this.df = df;
}
#Override
public ObjectWriter modify(EndpointConfigBase<?> endpointConfigBase, MultivaluedMap<String, Object> multivaluedMap, Object o, ObjectWriter objectWriter, JsonGenerator jsonGenerator) throws IOException {
return objectWriter.with(new SimpleDateFormat(dateFormat));
}
}
}

Unable to intercept and manipulate HttpServletResponse in Spring Boot

I have a requirement to Base64 decode every JSON request payload that my Spring Boot service receives. The JSON payload would have been Base64 encoded at the client before posting using the HTTP POST method. Further, I also need to Base64 encode the JSON response before presenting to the calling client application.
I am required to reduce boilerplate code by using handler interceptors.
I have already achieved the request/incoming leg of the operation by the use of interceptors but is yet to achieve this for the response leg.
I have posted the code snippets below. The code to intercept the response and base64 encode it is in the postHandle method of the interceptor class.
What am I doing wrong here?
Interceptor Class:
public class Base64ResponseEncodingInterceptor implements HandlerInterceptor {
private static final String DECODED_REQUEST_ATTRIB = "decodedRequest";
private static final String POST = "POST";
#Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView arg3) throws Exception {
try {
if (POST.equalsIgnoreCase(request.getMethod())) {
CharResponseWrapper res = new CharResponseWrapper(response);
res.getWriter();
byte[] encoded = Base64.encodeBase64(res.toString().getBytes());
byte[] encoded = Base64.encodeBase64(response.getHeader(ENCODED_RESPONSE_ATTRIB).getBytes());
response.getWriter().write(new String(encoded));
}
} catch (Exception e) {
throw new Exception(e.getMessage());
}
}
// preHandle and afterCompletion methods
// Omitted
}
The CharResponseWrapper Class used above:
public class CharResponseWrapper extends HttpServletResponseWrapper {
protected CharArrayWriter charWriter;
protected PrintWriter writer;
protected boolean getOutputStreamCalled;
protected boolean getWriterCalled;
public CharResponseWrapper(HttpServletResponse response) {
super(response);
charWriter = new CharArrayWriter();
}
#Override
public ServletOutputStream getOutputStream() throws IOException {
if (getWriterCalled) {
throw new IllegalStateException("getWriter already called");
}
getOutputStreamCalled = true;
return super.getOutputStream();
}
#Override
public PrintWriter getWriter() throws IOException {
if (writer != null) {
return writer;
}
if (getOutputStreamCalled) {
throw new IllegalStateException("getOutputStream already called");
}
getWriterCalled = true;
writer = new PrintWriter(charWriter);
return writer;
}
#Override
public String toString() {
String s = null;
if (writer != null) {
s = charWriter.toString();
}
return s;
}
}
JavaConfig Class where Interceptor is registered:
#Configuration
#EnableJpaRepositories(repositoryBaseClass = BaseRepositoryBean.class, basePackages = "")
#EntityScan(basePackages = { "com.companyname", "com.companyname.productname"})
public class RestConfig extends WebMvcConfigurerAdapter {
#Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new Base64ResponseEncodingInterceptor());
}
}
The Controller Class, where the Interceptor is used (Only the working request leg is shown here):
#Autowired
HttpServletRequest request;
String decodedRequest = null;
#ModelAttribute("decodedRequest")
public void getDecodedParam(){
decodedRequest = (String) request.getAttribute("decodedRequest");
}
The code in the postHandle method does not work. It is either the HttpServletResponse is null or I get an exception message:
getOutputStream already called
Update: Work around solution to reading the response directly in the ResponseBodyAdvice
In the Controller Class, I added the following:
#RestController
#RequestMapping("/api/ipmanager")
public class IPProfileRestController extends AbstractRestController {
#Autowired
HttpServletResponse response;
String encodedResponse = null;
#ModelAttribute("encodedResponse")
public void getEncodedResponse(){
response.setHeader("encodedResponse", StringUtils.EMPTY);
}
#RequestMapping(value = "/time", method = { RequestMethod.POST }, produces = { MediaType.APPLICATION_JSON_VALUE, MediaType.TEXT_PLAIN_VALUE }, consumes = {
MediaType.APPLICATION_JSON_VALUE })
public #ResponseBody String saveAccessClientTime(#RequestBody String ecodedRequest) {
// Some code here
String controllerResponse = prettyJson(iPProfileResponse);
response.setHeader("encodedResponse", controllerResponse);
return controllerResponse;
}
}
I have the following in the ResponseBodyAdvice
#ControllerAdvice
public class Base64EncodedResponseBodyAdvice implements ResponseBodyAdvice<Object> {
#Override
public boolean supports(MethodParameter returnType,
Class<? extends HttpMessageConverter<?>> converterType) {
return true;
}
#Override
public Object beforeBodyWrite(Object body,
MethodParameter returnType,
MediaType selectedContentType,
Class<? extends HttpMessageConverter<?>> converterType,
ServerHttpRequest request,
ServerHttpResponse response) {
String body1 = StringUtils.EMPTY;
// Encode the response and return
List<String> listOfHeaderValues = response.getHeaders().get("encodedResponse");
body1=new String(Base64.encodeBase64(listOfHeaderValues.get(0).getBytes()));
return body1;
}
}
As the Spring MVC documentation states:
the postHandle method of HandlerInterceptor is not always ideally
suited for use with #ResponseBody and ResponseEntity methods. In such
cases an HttpMessageConverter writes to and commits the response
before postHandle is called which makes it impossible to change the
response, for example to add a header. Instead an application can
implement ResponseBodyAdvice and either declare it as an
#ControllerAdvice bean or configure it directly on
RequestMappingHandlerAdapter.
With that being said:
What am I doing wrong here?
Since the response has been already committed, you can't change it. In order to change the response you should register a ResponseBodyAdvice<T> and put your response encoding logic there:
#ControllerAdvice
public class Base64EncodedResponseBodyAdvice implements ResponseBodyAdvice<Object> {
#Override
public boolean supports(MethodParameter returnType,
Class<? extends HttpMessageConverter<?>> converterType) {
return true;
}
#Override
public Object beforeBodyWrite(Object body,
MethodParameter returnType,
MediaType selectedContentType,
Class<? extends HttpMessageConverter<?>> converterType,
ServerHttpRequest request,
ServerHttpResponse response) {
// Encode the response and return
}
}
If your method is returning ResponseEntity<T> then write this code in postHandle() method of your HandlerInterceptor:
eg. response.addHeader("jwt_token", "kajdlakjd");
it will work!!

Spring MVC - #ExceptionHandler based on Accept header

I have a HandlerInterceptorAdapter that intercepts all requests and performs user authorization checks. Very basically:
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
User user = ... // get user
checkIfAuthorized(user); // throws AuthorizationException
return true;
}
I then have an #ExceptionHandler for that AuthorizationException.
#ExceptionHandler(value = AuthorizationException.class)
public ResponseEntity<String> handleNotAuthorized(AuthorizationException e) {
// TODO Custom EXCEPTION HANDLER for json/jsp/xml/other types, based on content type
ResponseEntity<String> responseEntity = new ResponseEntity<>("You are not authorized to access that page.", HttpStatus.UNAUTHORIZED);
return responseEntity;
}
This is fine if the (unauthorized) request accepts text/plain (and can be easily changed for json).
How can I make different #ExceptionHandlers for specific Accept headers?
#RequestMapping has produces(). Is there something similar for #ExceptionHandler?
I know this comes late but I've been looking up a solution to this, came across this question and found what I think to be a better solution. You can return "forward:/error" in your #ExceptionHandler (returning a String) to forward the request to a
#RequestMapping("/error")
ErrorController {...}
and use
#RequestMapping(produces = "text/html")
ModelAndView errorPage() {...}
on one method of that ErrorController,
#RequestMapping(produces = "application/json") // or no 'produces' attribute for a default
MyJsonObject errorJson() {...} on another.
I think this is a pretty neat way to do it, it's probably already out there but I didn't find it when trying to look it up.
So basically the #ExceptionHandler is the same for all, but forwards to a controller that can do the usual stuff
I think of two approaches:
Manually
public ResponseEntity<String> handleNotAuthorized(AuthorizationException e, HttpServletRequest request) {
// TODO Custom EXCEPTION HANDLER for json/jsp/xml/other types, based on content type
if (/*read header accept from request and build appropiate response*/) {}
ResponseEntity<String> responseEntity = new ResponseEntity<>("You are not authorized to access that page.", HttpStatus.UNAUTHORIZED);
return responseEntity;
Automatically
#ResponseBody
public SomeObject handleNotAuthorized(AuthorizationException e, HttpServletRequest request) {
// TODO Custom EXCEPTION HANDLER for json/jsp/xml/other types, based on content type
/* Construct someObject and let Spring MessageConverters transform it to JSON or XML. I don't remember what happens in case of HTML (it should go to a view)*/
return someObject;
Don't forget to set the Response's Status code.
Not exactly the same use case, but the same requirement. I solve it with a custom HttpMessageConverter implementation.
#RestController
#RequestMapping("/foo")
public class MyResource {
#GetMapping(path = "/{id}", produces = "application/json")
public ResponseEntity<MyDto> get (#PathVariable(ID) long id)
throws IOException {
throw new MyCustomException();
}
#GetMapping(path = "/{id}/export", produces = "application/zip")
public ResponseEntity<byte[]> export (#PathVariable(ID) long id)
throws IOException {
throw new MyCustomException();
}
}
...
#ControllerAdvice
public class MyCustomExceptionHandler {
#ResponseBody
#ExceptionHandler
#ResponseStatus(BAD_REQUEST)
public JsonAPIErrorDocument handleException (MyCustomException e) {
return ....;
}
}
...
public class JsonAPIErrorDocumentToByteArrayMessageConverter extends AbstractHttpMessageConverter {
public ErrorDocumentToByteArrayMessageConverter () {
super(new MediaType("application", "zip"), MediaType.ALL);
}
#Override
protected boolean supports (Class clazz) {
return JsonAPIErrorDocument.class == clazz;
}
#Override
protected Object readInternal (Class clazz, HttpInputMessage inputMessage)
throws IOException,
HttpMessageNotReadableException {
return new byte[0];
}
#Override
protected void writeInternal (Object t, HttpOutputMessage outputMessage)
throws IOException,
HttpMessageNotWritableException {
}
}
...
#EnableWebMvc
#Configuration
#ComponentScan({ "com.foo" })
public class ApplicationConfig implements WebMvcConfigurer {
...
#Override
public void configureMessageConverters (List<HttpMessageConverter<?>> converters) {
converters.add(new MappingJackson2HttpMessageConverter(objectMapper));
converters.add(new ByteArrayHttpMessageConverter());
converters.add(new JsonAPIErrorDocumentToByteArrayMessageConverter());
}
...
}

Jersey: Use Provider in Resource Filter

Using Jersey 1.14 and Spring 3.1.2
I want to create a filter like this: https://gist.github.com/3031495
but in that filter I want access to a provider I created.
I'm getting an IllegalStateException. I suspect something in my lifecycle is hosed up. I can access #Context private HttpServletRequest and pull the session info I need from there, but then two classes have to know about where/how to get my "AuthUser" object.
Any help is appreciated!
My Provider:
#Component
#Provider
public class AuthUserProvider extends AbstractHttpContextInjectable<AuthUser> implements
InjectableProvider<Context, Type> {
private static final Logger LOG = LoggerFactory.getLogger(AuthUserProvider.class);
#Context
HttpServletRequest req;
public void init() {
LOG.debug("created");
}
#Override
// this may return a null AuthUser, which is what we want....remember, a
// null AuthUser means the user hasn't authenticated yet
public AuthUser getValue(HttpContext ctx) {
return (AuthUser) req.getSession().getAttribute(AuthUser.KEY);
}
// InjectableProvider implementation:
public ComponentScope getScope() {
return ComponentScope.Singleton;
}
public Injectable<AuthUser> getInjectable(ComponentContext ic, Context ctx, Type c) {
if (AuthUser.class.equals(c)) {
return this;
}
return null;
}
}
My Filter:
#Component
public class TodoFilter implements ResourceFilter {
private static final Logger LOG = LoggerFactory.getLogger(TodoFilter.class);
#Autowired
private JdbcTemplate todoTemplate;
// this works
#Context
private HttpServletRequest servletRequest;
// this throws a java.lang.IllegalStateException
// #Context
// private AuthUser authUser;
public void init() throws Exception {
LOG.debug("created");
LOG.debug(todoTemplate.getDataSource().getConnection().getMetaData()
.getDatabaseProductName());
}
#Override
public ContainerRequestFilter getRequestFilter() {
return new ContainerRequestFilter() {
#Override
public ContainerRequest filter(ContainerRequest request) {
LOG.debug("checking if {} is authorized to use {}", "my authenticated user",
request.getPath());
// String name = request.getUserPrincipal().getName();
// String[] admins = settings.getAdminUsers();
// for (String adminName : admins) {
// if (adminName.equals(name))
// return request;
// }
// if (authUser.getUsername().equals("jberk")) {
// return request;
// }
// return HTTP 403 if name is not found in admin users
throw new WebApplicationException(Response.status(Response.Status.FORBIDDEN)
.entity("You are not authorized!").build());
}
};
}
#Override
public ContainerResponseFilter getResponseFilter() {
return new ContainerResponseFilter() {
#Override
public ContainerResponse filter(ContainerRequest request,
ContainerResponse response) {
// do nothing
return response;
}
};
}
}
My Service (aka Resource):
#Component
#Path("/rs/todo")
#Produces(MediaType.APPLICATION_JSON)
#ResourceFilters(TodoFilter.class)
public class TodoService {
#GET / #POST methods
}
so I think I figured this out....
I added this to my ResourceFilter:
#Context
private HttpContext ctx;
#Autowired
private AuthUserProvider provider;
then I can do this in the filter method:
public ContainerRequest filter(ContainerRequest request) {
AuthUser authUser = provider.getValue(ctx);
// use authuser in some way
}
this might not be "correct"...but it's working and I don't have code duplication
public ComponentScope getScope() {
return ComponentScope.Singleton;
}
It should be
public ComponentScope getScope() {
return ComponentScope.PerRequest;
}

Categories

Resources