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.
}
}
Related
I am able to access HttpServletRequest by using #Context annotation in my rest service. But unable to access the same in repository class.I do not want to pass the request form MyService to MyRespository while calling methods.
#Path("/someUrl")
public MyService{
#Context
private HttpServletRequest request;
#Get
public void someMethod()
{
myRepository.someMethod();
}
}
But same annotation not working for my Repository class
#Repository
public MyRepository
{
#Context
private HttpServletRequest request;
public void someMethod()
{
//need request here
}
}
it injection null request. Not sure why this is not working.
The problem is the way Jersey (and its DI framework HK2) is integrated, is that Spring components can be injected into Jersey (HK2) components, but not vice versa. HttpServletRequest is bound as a Jersey component.
What you can do is create an HK2 service, that wraps the Spring repo, and the HttpServletRequest. IMO, it is better design anyway. A repository shouldn't be concerned with the HttpServletRequest, it is only concerned with data.
So you can have
public class MyService {
#Inject // or #Autowired (both work)
private MyRepository repository;
#Context
private HttpServletRequest request;
}
Then bind the service with HK2
import org.glassfish.hk2.utilities.binding.AbstractBinder;
import org.glassfish.jersey.process.internal.RequestScoped;
public class AppBinder extends AbstractBinder {
#Override
public void configure() {
bindAsContract(MyService.class).in(RequestScoped.class);
// note, if `MyService` is an interface, and you have
// an implementation, you should use the syntax
//
// bind(MyServiceImpl.class).to(MyService.class).in(...);
//
// Then you inject `MyService`. Whatever the `to(..)` is,
// that is what you can inject
}
}
And register the binder with Jersey
public class JerseyConfig extends ResourceConfig {
public JerseyConfig() {
register(new AppBinder());
}
}
Then you can inject MyService into your resource class.
If you don't want to go this route, then you need to make MyRepository an HK2 service, or use a an HK2 Factory to wrap the repository, and explicitly inject it. Something like
import javax.inject.Inject;
import org.glassfish.hk2.api.Factory;
import org.glassfish.hk2.api.ServiceLocator;
import org.springframework.context.ApplicationContext;
public class MyRepositoryFactory implements Factory<MyRepository> {
private final MyRepository repo;
#Inject
public MyRepositoryFactory(ApplicationContext ctx, ServiceLocator locator) {
MyRepository r = ctx.getBean(MyRepository.class);
locator.inject(r);
this.repo = r;
}
#Override
public MyRepository provide() {
return repo;
}
#Override
public void dispose(MyRepository t) {/* noop */}
}
Then register the factory
#Override
public void configure() {
bindFactory(MyRepositoryFactory.class).to(MyRepository.class).in(Singleton.class);
}
If you do the above, then you just use the MyRepository, instead of adding the service layer. Basically you need to get the repo from Spring, and explicitly inject it with the HK2 ServiceLocator (which is the HK2 analogue of the Spring ApplicationContext).
How can/should I pass an object from a ContainerRequestFilter to a (post-matching) resource in (JAX-RS) Resteasy version 3.0.11 that has undertow embedded and uses Guice?
The method ContainerRequestContext#setProperty stores values which are synced with the HttpServletRequest. So with plain JAX-RS you can store an attribute like this:
#Provider
public class SomeFilter implements ContainerRequestFilter {
#Override
public void filter(ContainerRequestContext requestContext) throws IOException {
requestContext.setProperty("someProperty", "someValue");
}
}
And afterwards you can obtain it in your resource class:
#GET
public Response someMethod(#Context org.jboss.resteasy.spi.HttpRequest request) {
return Response.ok(request.getAttribute("someProperty")).build();
}
With CDI you also can inject any bean in the filter and resource class:
#Provider
public class SomeFilter implements ContainerRequestFilter {
#Inject
private SomeBean someBean;
#Override
public void filter(ContainerRequestContext requestContext) throws IOException {
someBean.setFoo("bar");
}
}
In your resource class:
#Inject
private SomeBean someBean;
#GET
public Response someMethod() {
return Response.ok(someBean.getFoo()).build();
}
I'd expect the same to be working with Guice.
Update: As #bakil pointed out correctly you should use a #RequestScoped bean if the object you want to pass should only be associated with the current request.
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");
}
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 need to create a new EmployeeInfoCache instance (not a singleton) from info in the HttpServletRequest that is used to get info from an external app. I then want to give this object as a dependency to non-web-layer objects (where it will be set for all #Autowired references). EmployeeInfoCache itself has no web-layer dependencies (e.g. HttpServletRequest).
Can this be done? I thought about writing a spring interceptor which does the following but I don't know what to do to put an object in the spring context such that it will be used to resolve all #Autowired dependencies.
e.g.
public class MyInterceptor extends HandlerInterceptorAdapter
{
public boolean preHandle(
HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception
{
- use info from HttpServletRequest to make calls to external app
- create EmployeeInfoCache object w/ this info
- add EmployeeInfoCache to spring application context where it will be used for resolution of #Autowired
}
}
And the remaining code:
// Assume don't have 'Component' or a similar annotation?
public class EmployeeInfoCache
{
...
}
// REST controller that calls the business logic method
#Controller
MyController
{
#Autowired
private MyBusinessObjectInterface myBusinessObject;
#RequestMapping(...)
public #ResponseBody MyResult myMethod(#RequestBody MyObject myObject, HttpServletRequest request, HttpServletResponse response)
{
myBusinessObject.doIt();
}
}
// Non-web-layer code that uses EmployeeInfoCache
#Service(...)
MyBusinessObject implements MyBusinessObjectInterface
{
// I want the EmployeeInfoCache instance created in MyInterceptor to be autowired here
#Autowired
private EmployeeInfoCache employeeInfoCache;
#Override
public void doIt()
{
employeeInfoCache.getName();
}
}
It sounds like you want to use a factory pattern. Have a Spring bean which is the factory method that returns the Cache.
http://kh-yiu.blogspot.in/2013/04/spring-implementing-factory-pattern.html