Add custom header to REST PUT request - spring-boot

I have an application that uses Spring Boot 2.1.8 with an Angular front end. I have defined my repositories using #RepositoryRestResource. The Spring Boot app basically provides a REST API for the Angular piece. The application defines several business entities and the REST respo provides CRUD functionality for each entity. Here is a typical repo interface for one of those entities:
// REST resouce /api/privgroups
#RepositoryRestResource(collectionResourceRel = "privgroups", path = "privgroups")
public interface PrivGroupRepository extends CrudRepository<PrivGroup, Long>
{
List<PrivGroup> findAll();
}
GETing and single entity, POSTing (creating an entity) and PUTing (updating an entity) are all working fine, but I would like to return a custom header when updating (HTTP PUT) an entity. The header would be consumed by the Angular side to display a custom toastr message specific to that entity. Since the repositories also implement the REST interface I am unsure how to add a specific header that would change based on the target entity.
I have developed applications that include a REST controller that calls against a service which in turn calls against a repository. In this case, I have more control and can easily return custom headers like so:
#PutMapping("/{id}")
public ResponseEntity<MyEntity> updateMyEntity(#PathVariable("id") Long id, #RequestBody MyEntity myEntity)
{
MyEntity updatedEntity = this.MyEntityService.updateMyEntity(MyEntity);
return ResponseEntity.ok()
.header("X-Save", "MyEntity")
.body(updatedEntity);
}
Is there a newer "built-in" technique I can use to achieve this? I know I can add headers using a filter and I've read a couple posts on the subject. I think it would be difficult to identify the entity being updated and I'm unsure this is the best approach.
NOTE that this post:
Rest API - how add custom headers?
is really old.
The spring data rest docs https://docs.spring.io/spring-data/rest/docs/current/reference/html/
don't have anything specific on the subject.

I have used Spring Data Rest recently and I didn't find any "built-in" technique for that. However you can achieve that by implementing the ResponseBodyAdvice interface in a class annotated with #ControllerAdvice. This is how I got it:
#ControllerAdvice
public class PutMyEntityBodyAdvice implements ResponseBodyAdvice<Object> {
#Override
public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
// Write the condition to check if beforyBodyWrite should be called
return returnType.getParameterType().equals(ResponseEntity.class);
}
#Override
public Object beforeBodyWrite(Object object, MethodParameter returnType, MediaType selectedContentType,
Class<? extends HttpMessageConverter<?>> selectedConverterType,
ServerHttpRequest request, ServerHttpResponse response) {
// Check if it is a PUT request for MyEntity object
if (HttpMethod.PUT.equals(request.getMethod()) && object instanceof PersistentEntityResource) {
PersistentEntityResource persistentEntityResource = (PersistentEntityResource) object;
if (persistentEntityResource.getContent().getClass().equals(MyEntity.class)) {
// Add custom header or manipulate response object
response.getHeaders().add("X-Save", "MyEntity");
}
}
return object;
}
}

The answer by #pepevalbe looks promising.
An alternative might be - as you suggested - using a standard Servlet Filter or Spring HandlerInterceptor.
To address the issue of getting a reference to the modified entity in that case you could register a Spring Data Rest event listener that would simply store a reference to the modified entity in ThreadLocal storage from where it could be retrieved in the Filter or HandlerInterceptor.
https://www.baeldung.com/java-threadlocal
https://docs.spring.io/spring-data/rest/docs/current/reference/html/#events.application-listener

Related

Spring Graphql - How to use a custom DataFetcherExceptionHandler and override the default one?

I'm new to spring graphql and I was trying to implement my own DataFetcherExceptionHandler so I can wrap all exceptions with my custom one.
I've implemented my custom class that implements DataFetcherExceptionHandler but it seems like it still uses the default one, the SimpleDataFetcherExceptionHandler.
How can I make my custom DataFetcherExceptionHandler the default one for the graphql exceptions?
My class:
#Slf4j
#AllArgsConstructor
#Component
public class GraphqlExceptionHandler implements DataFetcherExceptionHandler {
public DataFetcherExceptionHandlerResult onException(DataFetcherExceptionHandlerParameters handlerParameters) {
Throwable exception = handlerParameters.getException();
SourceLocation sourceLocation = handlerParameters.getSourceLocation();
ResultPath path = handlerParameters.getPath();
MyCustomException error = exposedException(exception, sourceLocation, path);
return DataFetcherExceptionHandlerResult.newResult().error(error).build();
}
#Override
public CompletableFuture<DataFetcherExceptionHandlerResult> handleException(DataFetcherExceptionHandlerParameters handlerParameters) {
return CompletableFuture.completedFuture(this.onException(handlerParameters));
}
Note: I'm not sure if I can use my custom exception like that, but I'm not able to test it while I can't make the exception handler the default one.
With Spring for GraphQL you can implement a DataFetcherExceptionResolver or more specifically a DataFetcherExceptionResolverAdapter that you can for example annotate with #Component to register it automatically.
The DataFetcherExceptionHandler from graphql-java is used by Spring for GraphQL internally to delegate to your DataFetcherExceptionResolver classes.
Inside your own DataFetcherExceptionResolverAdapter, you can get the informations that are available as DataFetcherExceptionHandlerParameters (Path, SourceLocation and so on) in a DataFetcherExceptionHandler from the DataFetchingEnvironment that is passed to DataFetcherExceptionResolverAdapter resolveToSingleError and resolveToMultipleErrors methods.
See here for more informations: https://docs.spring.io/spring-graphql/docs/current/reference/html/#execution-exceptions
You can find an example implementation here: https://github.com/nilshartmann/spring-graphql-training/blob/main/app/publy-backend/src/main/java/nh/publy/backend/graphql/runtime/PublyGraphQLExceptionResolver.java

How to validate request parameters on feign client

Is there a way to add validation to feign clients on the request parameters.
For example:
#FeignClient
public interface ZipCodeClient {
#GetMapping("/zipcodes/{zipCode}")
Optional<ZipCodeView> findByZipCode(#PathVariable("zipCode") String zipCode);
}
It would be nice to verify that zipcode is not empty and is of certain length etc, before sending the HTTP call to the server.
If your validations are simple, apply to only headers and query string parameters, you can use a RequestInterceptor for this, as it provides you the opportunity to review the RequestTemplate before it is sent to the Client.
public class ValidatingRequestInterceptor implements RequestInterceptor {
public void apply(RequestTemplate requestTemplate) {
// use the methods on the request template to check the query and values.
// throw an exception if the request is not valid.
}
}
If you need to validate the request body, you can use a custom Encoder
public class ValidatingEncoder implements Encoder {
public void encode(Object object, Type type, RequestTemplate template) {
// validate the object
// throw an exception if the request is not valid.
}
}
Lastly, if you want to validate individual parameters, you can provide a custom Expander for the parameter and validate it there. You can look at this answer for a complete explanation on how to create a custom expander that can work with Spring Cloud.
How to custom #FeignClient Expander to convert param?
For completeness, I've included an example for how to do this with vanilla Feign.
public class ZipCodeExpander implements Expander {
public String expand(Object value) {
// validate the object
// throw an exception if the request is not valid.
}
}
public interface ZipCodeClient {
#RequestLine("GET /zipcodes/{zipCode}")
Optional<ZipCodeView> findByZipCode(#Param(expander = ZipCodeExpander.class) ("zipCode") String zipCode);
}
As pointed out in this comment, a solution using the Bean Validation API would be nice. And indeed, I found in a Spring Boot project that merely placing #org.springframework.validation.annotation.Validated on the interface is sufficient for enabling Bean Validation.
So for example:
#FeignClient
#Validated
public interface ZipCodeClient {
#GetMapping("/zipcodes/{zipCode}")
Optional<ZipCodeView> findByZipCode(#PathVariable("zipCode") #NotEmpty String zipCode);
}
triggering a ConstraintViolationException in the case of violations.
Any standard Bean Validation feature should work here.
UDPATE Note that there seems to be a potential issue with this solution that might require setting a Hibernate Validator configuration property like this: hibernate.validator.allow_parallel_method_parameter_constraint=true

Spring boot - running check on every request and storing objects for lifecycle

I have a scenario where I need to run a few db checks at the start of every web request, and in the case of success I need to store objects for use later in the request by the controller, or in the case of failure I need to render an error page.
A very similar real world example would be a SaaS app checking and loading the account based on a vanity url, then storing the account for use by controllers to avoid multiple db requests.
What are the best ways to achieve this in a Spring boot app? I have experimented with Filters but I think an Interceptor might be better at the task, that covers running the check but what about storing the objects for later use? Is there a request lifecycle context of some kind that I can store against?
Spring supports request scope for beans. You can use them for storing data used during request execution.
In my experience, best way I've done similar stuff is through HandlerMethodArgumentResolver.
Basically imagine you have a custom type, let's call it UserContext where you store the information that's needed for the request. And you have a UserContextService let's say that has a method getUserContext(HttServletRequest), that is used to retrieve the context based on the request, from which you can call your database based on whatever request parameter/header/path-variable, etc. You can refine that as you need. But based on this simple assumptions, you can have a controller that looks like this:
#RequestMapping("/some/url")
public SomeResponse someMethod(UserContext userContext, ...) {
//do something here with UserContext
}
The way that Spring will inject this UserContext into your controller would be with a custom HandlerMethodArgumentResolver like this:
#Component
public class UserHandlerMethodArgumentResolver implements HandlerMethodArgumentResolver {
#Autowired
UserContextService
#Override
public boolean supportsParameter(MethodParameter parameter) {
return parameter.getParameterType().equals(UserContext.class);
}
#Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
HttpServletRequest req = (HttpServletRequest)webRequest.getNativeRequest();
UserContext userContext = userContextService.getUserContext(req);
if (userContext != null) {
return userContext;
} else {
return WebArgumentResolver.UNRESOLVED;
//Or throw exception
}
}
}
That you'll register by overriding the WebMvcConfigurer.addArgumentResolvers method in your WebMvcConfigurer bean/config-class.
This mechanism is the same used by #PathVariable, #RequestParam, etc...

Jaxb setting dynamic #XmlRootElement with Spring Web Services

I have a Spring application that consumes a SOAP web services. I have several classes that are quite simple and only differ in the #XmlRootElement. I'm wondering if there's a way to create a more generic class that I can set the root element on dymanically.
Here's a few of the classes with only the root element being different.
#XmlRootElement(name="safetydate")
public class SafetyDateRequest extends Carrier411RequestImpl {
}
#XmlRootElement(name="checkallsafety")
public class SafetyGetAllRequest extends Carrier411RequestImpl {
}
#XmlRootElement(name="checksafetyupdates")
public class SafetyGetUpdatesRequest extends Carrier411RequestImpl {
}
In another class, I'm processing these classes in the following fashion:
private void sendRequest(Carrier411Request request, Carrier411ResponseHandler responseHandler) throws FaultCodeException {
Carrier411Response response = (Carrier411Response) ws.marshalSendAndReceive(registry.get(request.getClass()), request);
checkResponseForFault(response);
responseHandler.handleResponse(request, response);
}
I know there's another version of marshalSendAndReceive that accepts a callback allowing you to modify the request before actually sending it, but I haven't figured out how to achieve what I'm trying to do.

How does delete operation work with Rest in Spring Data

Currently we have exposed our methods like this
#RestController
#RequestMapping("/app/person")
public class PersonResource {
#Timed
public void delete(#PathVariable Long id) {
log.debug("REST request to delete Person: {}", id);
personRepository.delete(id);
}
}
The operations of this method, in terms of input and output, are very clear to the user developer.
This article http://spring.io/guides/gs/accessing-data-rest/ shows how to expose JPARepositories directly obviating the need of a service layer.
#RepositoryRestResource(collectionResourceRel="people", path="people")
public interface PersonRepository extends JpaRepository<PersonEntity, Long> {
}
It is not obvious to me how I can make a "delete operation" available with PathVariable Long id.
There is an excellent article on this topic. https://github.com/spring-projects/spring-data-rest/wiki/Configuring-the-REST-URL-path
But it actually shows how to supress export of a delete operation.
As documented here, Spring Data REST will expose item resources for the repository you declare. Thus, all you need to do is discover the URI of the resource to delete and issue a DELETE request to it.

Resources