I'm looking for a way in order to inject a #RequestScoped custom class into my #Stateless JAX-RS endpoint:
I want each time the application receives a request my custom class is injected in my JAX-RS endpoint.
Custom class:
#RequestScoped
public class CurrentTransaction {
private String user;
private String token;
#PersistenceContext(name="mysql")
protected EntityManager em;
#Inject HttpServletRequest request;
public CurrentTransaction() {
this.user = request.getHeader("user");
this.token = request.getHeader("token");
}
//getters and setters ...
}
So, I declare my CurrentTransaction class as #RequestScoped in order to be initialized each time a request is received.
In order to do this, I need to access to HttpServletResquest in order to get header parameters.
JAX-RS endpoint:
#Stateless
#Path("/areas")
public class AreasEndpoint {
#PersistenceContext(unitName = "mysql")
protected EntityManager em;
#Inject
protected CurrentTransaction current_user_service;
#POST
#Path("postman")
#Consumes(MediaType.APPLICATION_JSON)
#Produces(MediaType.APPLICATION_JSON)
#Authentication
public Response create(AreaRequest request) {
if (this.current_user_service.getUser() == null) {
System.out.println("Go!!!");
return Response.status(Status.FORBIDDEN).build();
} else {
System.out.println("---- user: " + this.current_user_service.getUser());
System.out.println("---- token: " + this.current_user_service.getToken());
}
}
// ...
}
CDI arrive to perform the constructor of CurrentTransaction class. However, HttpServletRequest request field is not initialized (injected).
What am I doing wrong?
A late answer on this one--maybe useful to other readers: dependency injection in CDI is done in the following order:
the constructor is invoked
fields are injected
#PostConstruct annotated method is invoked
The last point is where you want to step in for further initialization that needs access on the injected fields:
#Inject HttpServletRequest request;
public CurrentTransaction() {
// field injection has not yet taken place here
}
#PostConstruct
public void init() {
// the injected request is now available
this.user = request.getHeader("user");
this.token = request.getHeader("token");
}
Related
Question about RestEASY 3.6.2 on JBoss 7.1.0.
I have the following working JaxRS service:
#Path("my-service")
public class MyResource {
#Context
HttpServletRequest request;
#GET
#Path("get-stuff")
#Produces(MediaType.APPLICATION_JSON)
public Response doStuff() {
MyCustomContext customContext = new MyCustomContext(request);
// ... use the customContext here.
}
}
With the way this is currently set up, every rest method requires a MyCustomContext customContext = new MyCustomContext(request);. That is annoying.
Is there some way to inject the MyCustomContext?
#Path("my-service")
public class MyResource {
#Context
MyCustomContext context;
#GET
#Path("get-stuff")
#Produces(MediaType.APPLICATION_JSON)
public Response doStuff() {
// ... use the customContext here.
}
}
#Producer // ???
public class MyCustomContext {
#Context
HttpServletRequest request;
public MyCustomContext() {
// construct with request object.
}
}
I have found a ton of links hinted towards a way to do this, but I am coming up empty.
I do not know any way of injecting a custom class instance/bean with #Context. I would like to outline alternative approaches that depend on the concrete requirement.
A) No injection needed at all.
Make your custom context a class member of your JAX-RS resource class (instead of local variable within each method). Utilize #PostConstruct to instantiate your custom context once the container has created an initialized your resource class instance. The resource class must be a CDI-bean with request scope for this to work.
#Path("my-service")
#RequestScoped // CDI-bean with request scope only
public class MyResource {
#Context
private HttpServletRequest request;
private MyCustomContext customContext;
#PostConstruct
public void initialize() {
this.customContext = new MyCustomContext(this.request); // request is initialized by the container already at this point
}
#GET
#Path("get-stuff")
#Produces(MediaType.APPLICATION_JSON)
public Response doStuff() {
// ... use the customContext here.
}
}
B) Your custom context requires an HttpServletRequest instance only
Beside JAX-RS via #Context, CDI also provides a predefined bean for HttpServletRequest via #Inject. You can make your custom context a CDI-bean also and inject that predefined CDI-bean. Afterwards you are able to inject your custom context into your JAX-RS resource (regardless of whether it is an EJB or CDI-bean).
#Dependent // make your custom context a CDI-bean
public class MyCustomContext {
#Inject // inject predefined CDI-bean
private HttpServletRequest request;
}
#Path("my-service")
#RequestScoped // either CDI-bean
//#Stateless // or EJB
public class MyResource {
#Inject // inject custom context via CDI
private MyCustomContext customContext;
#GET
#Path("get-stuff")
#Produces(MediaType.APPLICATION_JSON)
public Response doStuff() {
// ... use the customContext here.
}
}
C) Your custom context requires an instance exclusiveley provided via provider specific #Context e.g. Request
If you inject an instance via #Context into your non JAX-RS custom context CDI-bean it will be null. You need some mechanism to provide the injected instance from your JAX-RS resource. Making CDI responsible for the injection via #Inject on your custom context and adding a producer method via #Produces to your JAX-RS resource will do the job.
#Dependent // make your custom context a CDI-bean
public class MyCustomContext {
//#Context // in non JAX-RS context the instance will be null
#Inject // instead inject the JAX-RS context instance via CDI
private Request request;
}
#Path("my-service")
#RequestScoped // either CDI-bean
//#Stateless // or EJB
public class MyResource {
#Context // in JAX-RS context the instance will not be null
private Request request;
#Inject
private MyCustomContext customContext;
#Produces // provide the JAX-RS context instance for injection via CDI
#RequestScoped
public Request getContextRequest() {
return this.request;
}
#GET
#Path("get-stuff")
#Produces(MediaType.APPLICATION_JSON)
public Response doStuff() {
// ... use the customContext here.
}
}
I have a spring application and want to create a bean at runtime per request to inject it into another class, just like #Producer for CDI.
My bean is just a simple POJO:
public class UserDetails {
private String name;
// getter / setter ...
public UserDetails(String name) {
this.name = name;
}
}
My producer class looks like this:
#Configuration
public class UserFactory {
#Bean
#Scope("request")
public UserDetails createUserDetails() {
// this method should be called on every request
String name = SecurityContextHolder.getContext()
.getAuthentication().getPrincipal(); // get some user details, just an example (I am aware of Principal)
// construct a complex user details object here
return new UserDetails(name)
}
}
And this is the class where the UserDetails instance should be injected:
#RestController
#RequestMapping(value = "/api/something")
public class MyResource {
#RequestMapping(method = RequestMethod.GET)
#ResponseBody
public List<String> getSomething(UserDetails userDetails) {
// the userdetails should be injected here per request, some annotations missing?
// do something
}
}
The problem is that Spring complains at runtime about no default constructor (of course).
Failed to instantiate [UserDetails]: No default constructor found
But this is intended and I want to call my own factory to let it handle the Instantiation.
How can I achieve this? Why is UserFactory never called?
Basically you aren't using your scoped proxy. You cannot inject a scoped proxy into a method, you have to inject it into your controller.
public List<String> getSomething(UserDetails userDetails) { ... }
This will lead to spring trying to create a new instance of UserDetails through reflection, it will not inject your scoped bean. Hence it complains about the fact you need a default no-args constructor.
Instead what you should do is wire the dependency into your controller instead of the controller method.
#RestController
#RequestMapping(value = "/api/something")
public class MyResource {
#Autowired
private UserDetails userDetails;
#RequestMapping(method = RequestMethod.GET)
#ResponseBody
public List<String> getSomething() {
// the userdetails should be injected here per request, some annotations missing?
// do something
}
}
The idea is that the UserDetails is a scoped proxy and when used will either use the already present object or create a new one based on the #Bean method.
Additonally, the #Scope annotation in the UserFactory has to be modified as follows in order to work:
#Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS)
You're trying to inject a request scoped bean on a singleton bean.
You need to use a proxy for the UserDetails
#Scope(value = "request", proxyMode = ScopedProxyMode.INTERFACES)
You need a no arguments constructor in class UserDetails.
public class UserDetails {
private String name;
// getter / setter ...
public UserDetails() {
}
public UserDetails(String name) {
this.name = name;
}
}
I have a simple POJO class called MyService.java:
#RequestScoped
public class MyService {
public String helloWorld(){
return "hello world!";
}
}
I have a JAX-RS class where I want to Inject my POJO using CDI:
#Path("service")
public class RestfulService {
public static AtomicInteger counter = new AtomicInteger(0);
#EJB
MyEJB ejb;// The EJb injection works!
#Inject
MyService service; // CDI injection does NOT work!
#GET
#Path("counter")
#Produces(MediaType.TEXT_PLAIN)
public String getTotal() {
return "Number of hits: " + counter.incrementAndGet();
}
}
Here is the exception thrown:
g.glassfish.hk2.api.UnsatisfiedDependencyException: There was no object available for injection at SystemInjecteeImpl(requiredType=MyService,parent=RestfulService,qualifiers={},position=-1,optional=false,self=false,unqualified=null,1492861225)
at org.jvnet.hk2.internal.ThreeThirtyResolver.resolve(ThreeThirtyResolver.java:74)
The files beans.xmlis present and the discovery-mode is set to annotated.
Any help please? Seems a basic example to me.
I have something like this setup below. This is a simplified version but I think it gets the basic idea across. I am using Jersey 2.16, Java 1.8, and Glassfish Open Source 4.1
public interface IReportService {
String addNewReport(Report report);
}
#Path("reports")
public class ReportResource implements IReportService {
/**
* Service layer.
*/
#EJB
private transient ReportService service;
#POST
#Consumes({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
#Produces(MediaType.TEXT_PLAIN)
#Override
public String addNewReport(final Report report) {
return service.addNewReport(report);
}
}
#Stateless
#LocalBean
public class ReportService implements IReportService {
#EJB
private IReportPersistence reportPersistence;
#Context
SecurityContext secContext;
public String addNewReport(final Report report) {
report.setUserName(secContext.getUserPrincipal().getName());
reportPersistence.persist(report);
}
}
But when I deploy and try to hit the web-service I get a NullPointer exception from the security context. It just seems that the Context is not being injected at all. I checked and it's the secContext variable itself, not just the return from getUserPrincipal() that is null. There are no warning or exceptions in the Glassfish log besides my NullPointer (which results in a 500 error returned to the web client).
The problem is that you are using the SecurityContext in the wrong place. You have to use it inside your REST resource class.
You can try the following:
#POST
#Consumes({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
#Produces(MediaType.TEXT_PLAIN)
#Override
public String addNewReport(final Report report, #Context SecurityContext sc) {
report.setUserName(sC.getUserPrincipal().getName());
return service.addNewReport(report);
}
For more details have a look at the Jersey Documentation - Chapter 16. Security.
Inside of EJBs you have to use the EJBContext (or the SessionContext).
Thank you, I have solved using the EJBContext inside the EJBs, as pointed by unwichtich.
In conclusion, SecurityContext is only for the JAX-RS bean, I have used the EJBContext object inplace of SecurityContext into the other java beans.
You can also use the SessionContext object but EJBContext interface resembles the SecurityContext one. Here is an usage example:
#DeclareRoles({"administrator","operator","user"})
#PermitAll
#Stateless
public class myFacade {
#PersistenceContext(unitName = "myPersistencePU")
private EntityManager em;
#Resource EJBContext securityContext;
public DataStuff find(Object id) {
//Now the securityContext is != null :-D
String username = securityContext.getCallerPrincipal().getName();
if(username.equals("gino"){
return null;
}
return getEntityManager().find(entityClass, id);
}
}
It works auto-magically as expected, the EJB sees the same Principal(s) as the JAX-RS servlet do.
I have Session scope bean which behaves as request scope bean.
I'm using it in Singleton bean, and maybe that's why with second request is has null values?
Code which I used to setup the bean:
In singleton:
#Autowired
private MyBean myBean;
MyBean class:
#Component
#Scope(value = "session", proxyMode = ScopedProxyMode.INTERFACES)
public class MyBeanImpl implements MyBean, Serializable {
Configuration:
#Configuration
#ComponentScan(scopedProxy = ScopedProxyMode.INTERFACES, value = { "my.package.with.bean" })
public class ComponentsConfig {
}
MyBean is simple pojo. with getters and setters.
With first request I set values on that bean, with second request in the same class (singleton) I want to read those values, but all values are null. There is no way that something has overrdiden those values.
EDIT
How I make requests - It's just simple browser request, and code which read/writes to my session bean lays in filters.
This is singleton:
#Service
public class SingletonBean {
#Autowired
private MyBean myBean;
// here I save the data to session scoped bean
private void saveUser(LdapContext ctx, String principal) throws NamingException {
Attributes attributes = getAttributes();
myBean.setId("id");
myBean.setName("name");
myBean.setEmail("email");
myBean.setTelNum("123");
myBean.setGroups(Lists.newArrayList("one", "two"));
myBean.setAddress("address");
}
// here I try to read data from session scoped bean:
#Override
protected AuthorizationInfo queryForAuthorizationInfo(PrincipalCollection principals,
LdapContextFactory ldapContextFactory) throws NamingException {
// here userInfo will be null
List<String> userInfo = myBean.getGroups();
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
for (String role : userInfo ) {
authorizationInfo.addRole(role);
}
return authorizationInfo;
}
}
When user logs in, and he is authenticated, I save his details in session bean. When he tries to open any page method queryForAuthorizationInfo is executed (after chain of filters) and values are null in that object.