Is it possible to use Guice AOP to intercept an annotated method on a Jersey resource?
I have a successfully configured Guice integration working with Jersey with respect to Dependency Injection without any problems, however my configured Interceptor is not intercepting my annotated method at all.
web.xml
<listener>
<listener-class>my.package.GuiceConfig</listener-class>
</listener>
<filter>
<filter-name>guiceFilter</filter-name>
<filter-class>com.google.inject.servlet.GuiceFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>guiceFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
GuiceConfig configuration module
public class GuiceConfig extends GuiceServletContextListener {
#Override
protected Injector getInjector() {
return Guice.createInjector(new JerseyServletModule() {
#Override
protected void configureServlets() {
bindInterceptor(Matchers.any(),
Matchers.annotatedWith(RequiredAuthority.class),
new AuthorisationInterceptor());
Map<String, String> params = new HashMap<String, String>();
params.put(JSP_TEMPLATES_BASE_PATH, "/WEB-INF/jsp");
params.put(FEATURE_FILTER_FORWARD_ON_404, "true");
params.put(PROPERTY_PACKAGES, "my.service.package");
filter("/*").through(GuiceContainer.class, params);
}
});
}
}
RequiredAuthority annotation
#Target(ElementType.METHOD)
#Retention(RetentionPolicy.RUNTIME)
public #interface RequiredAuthority {
String value();
}
AuthorisationInterceptor aspect
public class AuthorisationInterceptor implements MethodInterceptor {
public Object invoke(MethodInvocation methodInvocation) throws Throwable {
// Allow invocation to process or throw an appropriate exception
}
}
TempResource JAX-RS resource class
#Path("/temp")
public class TempResource {
#GET
#Produces(MediaType.APPLICATION_JSON)
#RequiredAuthority("PERMISSION")
public String getTemp() {
// Return resource normally
}
}
Looks like configureServlets() isn't calling:
bind(TempResource.class);
Related
We wan't to use aop methodinterceptors using guice as provider under tomcat 8.5. We are currently already using interceptors and guice in our Java-FX applications without any problems.
Trying to do the same under Tomcat does not work. Objects are injected into servlets via guice, but the annotated methods of those objects are not bound to the interceptor. It looks as if guice might think that cglib/asm are not available and revert to none-aop.
Are there any special preconditions the servlet containers needs to fullfil in order to use guice's aop? As stated above the same configuration of guice/cglib/asm works for us in none webapp projects.
#Singleton
public class TestServlet extends HttpServlet {
#Inject
X x;
public TestServlet() {
System.out.println("constructor");
try {
throw new IOException();
} catch (final Exception e) {
e.printStackTrace();
}
}
#Override
protected void doGet(final HttpServletRequest req, final HttpServletResponse resp)
throws ServletException, IOException {
testMethod(resp);
}
protected void testMethod(final HttpServletResponse resp) throws IOException {
x.test(resp);
}
}
We wan't X in our servlet to contain methods that are intercepted.
By putting the throw/catch thing in the constructor we verified that the constructor is called through guice.
public class X {
#Y
public int test(final ServletResponse res) throws IOException {
res.getWriter().write("123");
return 1;
}
}
public class TestInterceptor implements MethodInterceptor {
#Override
public Object invoke(final MethodInvocation arg0) throws Throwable {
System.out.println("METHOD INTERCEPTOR " + arg0);
return arg0.proceed();
}
}
public class Module extends AbstractModule {
#Override
protected void configure() {
System.out.println("TestInterceptor configuration");
bindInterceptor(Matchers.any(), Matchers.annotatedWith(Y.class), new TestInterceptor());
}
}
#WebListener
public class BootStrap extends GuiceServletContextListener {
...
#Override
protected Injector getInjector() {
final Injector inject = Guice.injector(new Module(), new ServletModule() {
#Override
protected void configureServlets() {
super.configureServlets();
System.out.println("Injector");
serve("/test2/*").with(TestServlet.class);
}
});
return inject;
}
}
The servlet is reachable and X is none-null but looking at it in the debugger it is clear no binary code modifications were done.
Are we missing somethings here? Can anyone link an example of a working guice (4+)/tomcat (8+)/aop example?
Edit
It turned out to be unrelated to the servlet container. The trouble was with guice itself. Sorry for the confusion, this problem was very hard to fence in.
For those interested we opened an issue
https://github.com/google/guice/issues/1094
Note that at the time of writing this is not accepted as a bug. It could also be that we missinterpreted the javadoc.
I've used Guice AOP with Tomcat before (though it was an older version of Tomcat) and AOP worked. (I can't link because it was proprietary code).
One thing I notice looking at your posted code is that you are not using the GuiceFilter, which I believe is required.
As stated here you need to configure it at the top of your web.xml like so:
<filter>
<filter-name>guiceFilter</filter-name>
<filter-class>com.google.inject.servlet.GuiceFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>guiceFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
Edit based on comments:
You shouldn't have to modify/hack the classloader to use Guice interceptors in a servlet container. They should work out of the box with no additional changes.
There are REST web service based on Jersey 2.23.2 & Guice 3.0. To use Guice it is necessary to adjust the hk2-guice bridge (I'm using 2.5.0-b07). Everything works fine until I have tried to test the service using Jersey Test Framework. Can't configure hk2-guice bridge for tests.
My test:
public class SomeTest extends JerseyTestNg.ContainerPerClassTest {
#Override
protected TestContainerFactory getTestContainerFactory() throws TestContainerException {
return new GrizzlyWebTestContainerFactory();
}
#Override
protected Application configure() {
return super.configure(); // cant't configure
}
#Test
public void test() {
Assert.assertEquals(1, 1);
}
}
I can't configure test in SomeTest.configure() just returning new JerseyConfiguration() (see below) because JerseyConfiguration's constructor expected ServiceLocator's object.
Even if it were possible to return an object of class `JerseyConfiguration` - I'm not sure that my test would work because some filters and listener are defined in the web.xml file.
How to configure the test taking into account all filters, listener and hk2-guice bridge?
Web service details
Dependencies section from build.gradle:
def jerseyVersion = '2.23.2'
def hk2Version = '2.5.0-b07'
def giuceVersion = '3.0'
dependencies {
compile "javax.servlet:javax.servlet-api:3.1.0"
//jersey
compile "org.glassfish.jersey.core:jersey-server:${jerseyVersion}"
compile "org.glassfish.jersey.containers:jersey-container-servlet:${jerseyVersion}"
//hk2
compile "org.glassfish.hk2:guice-bridge:${hk2Version}"
//guice
compile "com.google.inject:guice:${giuceVersion}"
compile "com.google.inject.extensions:guice-servlet:${giuceVersion}"
}
File web.xml:
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
<display-name>REST API App</display-name>
<listener>
<listener-class>com.example.core.JerseyGuiceServletContextListener</listener-class>
</listener>
<filter>
<filter-name>guiceFilter</filter-name>
<filter-class>com.google.inject.servlet.GuiceFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>guiceFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<filter>
<filter-name>jerseyFilter</filter-name>
<filter-class>org.glassfish.jersey.servlet.ServletContainer</filter-class>
<init-param>
<param-name>javax.ws.rs.Application</param-name>
<param-value>com.example.core.JerseyConfiguration</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>jerseyFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
</web-app>
Class JerseyGuiceServletContextListener:
public class JerseyGuiceServletContextListener extends GuiceServletContextListener {
static Injector injector;
#Override
protected Injector getInjector() {
injector = Guice.createInjector(new JerseyServletModuleConfig());
return injector;
}
}
Class JerseyServletModuleConfig:
class JerseyServletModuleConfig extends ServletModule {
#Override
protected void configureServlets() {
bind(HeyResource.class).in(Scopes.SINGLETON);
}
}
Class JerseyConfiguration:
package com.example.core;
import com.google.inject.Injector;
import org.glassfish.hk2.api.ServiceLocator;
import org.glassfish.jersey.server.ResourceConfig;
import org.jvnet.hk2.guice.bridge.api.GuiceBridge;
import org.jvnet.hk2.guice.bridge.api.GuiceIntoHK2Bridge;
import javax.inject.Inject;
import javax.servlet.ServletContext;
class JerseyConfiguration extends ResourceConfig {
#Inject
public JerseyConfiguration(ServiceLocator serviceLocator, ServletContext servletContext) {
packages("com.example.ws");
GuiceBridge.getGuiceBridge().initializeGuiceBridge(serviceLocator);
GuiceIntoHK2Bridge guiceBridge = serviceLocator.getService(GuiceIntoHK2Bridge.class);
guiceBridge.bridgeGuiceInjector(JerseyGuiceServletContextListener.injector);
}
}
just overrode configureDeployment() & modified configure() methods in test
public class SomeTest extends JerseyTestNg.ContainerPerClassTest {
#Override
protected TestContainerFactory getTestContainerFactory() throws TestContainerException {
return new GrizzlyWebTestContainerFactory();
}
#Override
protected Application configure() {
return new ResourceConfig()
.packages("com.example.ws")
.register(GensonCustomResolver.class);
}
#Override
protected DeploymentContext configureDeployment() {
Application application = configure();
return ServletDeploymentContext.builder(application)
.addListener(JerseyGuiceServletContextListener.class)
.addFilter(GuiceFilter.class, "guiceFilter")
.addFilter(ServletContainer.class, "jerseyFilter", new HashMap<String, String>(){{
put("javax.ws.rs.Application", JerseyConfiguration.class.getCanonicalName());
}})
.build();
}
#Test
public void test() {
Assert.assertEquals(1, 1);
}
}
& add web.xml to test classpath.
How do I register my provider programmatically in jersey which implements the Exceptionmapper provided by jersey API? I don't want to use #Provider annotation and want to register the provider using ResourceConfig, how can I do that?
For example:
public class MyProvider implements ExceptionMapper<WebApplicationException> extends ResourceConfig {
public MyProvider() {
final Resource.Builder resourceBuilder = Resource.builder();
resourceBuilder.path("helloworld");
final ResourceMethod.Builder methodBuilder = resourceBuilder.addMethod("GET");
methodBuilder.produces(MediaType.TEXT_PLAIN_TYPE)
.handledBy(new Inflector<ContainerRequestContext, String>() {
#Override
public String apply(ContainerRequestContext containerRequestContext) {
return "Hello World!";
}
});
final Resource resource = resourceBuilder.build();
registerResources(resource);
}
#Override
public Response toResponse(WebApplicationException ex) {
String trace = Exceptions.getStackTraceAsString(ex);
return Response.status(500).entity(trace).type("text/plain").build();
}
}
Is this the correct way to do this?
I'm guessing you don't have a ResourceConfig, since you seem to not be sure how to use it. For one, it is not required. If you do use it, it should be it's own separate class. There you can register the mapper.
public class AppConfig extends ResourceConfig {
public AppConfig() {
register(new MyProvider());
}
}
But you are probably using a web.xml. In which case, you can register the provider, with the following <init-param>
<servlet>
<servlet-name>MyApplication</servlet-name>
<servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class>
<init-param>
<param-name>jersey.config.server.provider.classnames</param-name>
<param-value>
org.foo.providers.MyProvider
</param-value>
</init-param>
</servlet>
Have a look at What exactly is the ResourceConfig class in Jersey 2?
for more information on different deployment models. There are a few different ways to deploy applications. You can even mix and match (web.xml and ResourceConfig).
While #paul-samsotha's answer is correct, still there is implementation trick. I want to share it and hope it will help someone.
a) Implement your mapper:
public class MyExceptionMapper implements ExceptionMapper<Throwable>, ResponseErrorMapper {
...
b) make sure you declare generic type, otherwise your mapper will never be called
public class MyExceptionMapper implements ExceptionMapper/* no generic declaration */, ResponseErrorMapper {
...
and may trigger
javax.ws.rs.ProcessingException: Could not find exception type for given ExceptionMapper class: class com...MyExceptionMapper.
c) Register it as resource:
ResourceConfig config = new ResourceConfig();
config.register(new MyExceptionMapper());
or
config.register(MyExceptionMapper.class);
d) make sure you enforce processing errors as well:
config.setProperties(new LinkedHashMap<String, Object>() {{
put(org.glassfish.jersey.server.ServerProperties.PROCESSING_RESPONSE_ERRORS_ENABLED, true);
}});
If you're using Spring and want to register the providers programmatically based on the presence of #Path and #Provider annotation you can use the following technique
#Component
public class JerseyConfig extends ResourceConfig {
#Autowired
private ApplicationContext applicationContext;
#PostConstruct
public init() {
applicationContext.getBeansWithAnnotation(Path.class).values().forEach(
component -> register(component.getClass())
);
applicationContext.getBeansWithAnnotation(Provider.class).values().forEach(
this::register
);
}
}
Could you possibly explain how I can get the ServletContext instance in my Application's sub-class? Is it possible? I have tried to do it like in the following snippet but it does not seem to work - the ctx is not set:
import javax.ws.rs.core.Application;
import javax.ws.rs.core.Context;
//...
#ApplicationPath("/")
public class MainApplication extends Application {
#Context ServletContext ctx;
#Override
public Set<Class<?>> getClasses() {
Set<Class<?>> classes = new HashSet<Class<?>>();
//...
return classes;
}
}
web.xml:
<web-app ...>
<context-param>
<param-name>environment</param-name>
<param-value>development</param-value>
</context-param>
<filter>
<filter-name>jersey-filter</filter-name>
<filter-class>org.glassfish.jersey.servlet.ServletContainer</filter-class>
<init-param>
<param-name>javax.ws.rs.Application</param-name>
<param-value>my.MainApplication</param-value>
</init-param>
</filter>
...
</web-app>
The problem is that I need to get context parameters from it. If there is another way, I would be grateful if somebody gave a hint.
I understand that Context annotation might not be purposed for this. Actually, I do not need ServletContext itself. If only I could get context params from web.xml, I would be absolutely happy.
Here is an example of what I really need:
import java.util.HashSet;
import java.util.Set;
import javax.servlet.ServletContext;
import javax.ws.rs.core.Application;
import javax.ws.rs.core.Context;
import org.glassfish.hk2.utilities.binding.AbstractBinder;
public class MainApplication extends Application {
#Context ServletContext ctx;
#Override
public Set<Object> getSingletons() {
Set<Object> set = new HashSet<Object>();
final String environment = ctx.getInitParameter("environment");
//final String environment = ... get context parameter from web xml
set.add(new AbstractBinder() {
#Override
protected void configure() {
bind(new BaseDataAccess(environment)).to(DataAccess.class);
}
});
//...
return set;
}
}
Thanks.
Since Jersey 2.5, ServletContext can be injected directly in constructor:
https://java.net/jira/browse/JERSEY-2184
public class MyApplication extends ResourceConfig {
public MyApplication(#Context ServletContext servletContext) {
// TODO
}
}
#Context can be made available on ResoureConfig by injecting it as a constructor parameter using #Context. Another way to access it is through an event handler.
Try the below code.
#ApplicationPath("...")
public class MyApplication extends ResourceConfig {
public MyApplication() {
register(StartupHandler.class);
}
private static class StartupHandler extends AbstractContainerLifecycleListener {
#Context
ServletContext ctx;
#Override
public void onStartup(Container container) {
// You can put code here for initialization.
}
}
// ...
Injection happens when you enter service method. Check if this is a problem.
There is interesting statement in documentation for Jersey version 1.18 for class
com.sun.jersey.spi.container.servlet.ServletContainer
The servlet or filter may be configured to have an initialization
parameter "com.sun.jersey.config.property.resourceConfigClass" or
"javax.ws.rs.Application" and whose value is a fully qualified name of
a class that implements ResourceConfig or Application. If the concrete
class has a constructor that takes a single parameter of the type Map
then the class is instantiated with that constructor and an instance
of Map that contains all the initialization parameters is passed as
the parameter.
If my understanding is correct the following constructor must be invoced with "an instance of Map that contains all the initialization parameters"
public class ExampleApplication extends Application {
public ExampleApplication(Map initParams) {
}
...
}
Here is appropriate part of web.xml:
<servlet>
<servlet-name>Jersey Web Application</servlet-name>
<servlet-class>com.sun.jersey.spi.container.servlet.ServletContainer</servlet-class>
<init-param>
<param-name>javax.ws.rs.Application</param-name>
<param-value>experiment.service.ExampleApplication</param-value>
</init-param>
</servlet>
But somehow it failed for me with the following message:
SEVERE: Missing dependency for constructor public
experiment.service.ExampleApplication(java.util.Map) at parameter
index 0
And for current version of Jersey (2.5.1) there are no such statement in documentstion:
https://jersey.java.net/apidocs/latest/jersey/org/glassfish/jersey/servlet/ServletContainer.html
You can use the ApplicationEventListener interface to get the ServletContext. After initialization has finished, you can 'catch' an ApplicationEvent and use the injected ServletContext to work with.
Works fine with: org.glassfish.jersey : 2.12
For additional versions, pls use comments - i dont know, sry.
Jersey Docs - 20.1.2. Event Listeners
Your MainApplication:
#ApplicationPath("/")
public class MainApplication extends Application {
#Override
public Set<Class<?>> getClasses() {
Set<Class<?>> set = new HashSet<Class<?>>();
set.add(MainApplicationListener.class);
return classes;
}
}
... or alternative MainResourceConfig (I prefer to use this one):
public class MainResourceConfig extends ResourceConfig {
public MainResourceConfig() {
register(MainApplicationListener.class);
}
}
And the ApplicationEventListener:
public class MainApplicationListener implements ApplicationEventListener {
#Context
private ServletContext ctx; //not null anymore :)
#Override
public void onEvent(ApplicationEvent event) {
switch (event.getType()) {
case INITIALIZATION_FINISHED:
// do whatever you want with your ServletContext ctx
break;
}
#Override
public RequestEventListener onRequest(RequestEvent requestEvent) {
return null;
}
}
Don't use #Context in your Application but in a Resource class.
#Path("/foos")
public class FooResource {
#Context
ServletContext ctx;
#GET
public Response getFoos() {
return Response.ok().build();
}
}
I'm new to Guice and already stuck :)
I pretty much copied classes GuiceConfig, OfyFactory and slightly modified Ofy from Motomapia project (which you can browse) using it as s sample.
I created GuiceServletContextListener which looks like this
public class GuiceConfig extends GuiceServletContextListener
{
static class CourierServletModule extends ServletModule
{
#Override
protected void configureServlets()
{
filter("/*").through(AsyncCacheFilter.class);
}
}
public static class CourierModule extends AbstractModule
{
#Override
protected void configure()
{
// External things that don't have Guice annotations
bind(AsyncCacheFilter.class).in(Singleton.class);
}
#Provides
#RequestScoped
Ofy provideOfy(OfyFactory fact)
{
return fact.begin();
}
}
#Override
public void contextInitialized(ServletContextEvent servletContextEvent)
{
super.contextInitialized(servletContextEvent);
}
#Override
protected Injector getInjector()
{
return Guice.createInjector(new CourierServletModule(), new CourierModule());
}
}
I added this listener into my web.xml
<web-app>
<listener>
<listener-class>com.mine.courierApp.server.GuiceConfig</listener-class>
</listener>
<!-- GUICE -->
<filter>
<filter-name>GuiceFilter</filter-name>
<filter-class>com.google.inject.servlet.GuiceFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>GuiceFilter</filter-name>
<url-pattern>/*</url-pattern>
<dispatcher>REQUEST</dispatcher>
<dispatcher>FORWARD</dispatcher>
<dispatcher>INCLUDE</dispatcher>
</filter-mapping>
<!-- My test servlet -->
<servlet>
<servlet-name>TestServlet</servlet-name>
<servlet-class>com.mine.courierApp.server.TestServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>TestServlet</servlet-name>
<url-pattern>/test</url-pattern>
</servlet-mapping>
</web-app>
OfyFactory looks like this
#Singleton
public class OfyFactory extends ObjectifyFactory
{
Injector injector;
#Inject
public OfyFactory(Injector injector)
{
this.injector = injector;
register(Pizza.class);
register(Ingredient.class);
}
#Override
public <T> T construct(Class<T> type)
{
return injector.getInstance(type);
}
#Override
public Ofy begin()
{
return new Ofy(super.begin());
}
}
Ofy doesn't have any Guice annotations at all...
public class Ofy extends ObjectifyWrapper<Ofy, OfyFactory>
{
// bunch of helper methods here
}
And finally test servlet where I'm trying to use injected field looks like this
public class TestServlet extends HttpServlet
{
#Inject Ofy ofy;
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
{
ofy.save(new Pizza());
}
}
Ofy ofy is always null. It's never injected. And it's not injected because OfyFactory is never instantiated, its constructor is never called.
Could you please point what I'm doing wrong? Why my singleton is never created?
Thanks a lot.
Instead of defining TestServlet in the web.xml file, try deleting its mapping from web.xml and adding this line in the configureServlets() method:
serve("/test").with(TestServlet.class);
You may also need to bind TestServlet as a Singleton either by annotating the class with #Singleton or by adding a
bind(TestServlet.class).in(Singleton.class);
line to one of the modules.
What's happening is that Guice is not actually creating your servlet so it isn't able to inject the Ofy object. Guice will only create servlets if it is instructed to do so using a serve(...).with(...) binding. Any servlets defined in the web.xml are outside of Guice's control.