I made an ExceptionMapper to catch and log all exceptions, like:
#Provider
public class CatchAllExceptionsMapper implements ExceptionMapper<Throwable> {
private static final Logger LOG = LoggerFactory.getLogger(CatchAllExceptionsMapper.class);
#Override
public Response toResponse(Throwable exception) {
LOG.error("Exception not catched!", exception);
return Response.serverError().build();
}
}
It catches the Exceptions my code throws, but if I send a Request with a JSON value that throws an IllegalStateException at my object's creation, this ExceptionMapper is ignored and I get a 400 Bad Request Response.
Funny thing is this Response is not the traditional Tomcat HTML formatted Response, its just plain text. It say just:
Cannot construct instance of `com.example.vo.AutoValue_Customer$Builder`, problem: First name is null or empty. at [Source: (org.glassfish.jersey.message.internal.ReaderInterceptorExecutor$UnCloseableInputStream); line: 14, column: 1]
I thought this might be something short-circuiting Jersey, but my #PreMatching ContainerRequestFilter is executed beforehand, so I really have no idea why the 400 Response is not the traditional HTML one from Tomcat.
Why is this happening? What can I do to catch this and return my own Response?
As stated by Paul Samsotha in the comments, JacksonFeature from the jersey-media-json-jackson package define some ExceptionMappers, like JsonMappingException and JsonParseException. The solution is to create our own, register them within the ResourceConfig and register JacksonFeature last, otherwise it won't work.
e.g.
#Provider
#Priority(1) // hack for overriding other implementations.
public class JsonMappingExceptionMapper implements ExceptionMapper<JsonMappingException> {
#Override
public Response toResponse(JsonMappingException exception) {
return Response.status(Status.BAD_REQUEST).build();
}
}
#Provider
#Priority(1) // hack for overriding other implementations.
public class JsonParseExceptionMapper implements ExceptionMapper<JsonParseException> {
#Override
public Response toResponse(JsonParseException exception) {
return Response.status(Status.BAD_REQUEST).build();
}
}
public class MyResourceConfig extends ResourceConfig {
public MyResourceConfig() {
register(CatchAllExceptionsMapper.class);
register(JsonMappingExceptionMapper.class);
register(JsonParseExceptionMapper.class);
register(JacksonFeature.class);
}
}
Related
In a RESTEasy project running on Wildfly server, there is a resource class:
#Path("/company")
public class CompanyResource {
#Inject
CompanyService companyService;
#PUT
#Consumes(MediaType.APPLICATION_JSON)
public void update(Company company) {
companyService.update(company);
}
}
Initially the REST API configuration class just extends Application without any extra #override on the existing methods of Application class. An http request, http://localhost:8080/workcontext/company, with PUT as the http request method could work, meaning the CompanyResource.update() can be invoked successfully when receiving the aforementioned http request.
However, I then tried to add a custom MessageBodyReader<Company>:
public class CompanyReader implements MessageBodyReader<Company> {
#Override
public boolean isReadable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
return true;
}
#Override
public Company readFrom(Class<Company> type, Type genericType, Annotation[] annotations, MediaType mediaType,
MultivaluedMap<String, String> httpHeaders, InputStream entityStream)
throws IOException, WebApplicationException {
try(JsonReader reader = Json.createReader(entityStream)) {
JsonObject companyJson = reader.readObject();
Company company = new Company();
company.setCompanyCode(companyJson.getString("companyCode"));
company.setName(companyJson.getString("name"));
company.setHeadquarter(Region.valueOf(companyJson.getString("headquarter")));
return company;
}
}
}
In order to make this custom MessageBodyReader<Company> work, I registered this class by overriding the Application.getClasses():
public class JaxRsConfiguration extends Application {
#Override
public Set<Class<?>> getClasses() {
Set<Class<?>> classes = new HashSet<>();
classes.add(CompanyReader.class);
return classes;
}
}
I expected that this MessageBodyReader<Company> could be invoked when sending the same http PUT request, but on the opposite the response is: RESTEASY003210: Could not find resource for full path: http://localhost:8080/workcontext/company
Question: How to make this custom MessageBodyReader work?
You should annotate you're CompanyReader with #Provider. In your application if you return any classes in Application.getClasses() or Application.getSingletons() then, per the spec, those are the only classes allowed to be used in your application.
If either getClasses or getSingletons returns a non-empty collection then only those classes or singletons returned MUST be included in the published JAX-RS application.
I want to set custom error Message for Unrecognized field and others errors with request body.
I tried ExceptionMapper but it dont work :/
Using quarkus and Jackson
public class ReqDTO {
private String a;
}
when send request:
{
"a": "a"
}
its look good. But when send:
{
"a": "a",
"b": "b"
}
have error response:
Unrecognized field "b"; (class ReqDTO), not marked as ignorable
Want customize this message and other bad json body (like too much fields) to own message like "BAD_JSON_SCHEMA".
Tried
#Provider
public class ExHandler implements ExceptionMapper<JsonMappingException>{
public Respone toResponse(JsonMappingException e) {
//response bad field impl
}
}
But its not work. Looks like a json handle exception faster. Tried with "Exception", "JsonParseException" but nothing changed :/
#Path("/")
public class Controller {
#Post
#Consume(APPLICATION_JSON)
#Produces(TEXT_PLAIN)
public Response getA(#Valid ReqDTO reqDTO){
//logic
}
}
#Edit
Found something like DeserializationProblemHandler but dont know how change message for handleUnknownProperty :/
#Singleton
RegisterCustomModuleCustomizer implements ObjectMapperCustomizer {
public void customize(ObjectMapper mapper){
mapper.addHandler(new DeserializationProblemHandler(){
#SneakyThrows
#Override
public boolean handleUnknownProperty(...... params){
throw new ApplicationException("ERRO_BODY_MESS");
}
}
}
}
#Provider
public class ExHandler implements ExceptionMapper<ApplicationException>{
public Respone toResponse(ApplicationException ex) {
return Response.status(BAD_REQUEST).entity(ex.getMessage()).build();
}
}
we are currently trying to upgrade from Camel 2.19.0 to 2.20.0.
We have one timed routed which schedule autostart = false
#ConfigurationProperties
#Component
public class AdaptionService extends SpringRouteBuilder {
#Value("${control.timer.cron}")
private String timerCron;
#Override
public void configure() throws Exception {
from("quartz2://adaptionServices/UserUpdateTimer?cron=" + timerCron)
.routeId("scheduler").autoStartup(false)
.to("direct:route-userUpdate");
from("direct:route-userUpdate")
.routeId("adaption_service")
.log("Executing Adaption Service (timed)");
}
No in the CamelContextConfiguration I try to star the timer route (I know I could simply achieve this by autoStartup(true). It is just an example to abstract from more complex use case we have.
#Configuration
public class CamelApplicationContextConfiguration implements CamelContextConfiguration {
#Override
public void beforeApplicationStart(CamelContext camelContext) {
camelContext.setUseMDCLogging(true);
}
#Override
public void afterApplicationStart(CamelContext camelContext) {
try {
camelContext.startRoute("scheduler");
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
That does not work. I checked startRoute(..) and appearently the routeService inside the camelContext is completely empty. With 2.19.0 this works without a flaw.
Any suggestions?
Regards
Holger
The Camel 2.20 release has changed the startup logic when using Spring / Spring-Boot with Camel. You can see that in the release notes.
However we should make CamelContextConfiguration call the afterApplicationStart at a later stage. And also document this API a bit better when its called:
I have logged a ticket about this: https://issues.apache.org/jira/browse/CAMEL-11945
I'm having two routes in two separated projects :
First route is setting the header with a data format bean name as a constant :
setHeader("dataFormatBeanName", constant("myFirstList"))
First route :
public class MyTest {
#Configuration
public static class MyTestConfig extends CamelConfiguration {
#Bean(name = "myFirstList")
public DataFormat getMyFirstListDataFormat() {
return new MyFirstListDataFormat();
}
#Bean(name = "mySecondList")
public DataFormat getMySecondListDataFormat() {
return new MySecondListDataFormat();
}
#Bean
public RouteBuilder route() {
return new RouteBuilder() {
#Override
public void configure() throws Exception {
from("direct:testFirstDataFormat").setHeader("dataFormatBeanName", constant("myFirstList")).to("direct:myRoute");
from("direct:testSecondDataFormat").setHeader("dataFormatBeanName", constant("mySecondList")).to("direct:myRoute");
}
};
}
}
}
Second route is supposed to retrieve the bean name from the header and use it as a custom marshaller. Something like :
custom(header("dataFormatBeanName"))
(doesn't compile)
Anyone knows how I'm supposed to get my bean name from the header to use it in the custom method ?
#Component
public class MyRouteBuilder extends RouteBuilder {
#Override
public void configure() throws Exception {
final RouteDefinition routedefinition = this.from("direct:myRoute");
routedefinition.marshal().custom(??????????).to("netty4:tcp://{{route.address}}:{{port}}?textline=true&sync=true");
}
After a few more hours searching, here is the solution a found :
No changes in the first class.
Second class uses an anonymous DataFormat in which I retrieve the bean name from the header and get the spring bean from camel context before calling its marshal method.
The AbstractXxxDataFormat class belongs to project2 and is inherited by the Project1 DataFormat.
#Override
public void configure() throws Exception {
final RouteDefinition routedefinition = this.from("direct:myRoute");
routedefinition.marshal(new DataFormat() {
#Override
public void marshal(final Exchange exchange, final Object graph, final OutputStream stream) throws Exception {
AbstractXxxDataFormat myDataFormat = (AbstractGoalDataFormat) getContext().getRegistry().lookupByName(exchange.getIn().getHeader("dataFormatBeanName", String.class));
myDataFormat.marshal(exchange, graph, stream);
}
#Override
public Object unmarshal(final Exchange exchange, final InputStream stream) throws Exception {
return null;
}
});
routedefinition.to("netty4:tcp://{{route.address}}:{{port}}?textline=true&sync=true");
}
If there's any better solution available, I'll be interested.
Have you tried simple("${header.dataFormatBeanName}") to access the header?
Also, rather than passing the format bean name in a header in the first place, why not factor out each .marshal() call into two subroutes (one for formatBeanA and one for formatBeanB) and then call the appropriate subroute rather than setting the header in the first place? I believe this could be a cleaner approach.
If you really need to get it in the route as a variable (as opposed to a predicate to be used in the builder api) you could use an inline processor to extract it:
public class MyRouteBuilder extends RouteBuilder {
public void configure() throws Exception {
from("someEndpoint")
.process(new Processor() {
public void process(Exchange exchange) throws Exception {
String beanName = exchange.getHeader("beanNameHeader");
}
});
}
}
Just be careful of scope and concurrency when storing the extracted beanName however.
A collegue of mine (thanks to him) found the definite solution :
set bean name in the exchange properties :
exchange.setProperty("myDataFormat", "myDataFormatAutowiredBean");
retrieve the dataFormat bean with RecipientList pattern and (un)marshal :
routedefinition.recipientList(simple("dataformat:${property.myDataFormat}:marshal"));
routedefinition.recipientList(simple("dataformat:${property.myDataFormat}:unmarshal"));
Very concise and works just fine.
I have a REST service of the form:
#GET
#NeedsInterception
public void getSomething(#QueryParam("xxx") #MyAnnotation String thing) {
//Stuff
}
I then have an interceptor for #NeedsInterception.
In it, I perform some logic on the element annotated with #MyAnnotation.
However, when the interceptor is called, the MethodInvocation object has not yet been resolved with the value of the QueryParam, instead it is always "";
Is there a way for me to make the interception happen after the QueryParam is resolved?
Don't know which kind of interceptor you are using but a jax-rs ReaderInterceptor is intended to wrap calls to MessageBodyReader.readFrom. As you don't send a request body with a #GET request this kind of interceptor won't be used.
A ContainerRequestFilter should help:
#Provider
public class SomeFilter implements ContainerRequestFilter {
#Override
public void filter(ContainerRequestContext requestContext) throws IOException {
MultivaluedMap<String,String> queryParameters = requestContext.getUriInfo().getQueryParameters();
}
}