JAX-RS ExceptionMapper throws MessageBodyProviderNotFoundException - jax-rs

Using JAX-RS, I have successfully implemented an ExceptionMapper for Exceptions that do not require a more sophisticated response than an HTTP status code, as follows.
#Provider
public class ISBNNotFoundManager implements ExceptionMapper<ISBNNotFoundException>{
#Override
public Response toResponse(ISBNNotFoundException exception) {
return Response.status(NOT_FOUND).build();
}
}
This works as expected.
However, I want to respond with something more useful when bean validation fails. The follow code snippet results in a MessageBodyProviderNotFoundException.
#Provider
public class ConstraintViolationExceptionMapper implements
ExceptionMapper<ConstraintViolationException> {
#Override
#Produces(MediaType.APPLICATION_JSON)
public Response toResponse(ConstraintViolationException exception) {
final Map<String, String> errorResponse =
exception.getConstraintViolations()
.stream()
.collect(
Collectors.toMap(o -> o.getPropertyPath().toString(), o -> o.getMessage()));
return Response.status(Response.Status.BAD_REQUEST).entity(errorResponse).build();
}
}
When a bean validation occurs the response includes the HTTP response code 500 and the root cause is given as follow:
org.glassfish.jersey.message.internal.MessageBodyProviderNotFoundException:
MessageBodyWriter not found for media type=application/json,
type=class java.util.HashMap, genericType=class java.util.HashMap.
What I have tried that didn't work:
Wrapping the Map in a GenericEntity like so. The same result as above:
new GenericEntity>(errorResponse) {}
What I tried the DID work:
Wrapping the map in a custom POJO, DataIntegrityValidation, as follows:
#XmlRootElement
public class DataIntegrityValidation {
private Map<String, String> errorResponse = new HashMap<>();
public Map<String, String> getErrorResponse() {
return errorResponse;
}
public void setErrorResponse(Map<String, String> errorResponse) {
this.errorResponse = errorResponse;
}
}
Then in the toResponse method I wrap the map in the DataIntegrityValidation POJO like so and add it to the response object.
DataIntegrityValidation dataIntegrityValidation =
new DataIntegrityValidation();
dataIntegrityValidation.setErrorResponse(errorResponse);
return
Response.status(Response.Status.BAD_REQUEST)
.entity(dataIntegrityValidation).build();
This gives the following JSON:
{
"errorResponse": {
"entry": [
{
"key": "saveBook.arg0.description",
"value": "size must be between 100 and 2147483647"
},
{
"key": "saveBook.arg0.published",
"value": "must be in the past"
},
{
"key": "saveBook.arg0.link",
"value": "must match \"^(https?:\\/\\/)?([\\da-z\\.-]+)\\.([a-z\\.]{2,6})([\\/\\w \\.-]*)*\\/?$\""
}
]
}
}
I can live with this but would really like to know why it cannot handle the Map even though it is wrapped in the Generic Entity.
All responses welcome.

The reason the marshalling failed for both Map and GenericEntity is because there is no JAXB definition associated with them. And when you wrapped the map in a POJO annotated with #XmlRootElement; it was able to marshal it correctly.

Related

How can the status code for a response provided by a #ExceptionHandler be set when the #Controller returns a reactive type (Mono)?

How can the status code for a response provided by a #ExceptionHandler be set when the #Controller returns a reactive type (Mono)?
It seems that it is not possible via returning a ResponseEntity or annotating the #ExceptionHandler method with #ResponseStatus.
A fairly minimal test showing the issue (note that the response body and content type are correctly verified while the status code is OK when it should be INTERNAL_SERVER_ERROR):
class SpringWebMvcWithReactiveResponseTypeExceptionHandlerCheckTest {
#RestController
#RequestMapping("/error-check", produces = ["text/plain;charset=UTF-8"])
class ExceptionHandlerCheckController {
#GetMapping("errorMono")
fun getErrorMono(): Mono<String> {
return Mono.error(Exception())
}
}
#ControllerAdvice
class ErrorHandler : ResponseEntityExceptionHandler() {
#ExceptionHandler(Exception::class)
#ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
fun handleException(ex: Exception): ResponseEntity<*> = ResponseEntity
.status(HttpStatus.INTERNAL_SERVER_ERROR)
.contentType(MediaType.APPLICATION_PROBLEM_JSON)
.body(mapOf("key" to "value"))
}
val mockMvc = MockMvcBuilders
.standaloneSetup(ExceptionHandlerCheckController())
.setControllerAdvice(ErrorHandler())
.build()
#Test
fun `getErrorMono returns HTTP Status OK instead of the one set by an ExceptionHandler`() {
mockMvc.get("/error-check/errorMono")
// .andExpect { status { isInternalServerError() } }
.andExpect { status { isOk() } }
.asyncDispatch()
.andExpect {
content {
contentType(MediaType.APPLICATION_PROBLEM_JSON_VALUE)
json("""
{
"key": "value"
}
""",strict = true
)
}
}
}
}
(build.gradle.kts excerpt showing relevant dependencies):
plugins: {
id("org.springframework.boot") version "2.4.5"
id("io.spring.dependency-management") version "1.0.11.RELEASE"
}
dependencies {
implementation("org.springframework.boot:spring-boot-starter-web")
implementation("org.springframework.boot:spring-boot-starter-webflux")
testImplementation("org.springframework.boot:spring-boot-starter-test")
}
The issue is that .andExpect { status ... has to be called after .asyncDispatch() (as was done with content type and body). It seems that the http status is updated. Possibly this is actually part of http standard for async requests, but I suspect this is a bug of the underlying MockHttpServletResponse.

Jackson parse JSON into Java Map

I have such JSON:
{
"list": [
{
"product": {
"id": 1123456,
"context": {
}
},
"items": [
]
},
and a code that reads it:
TypeReference<HashMap<String, Object>> typeRef
= new TypeReference<HashMap<String, Object>>() {};
InputStream inputStream = TypeReference.class.getResourceAsStream("/mocks/Docs.json");
Map<String, Object> map = mapper.readValue(inputStream, typeRef);
But I don't want the simple Map<String, Object>, I want to map into a map that looks like Map<String, MyRepresentation> map:
Is there a direct way to do it, or I need first to read it into Map<String, Object> and then manipulate it manually and fill the MyRepresentation object?
the JSON file structure doesn't correspond to the classes at all.
ConditionSummary looks like a type of contextData element accessible as docList[i].product.contextData if it had id, which is defined in the product element.
also, AccountManagerStatistics#map is not public and doesn't have #JsonProperty annotation, so it is out of json for now.
try creating the sample file first if you sure the classes represent the truth:
Map<String, AccountManagerStatistics> map = createStubData();
new ObjectMapper().writerFor(new TypeReference<Map<String, AccountManagerStatistics>>() {}).writeValueAsString(map)
or try to modify your classes to match the data,
which is probably what should be done here.
then you could start from the top and introduce a proper class instead of using Map<String, X>,
BTW no need for HashMap in TypeReference:
//#XmlRootElement
public class Root {
#JsonProperty("docList") //or #XmlElement("docList")
public final List<Doc> docs;
...
#ConstructorProperties({ "docs", ... })
public Root(List<Doc> docs, ...) {
this.docs = List.copyOf(docs);
...
}
}

Spring Data Rest Wrongly Detects findAll() As A Search Method

I extend CrudRepository so I can add #Query and method security annotations such as #PreAuthorize etc to the methods findAll, findById, save and delete. When I do this, the findAll method appears as /<REPO>/search/findAll:
public interface PricingPlanRepo extends CrudRepository<PricingPlan, UUID> {
#PreAuthorize("isFullyAuthenticated() and hasAnyScopeFor('pricingplan', 'read')")
#Query("SELECT e FROM #{#entityName} e WHERE e.tenant IN ?#{security.getTenants('pricingplan', 'r')} OR '*' IN ?#{security.getTenants('pricingplan', 'r')}")
#Override
Iterable<PricingPlan> findAll();
$ curl http://localhost:8084/pricingPlans/search
{
"_links" : {
"findAll" : {
"href" : "http://localhost:8084/pricingPlans/search/findAll"
},
"self" : {
"href" : "http://localhost:8084/pricingPlans/search"
}
}
}
Other repositories in which I don't mention findAll don't have that search endpoint. Is this a bug? How can I get around that?
According to Spring Data REST doc, to hide query methods, annotate your method with #RestResource(exported = false).
public interface PricingPlanRepo extends CrudRepository<PricingPlan, UUID> {
#RestResource(exported = false)
#Override
Iterable<PricingPlan> findAll();
}

Spring webflux error handler: How to get the reactor context of the request in the error handler?

Spring boot 2.1.5 Project Reactor 3.2.9
In my webflux project, I extensively use the reactor contexts in order to pass around some values.
My purpose here is to be able to get the context inside of the Exception handler.
A simple example:
#Component
#Order(-2)
public class GlobalErrorWebExceptionHandler extends
AbstractErrorWebExceptionHandler {
public GlobalErrorWebExceptionHandler(ErrorAttributes errorAttributes, ResourceProperties resourceProperties, ApplicationContext applicationContext, ServerCodecConfigurer configurer) {
super(errorAttributes, resourceProperties, applicationContext);
this.setMessageWriters(configurer.getWriters());
}
#Override
protected RouterFunction<ServerResponse> getRoutingFunction(
ErrorAttributes errorAttributes) {
return RouterFunctions
.route(RequestPredicates.all(), request -> {
Throwable error = errorAttributes.getError(request);
return ServerResponse.status(500).syncBody(error.getMessage()).doOnEach(serverResponseSignal -> {
//Here the context is empty because i guess i created a new flux
System.out.println("What is in my context ? " + serverResponseSignal.getContext());
System.out.println("What is my exception ? " + error);
});
});
}
}
I am not sure how to achieve that goal in a clean way with reactor. Anyone an idea ?
I found a trick to be able to achieve that. It does not sound clean but it seems to work.
In a filter, I keep the subscribed context into a request attribute:
#Component
public class MdcWebFilter implements WebFilter {
#NotNull
#Override
public Mono<Void> filter(#NotNull ServerWebExchange serverWebExchange,
WebFilterChain webFilterChain) {
Mono<Void> filter = webFilterChain.filter(serverWebExchange);
return filter
.subscriberContext((context) -> {
//This code is executed before the query
Context contextTmp = context.put("whatever", "whichever");
//I save the context in an attribute attribute
serverWebExchange.getAttributes().put("context", contextTmp);
return contextTmp;
});
}
}
Then after that it is possible to get it from the reactive error handler:
#Component
#Order(-2)
public class GlobalErrorWebExceptionHandler extends
AbstractErrorWebExceptionHandler {
public GlobalErrorWebExceptionHandler(ErrorAttributes errorAttributes, ResourceProperties resourceProperties, ApplicationContext applicationContext, ServerCodecConfigurer configurer) {
super(errorAttributes, resourceProperties, applicationContext);
this.setMessageWriters(configurer.getWriters());
}
#Override
protected RouterFunction<ServerResponse> getRoutingFunction(
ErrorAttributes errorAttributes) {
return RouterFunctions
.route(RequestPredicates.all(), request -> {
Throwable error = errorAttributes.getError(request);
//The context will be visible in the whole error handling flow
return ServerResponse.status(500).syncBody(error.getMessage())
.subscriberContext((Context) request.attribute("context").orElse(Context.empty())));
});
}
}

How to unwrap single item array and extract value field into one simple field?

I have a JSON document similar to the following:
{
"aaa": [
{
"value": "wewfewfew"
}
],
"bbb": [
{
"value": "wefwefw"
}
]
}
I need to deserialize this into something more clean such as:
public class MyEntity{
private String aaa;
private String bbb;
}
What's the best way to unwrap each array and extract the "value" field on deserialization? Just custom setters? Or is there a nicer way?
For completeness, if you use jackson, you can enable the deserialization feature UNWRAP_SINGLE_VALUE_ARRAYS.
To do that, you have to enable it for the ObjectMapper like so:
ObjectMapper objMapper = new ObjectMapper()
.enable(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS);
With that, you can just read the class as you are being used to in Jackson.
For example, assuming the class Person:
public class Person {
private String name;
// assume getter, setter et al.
}
and a json personJson:
{
"name" : [
"John Doe"
]
}
We can deserialize it via:
ObjectMapper objMapper = new ObjectMapper()
.enable(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS);
Person p = objMapper.readValue(personJson, Person.class);
Quick solution with Gson is to use a JsonDeserializer like this:
package stackoverflow.questions.q17853533;
import java.lang.reflect.Type;
import com.google.gson.*;
public class MyEntityDeserializer implements JsonDeserializer<MyEntity> {
public MyEntity deserialize(JsonElement json, Type typeOfT,
JsonDeserializationContext context) throws JsonParseException {
String aaa = json.getAsJsonObject().getAsJsonArray("aaa").get(0)
.getAsJsonObject().get("value").getAsString();
String bbb = json.getAsJsonObject().getAsJsonArray("bbb").get(0)
.getAsJsonObject().get("value").getAsString();
return new MyEntity(aaa, bbb);
}
}
and then use it when parsing:
package stackoverflow.questions.q17853533;
import com.google.gson.*;
public class Q17853533 {
public static void main(String[] arg) {
GsonBuilder builder = new GsonBuilder();
builder.registerTypeAdapter(MyEntity.class, new MyEntityDeserializer());
String testString = "{ \"aaa\": [{\"value\": \"wewfewfew\" } ], \"bbb\": [ {\"value\": \"wefwefw\" } ] }";
Gson gson = builder.create();
MyEntity entity= gson.fromJson(testString, MyEntity.class);
System.out.println(entity);
}
}