There is some way to support AOP suport in jax-rs method resource execution like:
public interface ResourceInvocationContext {
public Object[] getParameters(); // method parameter values
public Class<?> getResourceClass(); // class method resource
public Method getResourceMethod(); // method
public void proceed(); //proceed execution
}
public interface ContainerRequestResourceInvocationFilter {
//must call resourceInvocationContext.proceed() in case of non abortWith
public void aroundResourceInkove(ResourceInvocationContext resourceInvocationContext,ContainerRequestContext containerRequestContext);
}
Im asking for this because of current fight between cdi interceptor and jax-rs lifecycle, sometimes they dont work very well together in because current lifecycle of jax-rs.
It`s very dificult work with cdi interceptor because of termination of request (abortWith) as example.
Besides interceptors must have annotated methods to work, forcing many times develop to write cdi extension to override beans on processing adding unwanted annotations to force execution.
Could you describe the problems you run into when using a CDI interceptor in more detail? I'm asking because JAX-RS will improve the integration with CDI in the near future and defining an alternative Interceptor-like API would be weird.
its not a problem itself we can think more about how two spec fight talking each other in this cases. Code as use case:
@Interceptor
@MyQualifier
@Priority(Interceptor.Priority.APPLICATION)
public class MyInterceptor {
// First is there is no clean way to inject containerRequestContext
// @Inject and @Context (force use of @provider (Provider in interceptor ???)) seems doesnt works here. Maybe a @prematch thas set in a holder this guy (ugly and confuse).
private ContainerRequestContext containerRequestContext;
@AroundInvoke
public Object aroundInvoke(InvocationContext invocationContext) throws Exception {
Method method = invocationContext.getMethod(); //mandatory
Parameter[] methodParameters = method.getParameters(); //mandatory
Object[] parameters = invocationContext.getParameters(); //mandatory
//some logic checking for something in parameters values , annotations , behaviours , etc
// i want abort lifecycle in this point since makes no sense proceed anymore.
if(isSomethingGoWrongBeforeCallMethod) {
containerRequestContext.abortWith(Response.status(Status.BAD_REQUEST).entity(entity).build());
}
//Return null or return invocationContext.proceed() ?
return null; //wich one
return invocationContext.proceed(); //wich one
}
}
}
Another problem. How can i apply globally,dynamic,named binding (as jax-rs does) on this interceptor without write an extension thats modifies every bean adding qualifier @MyQualifier on cdi bean processing (ugly, annoying and confuse again).
Forgetting about cdi itself for a moment (think thats cdi is not supported in jax-rs impl and interceptor is not available). There is a gap in resource invocation after parameters was readed and before execution itself, did you see ? Maybe offering two new interface fill this gap forever (like ReaderInterceptor and WriterInterceptor does).
public interface ResourceInvocationContext {
public Object[] getParameters(); // method parameter values
public Class<?> getResourceClass(); // class method resource
public Method getResourceMethod(); // method
public void proceed(); //proceed execution
}
public interface ContainerRequestResourceInvocationFilter {
//must call resourceInvocationContext.proceed() in case of non abortWith
public void aroundResourceInkove(ResourceInvocationContext resourceInvocationContext,ContainerRequestContext containerRequestContext);
}
The very same implementation with new api:
@Provider
//Choose sequence of execution
@Priority(Priorities.SOMETHING..)
public class MyContainerRequestResourceInvocationFilter implements ContainerRequestResourceInvocationFilter {
//Injections can ben done as normally
public void aroundResourceInkove(ResourceInvocationContext resourceInvocationContext,ContainerRequestContext containerRequestContext){
Method method = resourceInvocationContext.getMethod(); //mandatory
Parameter[] methodParameters = method.getParameters(); //mandatory
Object[] parameters = resourceInvocationContext.getParameters(); //mandatory
//some logic checking for something in parameters values , annotations , behaviours , etc
// i want abort lifecycle in this point since makes no sense proceed anymore.
if(isSomethingGoWrongBeforeCallMethod) {
containerRequestContext.abortWith(Response.status(Status.BAD_REQUEST).entity(entity).build());
} else {
resourceInvocationContext.proceed();
}
}
}
if many implementation of ContainerRequestResourceInvocationFilter is provided, naturally chaining is provided before actual execution.
-> aroundInkove resource
-> aroundInvoke resource
-> actual method resource execution
Much lot better and talk nicelly with jax-rs spec and cdi. Applyed globally as usually and suporting named and dinamic binding if possible.
What did you guys thik about it ? Nowadays cdi and jax-rs does not talk each other very well yet in this kind of scenario, will open a huge flexibility for a lot os scenarios that we want abort request for some reason after parameters readed and before method resource invocation.
Once CDI is fully supported by JAX-RS, would there still be a need for your proposal?
I want to share my thoughts about some of these comments:
// First is there is no clean way to inject containerRequestContext // @Inject and @Context (force use of @provider (Provider in interceptor ???)) // seems doesnt works here. Maybe a @prematch thas set in a holder this guy // (ugly and confuse). private ContainerRequestContext containerRequestContext;
This will be fixed as soon as JAX-RS integrates more closely with CDI. One part of the improved CDI integration is the ability to inject JAX-RS context objects into CDI beans.
Another problem. How can i apply globally,dynamic,named binding (as jax-rs does) on this interceptor without write an extension thats modifies every bean adding qualifier @MyQualifier on cdi bean processing (ugly, annoying and confuse again).
Could you explain your use case in more detail? Of course you can dynamically modify CDI beans by adding qualifiers via an extension to control for which resources your interceptor gets invoked. But usually you can add the qualifiers directly to your bean. Unless you are writing some kind of framework or extension library.
Forgetting about cdi itself for a moment (think thats cdi is not supported in jax-rs impl and interceptor is not available). There is a gap in resource invocation after parameters was readed and before execution itself, did you see ? Maybe offering two new interface fill this gap forever (like ReaderInterceptor and WriterInterceptor does).
As JAX-RS will require CDI in the future, this won't be a problem anymore, because you can always use CDI interceptors for something like this.
To sum up:
I agree that you currently cannot easily access JAX-RS context objects from a CDI extension. At least not out of the box. Of course, you can store these objects in a request-scoped bean to access them later from the interceptor, but it requires some effort.
However, this will change soon when JAX-RS is integrated more deeply with CDI. Then you can simply inject these objects directly. And at this point you should be able to do whatever you want in a CDI interceptor. Or am I missing something?
@mkarg
I think that's its not just a simple CDI better integration problem, but a lifecycle problem. I will comment further with more source code. But we can discuss a little more about it.
@chkal
I think maybe you are missing some others aspects, even working around the injection problem in interceptor doesn't work. Have a look for this piece of code. As suggestions i'm workarounding for some missing features ok ? After that we can discuss a little more.
First lets resolve problem with ContainerRequestContext injection creating a kind of holder:
@RequestScoped
public class JaxRsContext {
private ContainerRequestContext containerRequestContext;
private Method resourceMethod;
private Class<?> resourceClass;
public ContainerRequestContext getContainerRequestContext() {
return containerRequestContext;
}
public void setContainerRequestContext(ContainerRequestContext containerRequestContext) {
this.containerRequestContext = containerRequestContext;
}
public Method getResourceMethod() {
return resourceMethod;
}
public void setResourceMethod(Method resourceMethod) {
this.resourceMethod = resourceMethod;
}
public Class<?> getResourceClass() {
return resourceClass;
}
public void setResourceClass(Class<?> resourceClass) {
this.resourceClass = resourceClass;
}
}
Ok. Now, at very early request we will set this guys.
@Provider
//Maybe @PreMatching ???
@Priority(0)
public class JaxRsContextContainerRequestFilter implements ContainerRequestFilter {
private static final Logger logger = LoggerFactory.getLogger(JaxRsContextContainerRequestFilter.class);
@Inject
private JaxRsContext jaxRsContext;
@Context
private ResourceInfo resourceInfo;
@Override
public void filter(ContainerRequestContext containerRequestContext) throws IOException {
Method method = resourceInfo.getResourceMethod();
Class<?> clazz = resourceInfo.getResourceClass();
jaxRsContext.setContainerRequestContext(containerRequestContext);
jaxRsContext.setResourceMethod(method);
jaxRsContext.setResourceClass(clazz);
}
}
So far so good. Now let's create an interceptor
@Interceptor
@MyQualifier
@Priority(Interceptor.Priority.APPLICATION)
public class MyInterceptor {
@Inject
private JaxRsContext jaxRsContext;
@AroundInvoke
public Object aroundInvoke(InvocationContext invocationContext) throws Exception {
Method method = invocationContext.getMethod(); //mandatory
Parameter[] methodParameters = method.getParameters(); //mandatory
Object[] parameters = invocationContext.getParameters(); //mandatory
// Some logic checking for something in parameters values , annotations , behaviours , etc
// I want abort lifecycle in this point since makes no sense proceed anymore.
if( isSomethingGoWrongBeforeCallMethod ) {
// Calling this method has no effect and doesnt work.
// Final status is 204 (no content) instead 400 (bad reqeust) maybe because initial request lifecycle is gone and no more abortion is allowed and its time to execute resource method. Jax-rs doesnt know about it because it`s in cdi scope
jaxRsContext.getContainerRequestContext().abortWith(Response.status(Status.BAD_REQUEST).entity(entity).build());
return null;
}
else {
//can proceed the interceptor chaining
return invocationContext.proceed();
}
}
}
}
In fact this approach doesn't work too. Some more ideas or maybe i'm missing something ?
Thanks for sharing these details.
So if I understand you correctly, the injection issue is solved by using workaround I proposed, correct? At least it looks like you were able to get access to all the information you need in the interceptor. However, as mentioned earlier, you won't need such a workaround any more in future JAX-RS versions.
Regarding your other issue. So basically you want to abort the request processing from a CDI interceptor in certain situations, correct? I guess the issue here is that calling ContainerRequestContext#abortWith() doesn't work correctly outside a ContainerRequestFilter. But I'm pretty sure that the behavior is JAX-RS implementation specific.
However, there could be an easier way to abort the request processing from the interceptor. You could try to throw a WebApplicationException or one of its subclasses. This should actually work, as it will look like the exception was thrown from the resource method for the JAX-RS implementation.
So basically you can replace this:
jaxRsContext.getContainerRequestContext().abortWith(
Response.status(Status.BAD_REQUEST).entity(entity).build()
);
return null;
With something like this:
throw new WebApplicationException(
Response.status(Status.BAD_REQUEST).entity(entity).build()
);
You could try to throw a
WebApplicationExceptionor one of its subclasses. This should actually work, as it will look like the exception was thrown from the resource method for the JAX-RS implementation.
I assume that might work on some or even all JAX-RS implementations, but indeed the specification does not guarantee that (it restricts its guarantees to resource methods). So this might be one point where JAX-RS should get extended for better CDI integration. For example, we could clearly specify that abortWith and WebApplicationException MUST be supported at any time.
throw new WebApplicationException(
Response.status(Status.BAD_REQUEST).entity(entity).build()
);
Seems works now throwing WebApplicationException with response as argument.
So, resuming issue for future notes:
Maybe clearly specify that abortWith and WebApplicationException MUST be supported at any time.
Better integration with CDI and JAX-RS context objects.
Thank you so much guys for help, time and discussion. I`m very thankful.
Most helpful comment
Seems works now throwing WebApplicationException with response as argument.
So, resuming issue for future notes:
Thank you so much guys for help, time and discussion. I`m very thankful.