Jersey JAX-RS: extend logging over resource-class dispatching - jersey

I am using Jersey JAX-RS for a REST-service.
I a running it in SE-Deployment with GrizzlyHttp inside my programm.
I have also registered a logger that prints out the HTTP-request and the response.
What I am missing is logging "between" the HTTP-server and the resource-class.
I'd like to see log-entries if no path matches or if a path matches but the parameter did not or similar cases. Today I need to check my annotations and compare it to the logged HTTP-parameters. A bit more logging arround that would be helpful.
Is that possible?

You can achieve it by using a pre-matching filter.
A pre-matching filter is a filter that gets invoked before the JAR-RS runtime tries to match the request with a resource. This type of filter lets you log the requested URI even though it may not match with any of your resources. You annotate a filter with the #PreMatching annotation, and that is all you have to do to have the filter get invoked before the resource matching phase.
Note:: If you want your filter to be discoverable by JAX-RS runtime during the provider scanning phase, then you need to annotate it with the #Provider annotation. Alternatively, you can manually register it in your application set-up (as I have done, that's why in the example below I haven't annotated the filter with #Provider).
Here's a very simple example of a pre-matching filter.
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.container.ContainerRequestFilter;
import javax.ws.rs.container.PreMatching;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.UriInfo;
import java.io.IOException;
#PreMatching
public class PreMatchingFilterExample implements ContainerRequestFilter {
private final static Logger LOG = LogManager.getLogger(PreMatchingFilterExample.class);
#Context
UriInfo uriInfo;
#Override
public void filter(ContainerRequestContext requestContext) throws IOException {
LOG.info("Resource not found: " + uriInfo.getAbsolutePath());
LOG.info("Path params: " + uriInfo.getPathParameters());
LOG.info("Query params: " + uriInfo.getQueryParameters());
}
}

Related

CDI Interceptors and Jersey JAX/RS

Using CDI API and Weld 3.0.x as the impl.
I have a simple CDI managed bean/#Named that has a method marked to be intercepted. The interceptor is a simple logging like interceptor. This all just works fine and as expected in a 'container' (like a jboss or wildfly); it also works in a Java SE program (booting CDI via SeContainerInitializer, etc).
Now using the SAME exact managed bean and interceptor, BUT the injection point of the managed bean is in a very simple JAX/RS controller (Jersey Runtime 2.27 with jersey-cdi2-se); the interceptor doesn't fire.
In this environment, the injection resolves fine, as do most other simple cdi things, like producers and post constructs, observes, etc. The rest end-point method can invoke the injectable's methods, but the interceptor will not fire.
There is no servlet or EE container in play; just Jersey and CDI/Weld -- so it is much like JavaSE case - nothing is booting a jetty/grizzly or other simple bootable HTTP container thing.
A bit more backstory:
Basically, I have a vanilla CDI BDU/JAR with some #Nameds in it; some of which have some interceptor annotations. The DBU doesn't contain an impl for the Interceptor annotations. I want to be able include the BDU in few different projects while allowing the host project to provide a specific implementation of interceptors (or omit it) that makes the most sense for the project.
This strategy works OK for host projects that target an EE container or JavaSE. Now I have this odd JavaSE/JAX-RS/Jersey mashup; everything but the interceptor works. It is like there is some interference in the jersey-cdi2-se stuff (or somewhere); or maybe some 'switch' that needs to be thrown. Weld bootstrap logging suggests that interceptor is in play.
Is there something special or other trick to getting CDI interceptors to work along side JAX-RS/Jersey?
This odd SE/jax-rs mashup is really just using AWS servless java container -- so the "http container" is provided by AWS API Gateway and proxied lambda.
(NOTE: I am aware it maybe questionable to use some/all/any of the above techs in a FAAS/Lambda environment. I will tackle the 'should I do this' just after I finish answering the 'can I do this'.)
EDIT & Update:
Is the interceptor enabled? Yes as far as I can tell, it is. I've scraped the following bits out of log (after turning on a bit of WELD logging):
[main] DEBUG org.jboss.weld.Bootstrap - WELD-000105: Enabled interceptor types for Weld BeanManager for /var/task [bean count=5]:
- class org.jboss.weld.contexts.activator.ActivateRequestContextInterceptor,
- class org.jboss.weld.contexts.activator.CdiRequestContextActivatorInterceptor,
- class org.jboss.weld.environment.se.contexts.activators.ActivateThreadScopeInterceptor,
- class com.pin.faas.MyTransactionalInterceptor
.....
[main] DEBUG org.jboss.weld.Bootstrap - WELD-000107: Interceptor: Interceptor [class com.pin.faas.MyTransactionalInterceptor intercepts #MyTransactional
As for some bits of code:
The interceptor binding:
package com.pin.api.annotation;
import java.lang.annotation.*;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import javax.interceptor.InterceptorBinding;
#Inherited
#InterceptorBinding
#Target({ TYPE, METHOD })
#Retention(RUNTIME)
public #interface MyTransactional
{
}
The interceptor impl:
package com.pin.faas;
import java.util.logging.Logger;
import javax.interceptor.*;
import com.pin.api.annotation.MyTransactional;
#Interceptor
#MyTransactional
public class MyTransactionalInterceptor
{
private static final Logger LOGGER = Logger.getLogger("DateServices");
public MyTransactionalInterceptor()
{
LOGGER.info("TransactionalInterceptor constructed");
}
#AroundInvoke
public Object handleTransactionBoudary(InvocationContext context)
throws Exception
{
LOGGER.info("handleTransactionBoudary called!");
return context.proceed();
}
}
The CDI managed bean that has an interceptor marker:
package com.pin.services.impl.businesslogic;
import com.pin.api.DatesService;
import com.pin.api.businesslogic.validations.BadCodeException;
import javax.inject.Inject;
import javax.enterprise.context.RequestScoped;
import javax.inject.Named;
import com.pin.api.annotation.MyTransactional;
#Named
#RequestScoped
public class DatesServiceImpl implements DatesService
{
#Inject
private SomeDao dao;
#Override
#MyTransactional
public String insertRecordIntoXferLog(Integer exceptionCode)
throws BadCodeException
{
dao.insertABrnch();
if(exceptionCode !=null && -1 == exceptionCode)
{
throw new BadCodeException("exception trigger value happenend, tossing an exception and expecting a rollback");
}
return("inserted");
}
}
Finally the JAX-RS controller injects a service:
package com.pin.api.rest;
import javax.enterprise.context.RequestScoped;
import javax.ws.rs.*;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import java.util.logging.Logger;
import javax.inject.Inject;
import com.pin.api.DatesService;
import com.pin.api.businesslogic.validations.BadCodeException;
import java.util.logging.Level;
#Path("/tx")
#RequestScoped
public class TxTestController
{
private static final Logger LOGGER = Logger.getLogger("DateServices");
#Inject
private DatesService service;
#GET
#Produces(MediaType.APPLICATION_JSON)
public Response testAtTransactional(#QueryParam("c") Integer code)
{
Integer val = 1;
LOGGER.info("testAtTransactional invoked");
if(null == code)
{
LOGGER.info("testAtTransactional DID NOT get a code value, assuming value = " + val);
}
else
{
val = code;
LOGGER.info("testAtTransactional DID get a code value = " + val);
}
String result = "called service.insertRecordIntoXferLog(" + val + ")";
try
{
service.insertRecordIntoXferLog(val);
}
catch(BadCodeException bce)
{
result = bce.getMessage();
}
catch(Exception other)
{
result = other.getMessage();
LOGGER.log(Level.SEVERE,"ERROR",other);
}
return Response.status(200).entity(result).build();
}
}
UPDATES
As per some comments I added an #Priority annotation. That didn't change anything. I also adjusted the packaging in a variety of ways in case there was some issue with BDU/Jar loading and CDI bean initialization; these packaging changes made no difference.
I think there is interference with JAX-RS CDI2-SE and interceptors. Looking at how jax-rs-cdi2-se boots a CDI container, it does NOT do anything for interceptors. Maybe it shouldn't need too, as the interceptor is in beans.xml. However in a standalone SE CDI2 sample (no jax-rs - just a console app), I found I had to explicitly enable the interceptor via initializer.enableInterceptors() before the interceptor would work. Explicitly enabling interceptors was needed even with the beans.xml interceptor activation. Maybe this is more of a JAX-RS issue or a Weld issue?

#WebMvcTest mapperscan conflict

My project used spring-boot (1.4.0.release) and mybatis-spring-boot-starter. When I try to have some test code for controller, I always get a exception
Caused by: java.lang.IllegalArgumentException: Property 'sqlSessionFactory' or 'sqlSessionTemplate' are required
at org.springframework.util.Assert.notNull(Assert.java:115)
at org.mybatis.spring.support.SqlSessionDaoSupport.checkDaoConfig(SqlSessionDaoSupport.java:75)
at org.mybatis.spring.mapper.MapperFactoryBean.checkDaoConfig(MapperFactoryBean.java:74)
at org.springframework.dao.support.DaoSupport.afterPropertiesSet(DaoSupport.java:44)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1637)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1574)
... 42 more`
But when I comment #MapperScan("com.toby.mapper"), it runs very well.
Here is my example class:
#MapperScan("com.toby.mapper")
#EnableTransactionManagement
#EnableConfigurationProperties(AppConfig.class)
#SpringBootApplication(scanBasePackages = "com.toby.configuration,com.toby.web.controller,com.toby.service,com.toby.dao")
public class Example {
public static void main(String[] args) throws Exception {
//new SpringApplicationBuilder().sources(Example.class).run(args);
SpringApplication application=new SpringApplication(Example.class);
application.addInitializers(new PropertyPasswordDecodingContextInitializer());
application.run(args);
}
}
Here is my test code:
package com.toby.web.controller;
import com.toby.common.config.AppConfig;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
/**
* Created by Toby on 2016/8/10.
*/
#RunWith(SpringRunner.class)
#WebMvcTest(value = MyRestController.class)
public class MyRestControllerTests {
#Autowired
private MockMvc mvc;
#MockBean
private AppConfig appConfig;
#Test
public void testHome() throws Exception {
/*this.mvc.perform(get("/users").accept(MediaType.TEXT_PLAIN))
.andExpect(status().isOk()).andExpect(content().string("Honda Civic"));*/
}
}
I guess you've updated the description or I didn't read it properly the first time. #MapperScan is a mybatis specific annotation that triggers something but is missing some guard of some sort.
We had the same problem in boot actually. Let's say you put #EnableCaching on your main app. Because slicing disables all auto-configurations but a list of specific ones, the cache auto-configuration would not kick in and you'll get an exception because the CacheManager isn't found. To fix that issue, we've started to create some annotation to easily enable those. If you look at WebMbcTest you'll see it's annotated with AutoConfigureCache that's going to provide a dummy no-op cache manager unless specified otherwise.
Your problem is that the mybatis support is a third party integration and there isn't any support for that. Some solutions:
Change #WebMbvcTest to provide the class of another configuration class, effectivly disabling the use of your main spring boot app. Of course that class shouldn't define the #MapperScan annotation
Move the MapperScan (and anything that's not required with slicing) to another Configuration class. It could be a class in the same package as your app. Slicing won't scan those by default so you'll be fine. It's by far the easiest
Create an issue in the mybatis support so that they improve the auto-configuration to back-off (prevent this exception). I am not sure that's possible actually
Long story short, since #MapperScan is a way to tell mybatis to scan your entities, maybe you shouldn't add it on your main boot app if you use slicing. Because your #WebMbcTest doesn't want to use that obviously.

Validating JMS payload in Spring

I have a simple service sending emails. It can be invoked using REST and JMS APIs. I want the requests to be validated before processing.
When I invoke it using REST I can see that org.springframework.validation.DataBinder invokes void validate(Object target, Errors errors, Object... validationHints) and then validator from Hibernate is invoked. This works as expected.
The problem is I can't achieve the same effect with JMS Listener. The listener is implemented as follows:
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.jms.annotation.JmsListener;
import org.springframework.stereotype.Component;
import our.domain.mailing.Mailing;
import our.domain.mailing.jms.api.SendEmailFromTemplateRequest;
import our.domain.mailing.jms.api.SendSimpleEmailRequest;
import javax.validation.Valid;
#ConditionalOnProperty("jms.configuration.destination")
#Component
#AllArgsConstructor(onConstructor = #__(#Autowired))
#Slf4j
public class SendMailMessageListener {
Mailing mailing;
#JmsListener(destination = "${jms.configuration.destination}")
public void sendEmailUsingTemplate(#Valid SendEmailFromTemplateRequest request) {
log.debug("Received jms message: {}", request);
mailing.sendEmailTemplate(
request.getEmailDetails().getRecipients(),
request.getEmailDetails().getAccountType(),
request.getTemplateDetails().getTemplateCode(),
request.getTemplateDetails().getLanguage(),
request.getTemplateDetails().getParameters());
}
#JmsListener(destination = "${jms.configuration.destination}")
public void sendEmail(#Valid SendSimpleEmailRequest request) {
log.debug("Received jms message: {}", request);
mailing.sendEmail(
request.getRecipients(),
request.getSubject(),
request.getMessage());
}
}
The methods receive payloads but they are not validated. It's Spring Boot application and I have #EnableJms added. Can you guide what part of Spring source code is responsible for discovering #Validate and handling it? If you have any hints on how to make it running I would appreciate it a lot.
The solution is simple and was clearly described in official documentation: 29.6.3 Annotated endpoint method signature. There are few things you have to do:
Provide configuration implementing JmsListenerConfigurer (add #Configuration class implementing this interface)
Add annotation #EnableJms on the top of this configuration
Create bean DefaultMessageHandlerMethodFactory. It can be done in this configuration
Implement method void configureJmsListeners(JmsListenerEndpointRegistrar registrar) of interface JmsListenerConfigurer implemented by your configuration and set MessageHandlerMethodFactory using the bean you've just created
Add #Validated instead of #Valid to payload parameters
You can use #Valid in your listeners. Your answer was very close to it. In the step when you create DefaultMessageHandlerMethodFactory call .setValidator(validator) where validator is from org.springframework.validation. You can configure validator like this:
#Bean
public LocalValidatorFactoryBean configureValidator ()
{
return new LocalValidatorFactoryBean();
}
And then inject validator instance into your jms config

Where to throw customized 404 error in jersey when HttpServer can't find a resource

I want to customize 404 response, that the server (not me) throws whenever it can't find a requested resource, (or throw a customized WebApplicationException myself, if it's possible to test if a requested resource is present in one app? probably a List of resources is stored somewhere?).
please don't refer me to solutions that suggest to extend WebApplicationException, because even doing so, my problem is when to throw it?, when resource is not found! but how to express this need in jersey framework
Jersey throws javax.ws.rs.NotFoundException when it cannot find an endpoint. Just use an exception mapper to transform it to a response of your choice:
import javax.ws.rs.NotFoundException;
import javax.ws.rs.core.Response;
import javax.ws.rs.ext.ExceptionMapper;
import javax.ws.rs.ext.Provider;
#Provider
public class NotFoundExceptionMapper implements ExceptionMapper<NotFoundException> {
public Response toResponse(NotFoundException exception) {
return Response.status(Response.Status.NOT_FOUND)
.entity("No such resource")
.build();
}
}

Retrieving the servlet context path from a Spring web application

I would like to be able to dynamically retrieve the "servlet context path" (e.g. http://localhost/myapp or http://www.mysite.com) for my spring web application from a Service spring bean.
The reason for this is that I want to use this value in email that are going to be sent to users of the website.
While it would be pretty easy to do this from a Spring MVC controller, it is not so obvious to do this from a Service bean.
Can anyone please advise?
EDIT: Additional requirement:
I was wondering if there wasn't a way of retrieving the context path upon startup of the application and having it available for retrieval at all time by all my services?
If you use a ServletContainer >= 2.5 you can use the following code to get the ContextPath:
import javax.servlet.ServletContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component
#Component
public class SpringBean {
#Autowired
private ServletContext servletContext;
#PostConstruct
public void showIt() {
System.out.println(servletContext.getContextPath());
}
}
As Andreas suggested, you can use the ServletContext. I use it like this to get the property in my components:
#Value("#{servletContext.contextPath}")
private String servletContextPath;
I would avoid creating a dependency on the web layer from your service layer. Get your controller to resolve the path using request.getRequestURL() and pass this directly to the service:
String path = request.getRequestURL().toString();
myService.doSomethingIncludingEmail(..., path, ...);
If the service is triggered by a controller, which I am assuming it is you can retrieve the path using HttpSerlvetRequest from the controller and pass the full path to the service.
If it is part of the UI flow, you can actually inject in HttpServletRequest in any layer, it works because if you inject in HttpServletRequest, Spring actually injects a proxy which delegates to the actual HttpServletRequest (by keeping a reference in a ThreadLocal).
import javax.servlet.http.HttpServletRequest;
import org.springframework.beans.factory.annotation.Autowired;
public class AServiceImpl implements AService{
#Autowired private HttpServletRequest httpServletRequest;
public String getAttribute(String name) {
return (String)this.httpServletRequest.getAttribute(name);
}
}
With Spring Boot, you can configure the context-path in application.properties:
server.servlet.context-path=/api
You can then get the path from a Service or Controller like this:
import org.springframework.beans.factory.annotation.Value;
#Value("${server.servlet.context-path}")
private String contextPath;

Resources