How can I set up an aop MethodInterceptor to work with Jersey resources?
Here is what I've tried, following this documentation:
Step 1 - InterceptionService
public class MyInterceptionService implements InterceptionService
{
private final Provider<AuthFilter> authFilterProvider;
#Inject
public HK2MethodInterceptionService(Provider<AuthFilter> authFilterProvider)
{
this.authFilterProvider = authFilterProvider;
}
/**
* Match any class.
*/
#Override
public Filter getDescriptorFilter()
{
return BuilderHelper.allFilter();
}
/**
* Intercept all Jersey resource methods for security.
*/
#Override
#Nullable
public List<MethodInterceptor> getMethodInterceptors(final Method method)
{
// don't intercept methods with PermitAll
if (method.isAnnotationPresent(PermitAll.class))
{
return null;
}
return Collections.singletonList(new MethodInterceptor()
{
#Override
public Object invoke(MethodInvocation methodInvocation) throws Throwable
{
if (!authFilterProvider.get().isAllowed(method))
{
throw new ForbiddenException();
}
return methodInvocation.proceed();
}
});
}
/**
* No constructor interception.
*/
#Override
#Nullable
public List<ConstructorInterceptor> getConstructorInterceptors(Constructor<?> constructor)
{
return null;
}
}
Step 2 - Register the service
public class MyResourceConfig extends ResourceConfig
{
public MyResourceConfig()
{
packages("package.with.my.resources");
// UPDATE: answer is remove this line
register(MyInterceptionService.class);
register(new AbstractBinder()
{
#Override
protected void configure()
{
bind(AuthFilter.class).to(AuthFilter.class).in(Singleton.class);
// UPDATE: answer is add the following line
// bind(MyInterceptionService.class).to(InterceptionService.class).in(Singleton.class);
}
});
}
}
However this doesn't appear to work because none of my resource methods are being intercepted. Could this be because I use #ManagedAsync with all of my resources? Any ideas?
Also, please do not suggest a ContainerRequestFilter. See this question for why I can't use one to handle security.
I think that rather than calling register(MyInterceptionService.class) you might want to instead add into your configure() statement:
bind(MyInterceptionService.class).to(InterceptionService.class).in(Singleton.class)
I am not sure it will work as I have not tried it myself so your results may vary lol
Related
I have a Java Spring Service with a RestController that calls an async method:
#RestController
public class SomeController {
#Autowired
//this is the service that contains the async-method
OtherService otherService;
#GetMapping
public void someFunctionWithinTheMainRequestThread() {
otherService.asyncMethod(RequestContextHolder.getRequestAttributes());
}
}
That async method needs to use the RequestContextAttributes because it is building Links with linkTo(...). The problem is that no matter how I pass the RequestAttributes to the method, I always get the error
java.lang.IllegalStateException: Cannot ask for request attribute - request is not active anymore!
This is the annotation on the async method:
public class OtherService {
#Async
#Transactional(readOnly = true)
public void asyncMethod(RequestAttributes context) {
RequestContextHolder.setRequestAttributes(context);
//doing a lot of stuff that takes a while
linkTo(methodOn(...)) //-> here the error occurs
}
What I tried:
Passing RequestAttributes manually as a Parameter (as seen in the code-snippets above)
Using the context-aware-pool executor described in this answer: How to enable request scope in async task executor - which basically seems to do the same as if I pass the context as a variable only that is is configured globally
Updating the servlet config and setting ThreadContextInheritable to true
Assigning the RequestAttributes to a final variable to try to get a copy of the original object which is marked as inactive by the main thread
No matter what I do, the request always seems to finish before my async method and I apparently never have a deep copy of the Attributes so they always get marked as inactive by the main thread before the async method is finished and then I can't use them anymore -> at least that is my understanding of the error.
I just want to be able to get the requestAttributes needed for the linkTo method in my async method even after the main thread finished the request, can someone point me in the right direction?
I found a solution that does work and removes the error. Since I don't think this is really clean I am hoping for more answers but in case it helps someone:
First I added this class. It creates a custom and very simple RequestAttributes-Implementation that enables us to keep the Attributes active for longer than they normally would be:
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;
public class AsyncRequestScopeAttr extends ServletRequestAttributes {
private Map<String, Object> requestAttributeMap = new HashMap<>();
public AsyncRequestScopeAttr(HttpServletRequest request) {
super(request);
}
#Override
public void requestCompleted() {
//keep the request active, normally here this.requestActive would be set to false -> we do that in the completeRequest()-method which is manually called after the async method is done
}
/**
* This method should be called after your async method is finished. Normally it is called when the
* request completes but since our async method can run longer we call it manually afterwards
*/
public void completeRequest() {
super.requestCompleted();
}
#Override
public Object getAttribute(String name, int scope) {
if(scope== RequestAttributes.SCOPE_REQUEST) {
return this.requestAttributeMap.get(name);
}
return null;
}
#Override
public void setAttribute(String name, Object value, int scope) {
if(scope== RequestAttributes.SCOPE_REQUEST){
this.requestAttributeMap.put(name, value);
}
}
#Override
public void removeAttribute(String name, int scope) {
if(scope== RequestAttributes.SCOPE_REQUEST) {
this.requestAttributeMap.remove(name);
}
}
#Override
public String[] getAttributeNames(int scope) {
if(scope== RequestAttributes.SCOPE_REQUEST) {
return this.requestAttributeMap.keySet().toArray(new String[0]);
}
return new String[0];
}
#Override
public void registerDestructionCallback(String name, Runnable callback, int scope) {
// Not Supported
}
#Override
public Object resolveReference(String key) {
// Not supported
return null;
}
#Override
public String getSessionId() {
return null;
}
#Override
public Object getSessionMutex() {
return null;
}
#Override
protected void updateAccessedSessionAttributes() {
}
}
Then in the RestController before the async method is called:
#Autowired
//this is the service that contains the async-method
OtherService otherService;
public void someFunctionWithinTheMainRequestThread(){
otherService.asyncMethod(getIndependentRequestAttributesForAsync());
}
private RequestAttributes getIndependentRequestAttributesForAsync(){
RequestAttributes requestAttributes = new AsyncRequestScopeAttr(((ServletRequestAttributes)RequestContextHolder.getRequestAttributes()).getRequest());
for (String attributeName : RequestContextHolder.getRequestAttributes().getAttributeNames(RequestAttributes.SCOPE_REQUEST)) {
RequestContextHolder.getRequestAttributes().setAttribute(attributeName, RequestContextHolder.getRequestAttributes().getAttribute(attributeName, RequestAttributes.SCOPE_REQUEST), RequestAttributes.SCOPE_REQUEST);
}
return requestAttributes;
}
And then in the async function:
public class OtherService {
#Async
#Transactional(readOnly=true)
public void asyncMethod(RequestAttributes context) {
//set the RequestAttributes for this thread
RequestContextHolder.setRequestAttributes(context);
// do your thing .... linkTo() etc.
//cleanup
((AsyncRequestScopeAttr)context).completeRequest();
RequestContextHolder.resetRequestAttributes();
}
}
again a small problem by understanding "how tapestry works".
I've got a Tapestry component (in this case a value encoder):
public class EditionEncoder implements ValueEncoder<Edition>, ValueEncoderFactory<Edition> {
#Inject
private IEditionManager editionDao;
public EditionEncoder(IEditionManager editionDao) {
this.editionManager = editionDao;
}
#Override
public String toClient(Edition value) {
if(value == null) {
return "";
}
return value.getName();
}
#Override
public Edition toValue(String clientValue) {
if(clientValue.equals("")) {
return null;
}
return editionManager.getEditionByName(clientValue);
}
#Override
public ValueEncoder<Edition> create(Class<Edition> type) {
return this;
}
}
Injecting the the Manager is not working, because the Encoder is created within a page like that:
public void create() {
editionEncoder = new EditionEncoder();
}
casued by this, i'm forced to use this ugly solution:
#Inject
private IEditionManager editionmanager;
editionEncoder = new EditionEncoder(editionManager);
Is there a better way to inject components during runtime or is there a better solution in general for it?
Thanks for your help in advance,
As soon as you use "new" then tapestry-ioc is not involved in object creation and can't inject. You should inject everything and never use "new" for singleton services. This is true for all ioc containers, not just tapestry-ioc.
Also if you put #Inject on a field then you don't also need a constructor to set it. Do one or the other, never both.
You should do something like this:
public class MyAppModule {
public void bind(ServiceBinder binder) {
binder.bind(EditionEncoder.class);
}
}
Then in your page/component/service
#Inject EditionEncoder editionEncoder;
If you wanted to put your own instantiated objects in there you can do
public class MyServiceModule {
public void bind(ServiceBinder binder) {
binder.bind(Service1.class, Service1Impl.class);
binder.bind(Service2.class, Service2Impl.class);
}
public SomeService buildSomeService(Service1 service1, Service2 service2, #AutoBuild Service3Impl service3) {
Date someDate = new Date();
return new SomeServiceImpl(service1, service2, service3, someDate);
}
}
I use GWTP and restyGWT. I would like to use placeManager in restyGWT DispatcherCallback, when my rest server will answer with 401 unauthorised I would like to redirect application to login page, that User could apply credentials and retried his request.
To do this I have to somehow get instance of PlaceManager (from gwtp framework). I cannot use #Inject annotation, cause I have manuall call to constructor as follow:
public class ForbiddenDispatcherFilter implements DispatcherFilter {
#Override
public boolean filter(Method method, RequestBuilder builder) {
builder.setCallback(new ForbiddenDispatcherCallback(method));
return true;
}
}
public class ForbiddenDispatcherCallback implements RequestCallback {
protected RequestCallback requestCallback;
public ForbiddenDispatcherCallback(Method method) {
this.requestCallback = method.builder.getCallback();
}
#Override
public void onResponseReceived(Request request, Response response) {
if (response.getStatusCode() == Response.SC_FORBIDDEN || response.getStatusCode() == Response.SC_UNAUTHORIZED) {
// make a hard redirect to login page
// TODO change redirect to GWTP native
Window.Location.assign("#login");
// PlaceRequest placeRequest = new
// PlaceRequest.Builder(placeManager.getCurrentPlaceRequest()).nameToken(Routing.Url.login).build();
// placeManager.revealPlace(placeRequest);
} else {
requestCallback.onResponseReceived(request, response);
}
}
public class RestyDispatcher extends DefaultFilterawareDispatcher {
public RestyDispatcher() {
addFilter(new ForbiddenDispatcherFilter());
addFilter(new BasicAuthHeaderDispatcherFilter());
}
#Override
public Request send(Method method, RequestBuilder builder) throws RequestException {
return super.send(method, builder);
}
}
Please help.
Edit
public class ClientModule extends AbstractPresenterModule {
#Override
protected void configure() {
bind(RestyGwtConfig.class).asEagerSingleton();
install(new DefaultModule.Builder()//
.defaultPlace(Routing.HOME.url)//
.errorPlace(Routing.ERROR.url)//
.unauthorizedPlace(Routing.LOGIN.url)//
.tokenFormatter(RouteTokenFormatter.class).build());
install(new AppModule());
// install(new
// GinFactoryModuleBuilder().build(AssistedInjectionFactory.class));
bind(CurrentUser.class).in(Singleton.class);
bind(IsAdminGatekeeper.class).in(Singleton.class);
bind(UserLoginGatekeeper.class).in(Singleton.class);
// Google Analytics
// bindConstant().annotatedWith(GaAccount.class).to("UA-8319339-6");
// Load and inject CSS resources
bind(ResourceLoader.class).asEagerSingleton();
}
}
and:
public class RestyGwtConfig {
static {
// GWT.log("--> RestyGwtConfig -> setDispatcher");
Defaults.setDispatcher(new RestyDispatcher());
// GWT.log("--> RestyGwtConfig -> setServiceRoot");
Defaults.setServiceRoot(new Resource(GWT.getModuleBaseURL()).resolve(ServiceRouting.SERVICE_ROOT).getUri());
UserCredentials.INSTANCE.setUserName("ronan");
UserCredentials.INSTANCE.setPassword("password");
}
}
How and where do you create your ForbiddenDispatcherFilter ?
You could use guice's AssistedInjection to inject the PlaceManager into your ForbiddenDispatcherCallback.
public class ForbiddenDispatcherCallback implements RequestCallback {
protected RequestCallback requestCallback;
protected PlaceManager placeManager;
#Inject
public ForbiddenDispatcherCallback(PlaceManager placeManager, #Assisted Method method) {
this.placeManager = placeManager;
this.requestCallback = method.builder.getCallback();
}
}
You need to define an factory interface:
public interface AssistedInjectionFactory {
ForbiddenDispatcherCallback createForbiddenCallback(Method method);
}
In the configure method of your ClientModule you need to call:
install(new GinFactoryModuleBuilder().build(AssistedInjectionFactory.class));
Then you can instantiate your class this way:
public class ForbiddenDispatcherFilter implements DispatcherFilter {
AssistedInjectionFactory factory;
#Inject
public ForbiddenDispatcherFilter(AssistedInjectionFactory factory)
{
this.factory = factory;
}
#Override
public boolean filter(Method method, RequestBuilder builder) {
builder.setCallback(factory.AssistedInjectionFactory(method))
return true;
}
}
Of course this requires that you also inject the ForbiddenDispatcherFilter.
Edit:
You could try to pass the RestyDispatcher to the constructor of your RestyGWTConfig:
public class RestyGwtConfig {
#Inject
public RestyGwtConfig(RestyDispatcher dispatcher) {
Defaults.setDispatcher(dispatcher);
}
static {
// GWT.log("--> RestyGwtConfig -> setServiceRoot");
Defaults.setServiceRoot(new Resource(GWT.getModuleBaseURL()).resolve(ServiceRouting.SERVICE_ROOT).getUri());
UserCredentials.INSTANCE.setUserName("ronan");
UserCredentials.INSTANCE.setPassword("password");
}
}
The RestyDispatcher looks like this:
public class RestyDispatcher extends DefaultFilterawareDispatcher {
#Inject
public RestyDispatcher(ForbiddenDispatcherFilter filter) {
addFilter(filter);
addFilter(new BasicAuthHeaderDispatcherFilter());
}
#Override
public Request send(Method method, RequestBuilder builder) throws RequestException {
return super.send(method, builder);
}
}
So I am testing a simple Google Guice interceptor -
My Annotation -
#Retention(RetentionPolicy.RUNTIME) #Target(ElementType.METHOD)
public #interface AppOpsOperation {
}
My Interceptor
public class AppOpsOperationDecorator implements MethodInterceptor {
private ServiceCallStack callStack = null ;
#Inject
public void setServiceCallStack(ServiceCallStack stack ){
callStack = stack ;
}
#Override
public Object invoke(MethodInvocation arg0) throws Throwable {
// Retrieve the call stack
// exclude service population if caller service is the same service
// else push the current service onto top of stack
System.out.println("Intercepting method -- :: " + arg0.getMethod().getName());
System.out.println("On object - :: " + arg0.getThis().getClass().getName());
System.out.println("On accessible object - :: " + arg0.getStaticPart().getClass().getName());
return invocation.proceed();
}
}
And now my Service interface and method
public interface MockCalledService extends AppOpsService {
#AppOpsOperation
public String methodOneCalled(String some);
#AppOpsOperation
public String methodTwoCalled(String some);
}
public class MockCalledServiceImpl extends BaseAppOpsService implements MockCalledService {
#Override
#AppOpsOperation
public String methodOneCalled(String some) {
System.out.println("MockCalledServiceImpl.methodOneCalled()");
return this.getClass().getCanonicalName() + "methodOneCalled";
}
#Override
public String methodTwoCalled(String some) {
System.out.println("MockCalledServiceImpl.methodTwoCalled()");
return this.getClass().getCanonicalName() + "methodTwoCalled";
}
}
And my Guice test module
public class MockTestGuiceModule extends AbstractModule {
#Override
protected void configure() {
bind(ServiceCallStack.class).toInstance(new ServiceCallStack());
AppOpsOperationDecorator decorator = new AppOpsOperationDecorator() ;
requestInjection(decorator);
bindInterceptor(Matchers.any(), Matchers.annotatedWith(AppOpsOperation.class),
decorator);
bind(MockCalledService.class).toInstance(new MockCalledServiceImpl());
}
}
This interceptor doesn't execute when I run the test below -
public class AppOpsOperationDecoratorTest {
private Injector injector = null ;
#Before
public void init(){
injector = Guice.createInjector(new MockTestGuiceModule());
}
#Test
public void testDecoratorInvocation() {
MockCalledService called = injector.getInstance(MockCalledService.class);
called.methodOneCalled("Test String");
}
}
Can you please highlight what I am doing wrong ?
I am answering after finding the real reason. Its so simple that its really tricky.
Method interception only works if you bind the interface with the class and not an instance of this implementation.
so instead of bind(MockCalledService.class).toInstance(new MockCalledServiceImpl());
we should write bind(MockCalledService.class).to(MockCalledServiceImpl.class);
Seems instances are not proxied :(
I'm trying to implement a ContainerRequestFilter that does custom validation of a request's parameters. I need to look up the resource method that will be matched to the URI so that I can scrape custom annotations from the method's parameters.
Based on this answer I should be able to inject ExtendedUriInfo and then use it to match the method:
public final class MyRequestFilter implements ContainerRequestFilter {
#Context private ExtendedUriInfo uriInfo;
#Override
public ContainerRequest filter(ContainerRequest containerRequest) {
System.out.println(uriInfo.getMatchedMethod());
return containerRequest;
}
}
But getMatchedMethod apparently returns null, all the way up until the method is actually invoked (at which point it's too late for me to do validation).
How can I retrieve the Method that will be matched to a given URI, before the resource method is invoked?
For those interested, I'm trying to roll my own required parameter validation, as described in JERSEY-351.
Actually, you should try to inject ResourceInfo into your custom request filter. I have tried it with RESTEasy and it works there. The advantage is that you code against the JSR interfaces and not the Jersey implementation.
public class MyFilter implements ContainerRequestFilter
{
#Context
private ResourceInfo resourceInfo;
#Override
public void filter(ContainerRequestContext requestContext)
throws IOException
{
Method theMethod = resourceInfo.getResourceMethod();
return;
}
}
I figured out how to solve my problem using only Jersey. There's apparently no way to match a request's URI to the method that will be matched before that method is invoked, at least in Jersey 1.x. However, I was able to use a ResourceFilterFactory to create a ResourceFilter for each individual resource method - that way these filters can know about the destination method ahead of time.
Here's my solution, including the validation for required query params (uses Guava and JSR 305):
public final class ValidationFilterFactory implements ResourceFilterFactory {
#Override
public List<ResourceFilter> create(AbstractMethod abstractMethod) {
//keep track of required query param names
final ImmutableSet.Builder<String> requiredQueryParamsBuilder =
ImmutableSet.builder();
//get the list of params from the resource method
final ImmutableList<Parameter> params =
Invokable.from(abstractMethod.getMethod()).getParameters();
for (Parameter param : params) {
//if the param isn't marked as #Nullable,
if (!param.isAnnotationPresent(Nullable.class)) {
//try getting the #QueryParam value
#Nullable final QueryParam queryParam =
param.getAnnotation(QueryParam.class);
//if it's present, add its value to the set
if (queryParam != null) {
requiredQueryParamsBuilder.add(queryParam.value());
}
}
}
//return the new validation filter for this resource method
return Collections.<ResourceFilter>singletonList(
new ValidationFilter(requiredQueryParamsBuilder.build())
);
}
private static final class ValidationFilter implements ResourceFilter {
final ImmutableSet<String> requiredQueryParams;
private ValidationFilter(ImmutableSet<String> requiredQueryParams) {
this.requiredQueryParams = requiredQueryParams;
}
#Override
public ContainerRequestFilter getRequestFilter() {
return new ContainerRequestFilter() {
#Override
public ContainerRequest filter(ContainerRequest request) {
final Collection<String> missingRequiredParams =
Sets.difference(
requiredQueryParams,
request.getQueryParameters().keySet()
);
if (!missingRequiredParams.isEmpty()) {
final String message =
"Required query params missing: " +
Joiner.on(", ").join(missingRequiredParams);
final Response response = Response
.status(Status.BAD_REQUEST)
.entity(message)
.build();
throw new WebApplicationException(response);
}
return request;
}
};
}
#Override
public ContainerResponseFilter getResponseFilter() {
return null;
}
}
}
And the ResourceFilterFactory is registered with Jersey as an init param of the servlet in web.xml:
<init-param>
<param-name>com.sun.jersey.spi.container.ResourceFilters</param-name>
<param-value>my.package.name.ValidationFilterFactory</param-value>
</init-param>
At startup, ValidationFilterFactory.create gets called for each resource method detected by Jersey.
Credit goes to this post for getting me on the right track: How can I get resource annotations in a Jersey ContainerResponseFilter
I know you're looking for a Jersey only solution but here's a Guice approach that should get things working:
public class Config extends GuiceServletContextListener {
#Override
protected Injector getInjector() {
return Guice.createInjector(
new JerseyServletModule() {
#Override
protected void configureServlets() {
bindInterceptor(Matchers.inSubpackage("org.example"), Matchers.any(), new ValidationInterceptor());
bind(Service.class);
Map<String, String> params = Maps.newHashMap();
params.put(PackagesResourceConfig.PROPERTY_PACKAGES, "org.example");
serve("/*").with(GuiceContainer.class, params);
}
});
}
public static class ValidationInterceptor implements MethodInterceptor {
public Object invoke(MethodInvocation method) throws Throwable {
System.out.println("Validating: " + method.getMethod());
return method.proceed();
}
}
}
#Path("/")
public class Service {
#GET
#Path("service")
#Produces({MediaType.TEXT_PLAIN})
public String service(#QueryParam("name") String name) {
return "Service " + name;
}
}
EDIT: A performance comparison:
public class AopPerformanceTest {
#Test
public void testAopPerformance() {
Service service = Guice.createInjector(
new AbstractModule() {
#Override
protected void configure() { bindInterceptor(Matchers.inSubpackage("org.example"), Matchers.any(), new ValidationInterceptor()); }
}).getInstance(Service.class);
System.out.println("Total time with AOP: " + timeService(service) + "ns");
}
#Test
public void testNonAopPerformance() {
System.out.println("Total time without AOP: " + timeService(new Service()) + "ns");
}
public long timeService(Service service) {
long sum = 0L;
long iterations = 1000000L;
for (int i = 0; i < iterations; i++) {
long start = System.nanoTime();
service.service(null);
sum += (System.nanoTime() - start);
}
return sum / iterations;
}
}
In resteasy-jaxrs-3.0.5, you can retrieve a ResourceMethodInvoker representing the matched resource method from ContainerRequestContext.getProperty() inside a ContainerRequestFilter:
import org.jboss.resteasy.core.ResourceMethodInvoker;
public class MyRequestFilter implements ContainerRequestFilter
{
public void filter(ContainerRequestContext request) throws IOException
{
String propName = "org.jboss.resteasy.core.ResourceMethodInvoker";
ResourceMethodInvoker invoker = (ResourceMethodInvoker)request.getProperty();
invoker.getMethod().getParameterTypes()....
}
}