Creating and injecting a per request scoped variable - java

I would like to have a variable that follows along the full lifecycle of a request in java EE.
For example it could be for a logging function, so that I can filter all log entries by request.
The key part that I want to get at is that it must be relatively easy to implement in an already existing application so if possible some sort of dependency injection that gets the variable related to the specific request.
I've tried injectiong a #RequestScoped variable, but it doesn't work since it is only scoped to the container. I would need to be able to inject the same object to different containers. Is this at all possible?
EDIT: I want something along the lines of this:
#RequestScoped
public class RequestVariables {
public String id;
}
#Stateless
public class Logger {
#Inject
private RequestVariables requestVariables;
public void log(String message) {
System.out.println(requestVariables.id + ":" + message);
}
}
#Stateless
public class Service {
#Inject
private Logger logger;
#Inject
private RequestVariables requestVariables;
public void save(String data) {
logger.log("Save");
session.save(data + requestVariables.id); //Maybe add request parameter to save aswell
}
}
public class API {
#Inject
private Service service;
#Inject
private Logger logger;
#Inject
private RequestVariables requestVariables;
#Path("/1")
#GET
public Response get(#QueryParam("data") String data) {
requestVariables.id = UUID.randomUUID().toString()
service.save(data);
logger.log("Get");
return Response.status(204).build();
}
}
Currently this is what I have experimented with:
#RequestScoped
public class RequestScope {
private int test = 0;
public RequestScope(int test) {
this.test = test;
}
public RequestScope(){}
public int getTest() {
return test;
}
public void setTest(int test) {
this.test = test;
}
}
#Provider
public class RequestScopeFilter implements ContainerRequestFilter {
#Inject
private javax.inject.Provider<RequestScope> requestScopeProvider;
#Context
private HttpServletRequest request;
#Override
public void filter(ContainerRequestContext requestContext) throws IOException {
requestScopeProvider.get().setTest(42);
request.setAttribute("test", "superTest");
}
}
#Stateless
#TransactionManagement(TransactionManagementType.BEAN)
#TransactionAttribute(value=TransactionAttributeType.NOT_SUPPORTED)
public class Service {
#Context
private HttpServletRequest httpServletRequest;
#Inject
private Provider<RequestScope> requestScopeProvider;
public void test() {
RequestScope scope = requestScopeProvider.get();
String test = (String)httpServletRequest.getAttribute("test");
}
}
So when I get the scope from my service then it is a new object with test set to 0, and then it throws an NPE since httpServletRequest is null

option #1
Implement an Interceptor and set the request id as HttpServletRequest attribute:
#AroundInvoke
public Object setRequestId(InvocationContext ic) throws Exception {
HttpServletRequest request = [..] // getHttpServletRequest(ic);
request.setAttribute("request-id", UUID.randomUUID().toString());
return ic.proceed();
}
Then use HttpServletRequest everywhere you need it
#Context
private HttpServletRequest httpRequest;
option #2
If want just to filter your logs by an unique id, you can configure your Logger to print the thread name: [%t]
Example: Log4j PatternLayout
option #3
Use a custom java bean to encapsulate the request data (query param, request id etc.) and pass this bean across your application services.
public class API {
#Inject
private Service service;
#Path("/1")
#GET
public Response get(MyCustomRequestBean data) {
service.doSomejob(data);
return Response.status(204).build();
}
}
Set the request id and query param in ParamConverter:
Jax-RS ParamConverter - ParamConverterProvider method return type mismatch

You can inject a provider in your service:
#Inject
Provider<RequestVariables> vars
And then call get () to get the instance. If you try to get () in a thread outside a request scope context you'll get an exception. I would however try to structure in a way that would not allow this to happen

A solution that I found is to use ThreadLocal variables. It seems rather dirty, but it works since each request is executed on it's own thread(as far as I am aware). So this is what I got:
public class RequestScope {
private static final ThreadLocal<String> id = ThreadLocal.withInitial(() -> UUID.randomUUID().toString());
public static String get() {
return id.get();
}
}
With that I can also easily exchange the ThreadLocal to return something more specific if so desired.
And I can get the variables from pretty much anywhere, assuming that the request is not starting a different thread

Related

Spring Rest Set Custom Object in Context

I am using Spring Rest for creating Rest APIs. For authentication, I have created a filter that extends from OncePerRequestFilter, this filter check if a valid token is present in the header. I have to set some custom object information in Spring context. So that I can retrieve it in my Controller classes. Something like:
AuthenticationFilter extends OncePerRequestFilter {
// validation goes here
requestContext.setSecurityContext(new SecurityContext() {
public Principal getUserPrincipal() {
return new UserInfo("", token, userID, userType);
}
}
});
Don't create custom filters for authentications. User security context instead it's pretty powerful.
I don't know about any option how put custom object into spring context. But I can show you how to create your custom context in spring framework and then use it as any other context. Of course you will be able to put custom objects into it.
PSEUDO CODE:
1.)
Create custom object which you want to have in context:
public class UserInfo implements Serializable {
private String id;
private String email;
//Getters and setters
}
2.)
Create custom context:
#Service
public class UserInfoContext {
private static final ThreadLocal<UserInfo> userInfoThreadLocal = new InheritableThreadLocal<>();
public void setUserInfo(UserInfo userInfo) {
UserInfoContext.userInfoThreadLocal.set(userInfo);
}
public UserInfo getUserInfo() {
return UserInfoContext.userInfoThreadLocal.get();
}
public void clearContext() {
UserInfoContext.userInfoThreadLocal.remove();
}
}
3.)
Create custom interceptor for initializing custom object inside custom context
#Service
public class UserInfoInterceptor implements HandlerInterceptor {
private UserInfoContext userInfoContext;
#Autowired
public UserInfoInterceptor(UserInfoContext userInfoContext) {
this.userInfoContext = userInfoContext;
}
#Override
public boolean preHandle(HttpServletRequest requestServlet, HttpServletResponse responseServlet, Object handler) throws Exception {
// Call userInfoContext.setUserInfo() with custom data.
return true;
}
#Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception exception) throws Exception {
userInfoContext.clearContext();
}
}
Thanks to ThreadLocal you are able to save custom information about current user.

#Required not working in spring?

Am I using this in a wrong way, or #Required has no effect?
Here I annotated bean Non setter with #Required, but app starts normally and prints "null".
I would expect app to report an error and not start properly.
#SpringBootApplication
public class ReqNotWorking {
public static void main(String[] args) {
SpringApplication.run(ReqNotWorking.class, args);
}
class Non{
private String req;
public String getReq() {
return req;
}
#Required
public void setReq(String req) {
this.req = req;
}
}
#Bean
public Non non(){
Non non = new Non();
//non.setReq("error");
return non;
}
#Bean
public CommandLineRunner commandLineRunner(Non non){
return args -> {
System.out.println(non.getReq());
};
}
}
You're instantiating the class Non via the new keyword so Spring has no way to apply its post-processors (RequiredAnnotationBeanPostProcessor, specifically) and verify the value of the field.
As a side-note, the #Required annotation probably doesn't make sense in a Java-configured Spring application, where all beans are implicitly required, unless otherwise noted.
You didn't describe where the "req" string is supposed to come from, but if it's available at startup time, you could leverage the #Value mechanism:
#Component
static class Non {
#Value("${req.value}")
private String req;
public String getReq() {
return req;
}
}

How to embed request level objects to #Context

In embedded Jersey I can register a Binder to put in some resources that I can eventually access using #Context
However, those things I put in are more global and not on a per request level. I do know I can do it with some property mapping, but I would rather do it through #Context with class like Response foo(#Context HttpServletRequest)
I tried the setRequestScopedInitializer() but it does not put them in as expected and following their example with the Ref gives me a null pointer exception
Is there any way of doing this?
Here's how I eventually did it, but I don't like that I used a named property
RoutingContext was the type I wanted to inject
public class RoutingContextFactory implements
Supplier<RoutingContext> {
#Inject
private ContainerRequest request;
#Override
public RoutingContext get() {
return (RoutingContext) request.getProperty(RoutingContext.class.getName());
}
}
My binder
public class MyBinder extends AbstractBinder {
#Override
protected void configure() {
bindFactory(RoutingContextFactory.class)
.to(RoutingContext.class)
.proxy(true)
.proxyForSameScope(false)
.in(RequestScoped.class);
}
}
Initialized by
final ResourceConfig resourceConfig = ResourceConfig.forApplicationClass(applicationClass);
resourceConfig.register(new MyBinder());
Loaded by
final ContainerRequest request = new ContainerRequest(...
request.setProperty(RoutingContext.class.getName(), routingContext);
Used by
#GET
#Produces(MediaType.TEXT_PLAIN)
public String hello(
#Context final RoutingContext routingContext) {
return "Hello"
+ routingContext;
}
I still wish there was a way for me to just go request.register(routingContext). I opened up https://github.com/jersey/jersey/issues/3682 for this.

SPRING #RestController: Passing Cookie to a Service from Every Method

I have a #RestController in which every method needs to pass a (WebSSO) cookie down to a service. The service in turn uses the cookie for authentication. I am autowiring the service bean in controller. The service has a setter setCredentials(String webSSOCookie). One easy way is to call this setter in every method. I would like to do it better way; for instance using HandlerInterceptor. However the HandlerInterceptor does not have access to the controller (and hence its members) - am I right?
In jersey I could use filter. How do I achieve it in SPRING?
#RestController
#RequestMapping("/documents")
public class ECMRestController {
#Autowired
public ECMService ecmService;
#RequestMapping(value="/{documentId}", method=RequestMethod.DELETE)
public void deleteDocument(#RequestParam("documentId") String documentId) throws IllegalArgumentException, HttpClientErrorException {
// I could get and pass the cookie to ecmService in every method.
// ecmService.setCredentials(webSSOCookieObtainedfromRequest);
// However I don't want to do it that way.
ecmService.deleteDocument(documentId);
}
// Other REST Methods that need to pass the cookie in the same way.
}
You can request the SecurityContextHolder to query the current Authentication that you had customized in a filter.
MyCustomAuth auth = (MyCustomAuth) SecurityContextHolder.getContext().getAuthentication();
auth.getCookie();
or you can just use ThreadLocal in a context that can be retrieve from anywhere:
public class CookieContext {
private static final ThreadLocal<Cookie> COOKIE = new ThreadLocal<>();
private static final CookieContext INSTANCE = new CookieContext();
public void setCookie(Cookie value) {
COOKIE.set(value);
}
public Cookie getCookie() {
return COOKIE.get();
}
public static CookieContext getContext() {
return INSTANCE;
}
}
public class CookieInterceptor extends HandlerInterceptorAdapter {
public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
Object handler) throws Exception {
CookieContext context = CookieContext.getContext();
context.setCookie(request.getCookies()[0]);
}
}

Custom provider can't inject into filter

I have a POJO that I'd like to inject into resources and filters:
public final class MyObject { }
I implemented a custom provider for it:
#Provider
public final class MyProvider
extends AbstractHttpContextInjectable<MyObject>
implements InjectableProvider<Context, Type> {
#Context private HttpServletRequest request;
#Override
public Injectable<MyObject> getInjectable(
ComponentContext componentContext,
Context annotation,
Type type
) {
if (type.equals(MyObject.class)) {
return this;
}
return null;
}
#Override
public ComponentScope getScope() {
return ComponentScope.PerRequest;
}
#Override
public MyObject getValue(HttpContext httpContext) {
//in reality, use session info from injected request to create MyObject
return new MyObject();
}
}
The object is successfully injected into my resource:
#Path("/test")
#ResourceFilters(MyFilter.class)
public final class MyResource {
#Context private HttpServletRequest request;
#Context private MyObject myObject;
#GET
public String execute() {
System.out.println(request != null); //true
System.out.println(myObject != null); //true
return "data";
}
}
But Jersey fails to inject it into my filter:
public final class MyFilter implements ResourceFilter {
#Context private HttpServletRequest request;
#Context private MyObject myObject;
#Override
public ContainerRequestFilter getRequestFilter() {
return new ContainerRequestFilter() {
#Override
public ContainerRequest filter(ContainerRequest containerRequest) {
System.out.println(request != null); //true
System.out.println(myObject != null); //false
return containerRequest;
}
};
}
#Override
public ContainerResponseFilter getResponseFilter() {
return null;
}
}
I'm guessing the difference has to do with the fact that in MyFilter the injection is done using proxies that defer to thread-local instances - this is because the fields annotated with #Context are declared in the outer class, which is instantiated once, but they are used to inject objects on a per-request basis. When I step through filter during debugging, I can see that MyFilter.request points to a proxy wrapping an instance of com.sun.jersey.server.impl.container.servlet.ThreadLocalInvoker.
What is my custom provider (or implementation otherwise) missing that it needs to do custom injection into my filter?
Note that I'm currently stuck with Jersey 1.1.4.1 (sorry).
EDIT: Using Jersey 1.17, I get an exception at startup instead:
SEVERE: Missing dependency for field: private mypackage.MyObject mypackage.MyFilter.myObject
I found a workaround using the Providers injectable interface from JSR-311. First, I had to make my provider implement ContextResolver:
#Provider
public final class MyProvider
extends AbstractHttpContextInjectable<MyObject>
implements InjectableProvider<Context, Type>, ContextResolver<MyObject> {
...
#Override
public MyObject getContext(Class<?> type) {
//in reality, using the same logic as before
return new MyObject();
}
}
Then I injected a Providers instance into my filter instead. When filter is called, I use it to look up the ContextResolver for MyObject and retrieve it dynamically:
public final class MyFilter implements ResourceFilter {
#Context private HttpServletRequest request;
#Context private Providers providers;
#Override
public ContainerRequestFilter getRequestFilter() {
return new ContainerRequestFilter() {
#Override
public ContainerRequest filter(ContainerRequest containerRequest) {
final ContextResolver<MyObject> myObjectResolver =
providers.getContextResolver(MyObject.class, null);
final MyObject myObject =
myObjectResolver.getContext(MyObject.class);
System.out.println(request != null); //true
System.out.println(myObject != null); //true
return containerRequest;
}
};
}
...
}
Credit goes to this answer for tipping me off about Providers. The solution works, but it isn't a pretty one. I'd still like to inject MyObject anywhere and just have it work, like HttpServletRequest - and I'd like to know what it is my provider's missing that it needs to make that happen.
I came across this question trying to achieve the same goal (i.e. inject something provided by a custom Provider implementation) and found that custom injection works nicely if you go by way of a filter factory.
So instead of putting the #Context MyClass myObj annotation into the Filter class, place an annotated field of that type in the filter factory responsible for creating the filter and have the factory pass "myObj" as a regular parameter.
I'm not sure this will help in your case and I haven't investigated the implications of using this with a per-request provider (mine is singleton-scoped), so YMMV.

Categories