How to use #Inject and CDI for enterprise beans with JAX-RS 2.0 (Resteasy) - rest

I have some problem finding out how get JAX-RS 2.0 to work with CDI on wildfly 10. I got some answer on another post that was a mix of JAX-RS 1.0/2.0 and they used other dependencies than the included libraries in Wildfly.
My objective is to inject a singleton enterprise bean that encapsulate business logic that resides in the same jar into my REST resource. The REST resource class is supposed to be request scoped and only deal with REST functionality (request and response). My POJO classes are JAXB notated.
How can I use JAX-RS 2.0 with the include CDI libraries in Wildfly 10?
The bean interface
#Local
public interface DateBean {
Date getLocalFormatDate();
}
The bean
#Singleton
public class DateBeanImpl implements DateBeanLocal {
private static final Logger LOG = Logger.getLogger("org.test.logger");
public DateBean() {
LOG.fine("DateBean");
}
#Override
public Date getLocalFormatDate() {
Calendar cal = Calendar.getInstance();
TimeZone localZone = TimeZone.getDefault();
cal.setTimeZone(localZone);
Date localTime = cal.getTime();
return localTime;
}
}
The REST resource
#Path("classroom")
#Consumes({MediaType.APPLICATION_XML, MediaType.TEXT_PLAIN})
#Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
public class ClassRoomResource {
private static final Logger LOG = Logger.getLogger("org.clearbyte.logger");
#Inject private DateBean dateBean;
public ClassRoomResource() {
LOG.fine("ClassRoomResource");
}
#GET
#Path("{id}/getDummy")
public ClassRoom getDummy(#PathParam("id") long id) {
ClassRoom room = new ClassRoom();
room.setRoomName("Dummy");
room.setRoomNr(id);
return room;
}
#GET
#Path("localDate")
#Produces({MediaType.TEXT_HTML})
public Response getLocalformatDate() {
LOG.fine("DateBean variable: " +dateBean);
Date localDate = dateBean.getLocalDate();
LOG.fine("Local date: " +localDate);
return Response.status(Response.Status.OK)
.entity(localDate.toString())
.build();
}
}

The Resteasy implementation of JAX-RS 2.0 are included in Wildlfy 10, so there is no need to add further dependencies.
The interface doesn’t need #Localwhen is resides in the same jar/war for CDI to find it. To make the enterprise bean singleton in CDI use the #ApplicationScope, you can omit the #Singleton notation if you don't need a managed bean with read/write synchronisation.
#ApplicationScoped
public class DateBeanImpl implements DateBean {
private static final Logger LOG = Logger.getLogger("org.test.logger");
public DateBean() {
LOG.fine("DateBean");
}
#Override
public Date getLocalFormatDate() {
//DateFormat dateFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
Calendar cal = Calendar.getInstance();
TimeZone localZone = TimeZone.getDefault();
cal.setTimeZone(localZone);
Date localTime = cal.getTime();
return localTime;
}
}
The make the REST resource request scoped use the #RequestScoped notation. Notice that the #Inject inject the interface and not the implementation of the bean.
#RequestScoped
#Path("classroom")
#Consumes({MediaType.APPLICATION_XML, MediaType.TEXT_PLAIN})
#Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
public class ClassRoomResource {
private static final Logger LOG = Logger.getLogger("org.clearbyte.logger");
#Inject private DateBean dateBean;
...
No configuration of the web.xml is necessary if you a extend the jax-rs Application class.
#ApplicationPath("rest")
public class ClassRoomApp extends Application {
private final Set<Class<?>> resources = new HashSet<>();
public ClassRoomApp() {
resources.add(ClassRoomResource.class);
}
#Override
public Set<Class<?>> getClasses() {
return resources;
}
}

Related

Dynamic injection using #SpringBean in wicket

I have a form that based on collected information generates a report. I have multiple sources from which to generate reports, but the form for them is the same. I tried to implement strategy pattern using an interface implementing report generator services, but that led to wicket complaining about serialization issues of various parts of the report generator. I would like to solve this without duplicating the code contained in the form, but I have not been able to find information on dynamic injection with #SpringBean.
Here is a rough mock up of what I have
public class ReportForm extends Panel {
private IReportGenerator reportGenerator;
public ReportForm(String id, IReportGenerator reportGenerator) {
super(id);
this.reportGenerator = reportGenerator;
final Form<Void> form = new Form<Void>("form");
this.add(form);
...
form.add(new AjaxButton("button1") {
private static final long serialVersionUID = 1L;
#Override
protected void onSubmit(AjaxRequestTarget target)
{
byte[] report = reportGenerator.getReport(...);
...
}
});
}
}
If I do it this way, wicket tries to serialize the concrete instance of reportGenerator. If I annotate the reportGenerator property with #SpringBean I receive Concrete bean could not be received from the application context for class: IReportGenerator
Edit: I have reworked implementations of IRerportGenerator to be able to annotate them with #Component and now I when I use #SpringBean annotation I get More than one bean of type [IReportGenerator] found, you have to specify the name of the bean (#SpringBean(name="foo")) or (#Named("foo") if using #javax.inject classes) in order to resolve this conflict. Which is exactly what I don't want to do.
I think the behavior you're trying to achieve can be done with a slight workaround, by introducing a Spring bean that holds all IReportGenerator instances:
#Component
public class ReportGeneratorHolder {
private final List<IReportGenerator> reportGenerators;
#Autowired
public ReportGeneratorHolder(List<IReportGenerator> reportGenerators) {
this.reportGenerators = reportGenerators;
}
public Optional<IReportGenerator> getReportGenerator(Class<? extends IReportGenerator> reportGeneratorClass) {
return reportGenerators.stream()
.filter(reportGeneratorClass::isAssignableFrom)
.findAny();
}
}
You can then inject this class into your Wicket page, and pass the desired class as a constructor-parameter. Depending on your Spring configuration you might need to introduce an interface for this as well.
public class ReportForm extends Panel {
#SpringBean
private ReportGeneratorHolder reportGeneratorHolder;
public ReportForm(String id, Class<? extends IReportGenerator> reportGeneratorClass) {
super(id);
IReportGenerator reportGenerator = reportGeneratorHolder
.getReportGenerator(reportGeneratorClass)
.orElseThrow(IllegalStateException::new);
// Form logic omitted for brevity
}
}
As far as I am able to find, looking through documentation and even the source for wicket #SpringBean annotation, this isn't possible. The closest I got is with explicitly creating a proxy for a Spring bean based on class passed. As described in 13.2.4 Using proxies from the wicket-spring project chapter in Wicket in Action.
public class ReportForm extends Panel {
private IReportGenerator reportGenerator;
private Class<? extends IReportGenerator> classType;
private static ISpringContextLocator CTX_LOCATOR = new ISpringContextLocator() {
public ApplicationContext getSpringContext() {
return ((MyApplication)MyApplication.get()).getApplicationContext();
}
};
public ReportForm(String id, Class<? extends IReportGenerator> classType) {
super(id);
this.classType = classType;
final Form<Void> form = new Form<Void>("form");
this.add(form);
...
form.add(new AjaxButton("button1") {
private static final long serialVersionUID = 1L;
#Override
protected void onSubmit(AjaxRequestTarget target)
{
byte[] report = getReportGenerator().getReport(...);
...
}
});
}
private <T> T createProxy(Class<T> classType) {
return (T) LazyInitProxyFactory.createProxy(classType, new
SpringBeanLocator(classType, CTX_LOCATOR));
}
private IReportGenerator getReportGenerator() {
if (reportGenerator = null) {
reportGenerator = createProxy(classType);
}
return reportGenerator;
}
}

FeignClient is changing the format of LocalDate passed to it

I'm have a #FeignClient in my app:
#FeignClient(name="${mongo.service.id}", url="${mongo.service.url}")
public interface MongoCustomerClaimInterface {
#GetMapping(path = "/api/customerClaim/countClaims/{businessDate}")
List<TransactionClaimStatusData> countClaimsByStatusToBusinessDate(
#PathVariable #DateTimeFormat(iso = DateTimeFormat.ISO.DATE)
LocalDate businessDate);
}
I call the feign method and passing it a formatted LocalDate variable, and printing it to the log:
LocalDate businessDate = getBusinessDate();
LocalDate formattedDate = LocalDate.parse(businessDate.toString(),
DateTimeFormatter.ISO_DATE);
log.info("formattedDate: " + formattedDate);
claimStatusDataList = mongoCustomerClaimInterface.countClaims(formattedDate);
The call generates 404 error and log:
2020-24-02 18:10:25.433 INFO DashboardServiceImpl - formattedDate: 2020-02-23
2020-24-02 18:10:25.440 DEBUG
RequestMappingHandlerMapping:
Looking up handler method for path /api/customerClaim/countClaims/2/23/20
RequestMappingHandlerMapping:
Did not find handler method for [/api/customerClaim/countClaims/2/23/20]
Although I pass a date in the format yyyy-mm-dd so it will match:
#DateTimeFormat(iso = DateTimeFormat.ISO.DATE)
Feign somehow changes the date and then no matching url is found
How can I prevent Feign from doing this and configure a uniform formatter?
So apparently Feign isn't working with all of SpringMvc annotations. #DateTimeFormat, as great as it is, is a SpringMvc annotation and NOT a FeignClient annotation.
I solved this by doing several things:
Created a MessageConvertersConfiguration class in my MvcConfig class.
In it, I created a LocalDate & LocalDateTime converter bean and added it to the converters list that this configurer uses when an http message arrives:
#Configuration
public class MvcConfig {
#Configuration
#EnableWebMvc
public class MessageConvertersConfiguration implements WebMvcConfigurer {
#Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(new MappingJackson2HttpMessageConverter(localDateTimeConverter()));
}
#Bean
public ObjectMapper localDateTimeConverter() {
ObjectMapper mapper = new ObjectMapper();
SimpleModule localDateTimeModule = new SimpleModule();
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("dd-MM-yyyy'T'HH:mm:ss");
localDateTimeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(dateTimeFormatter));
localDateTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(dateTimeFormatter));
DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
localDateTimeModule.addDeserializer(LocalDate.class, new LocalDateDeserializer(dateFormatter));
localDateTimeModule.addSerializer(LocalDate.class, new LocalDateSerializer(dateFormatter));
mapper.registerModules(localDateTimeModule);
return mapper;
}
}
}
Created a configuration class for my feign client. In it, I instantiated a SpringMvcContract. Because Feign is created before SpringMvc, the converters we just defined won't affect feign without this contract:
#Configuration
public class FeignConfig {
#Bean
public Contract feignContract() {
return new SpringMvcContract();
}
}
Eventually, I added to configuration attribute to my FeignClient:
#FeignClient(name="${mongo.service.id}", url="${mongo.service.url}", configuration = FeignConfig.class)
public interface MongoCustomerClaimInterface {
#GetMapping(path = "/api/customerClaim/countClaimsByStatusToBusinessDate/{businessDate}")
List<TransactionClaimStatusData> countClaimsByStatusToBusinessDate(#PathVariable #DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate businessDate);
}

Integration Tests for RESTEasy Endpoint

I want to perform integration tests on my REST endpoint but am running into issues.
Below is my endpoint. NOTE: I cannot change this part of the code.
#Path("/people")
public class PersonResource {
private final PersonService personService;
#Inject
public PersonResource(final PersonService personService) {
this.personService = personService;
}
#GET
#Produces("application/json")
public List<Person> getPersonList() {
return personService.getPersonList();
}
}
From what I've been able to find online, I have the following basic structure for my test.
public class PersonResourceTest {
private Dispatcher dispatcher;
private POJOResourceFactory factory;
#Before
public void setup() {
dispatcher = MockDispatcherFactory.createDispatcher();
factory = new POJOResourceFactory(PersonResource.class);
dispatcher.getRegistry().addResourceFactory(factory);
}
#Test
public void testEndpoint() throws URISyntaxException {
MockHttpRequest request = MockHttpRequest.get("people");
MockHttpResponse response = new MockHttpResponse();
dispatcher.invoke(request, response);
System.out.print("\n\n\n\n\n" + response.getStatus() + "\n\n\n\n\n");
System.out.print("\n\n\n\n\n" + response.getContentAsString() + "\n\n\n\n\n");
}
}
However, this results in the following error on the last line of the setup method.
java.lang.RuntimeException: RESTEASY003190: Could not find constructor for class: my.path.PersonResource
I explored the Registry API and thought maybe I should have been using addSingletonResource instead, so I changed the last line of setup to dispatcher.getRegistry().addSingletonResource(personResource); and added the following.
#Inject
private PersonResource personResource;
But that results in a NullPointerException on the last line of setup.
The sparse documentation on the mocking isn't very helpful. Can anyone point out where I'm going wrong? Thanks.
You need to do two things
Add a no arguments constructor to your source class:
public PersonResource() {
this(null)
}
In the test class, initialize the PersonResource class with an instance of PersonService class:
dispatcher.getRegistry().addSingletonResource(new PersonResource(new PersonService()));
If needed, the PersonService class can be mocked:
private Dispatcher dispatcher;
#Mock
private PersonService service;
#Before
public void setup() {
MockitoAnnotations.initMocks(this);
dispatcher = MockDispatcherFactory.createDispatcher();
PersonResource resource= new PersonResource(service);
ispatcher.getRegistry().addSingletonResource(resource);
}
Hope it helps!

Principal in EJB injected from REST service

I have a simple REST webservice that uses basic authentication.
#Path("/ws")
#Stateless
public class MyWebservice {
#EJB
private MyEJB myEjb;
#GET
#Path("/get")
#Produces(MediaType.APPLICATION_JSON)
public MyObject getObject() {
return myEjb.getObject();
}
}
The EJB is also really simple:
#Stateless
public class MyEJB {
#Resource(lookup = "java:comp/EJBContext")
private SessionContext sessionContext;
#PermitAll
public MyObject getObject() {
return new MyObject();
}
}
However, when I debug, the principal in the EJB in the sessionContext is always "anonymous", no matter what user I use to authenticate against the WS.
How can I set the EJB principal to the same as the one that is authenticated against the webservice?
Try this one:
#Resource
EJBContext context;
...
context.getCallerPrincipal()

Can I do setter injection using #Inject annotation

In my GWTP application I need to Inject HttpServletRequest, HttpSession as instance variable of ActionHandler.
My ActionHandler is initialized through Spring.
I can't get current Request object through Spring as it instantiates just POJO.
I am thinking about mixing GIN and Spring.
Would I be able inject HttpServletRequest using GIN in my ActionHandler which is instantiated through Spring?????
Is it possible to do following way??
#Configuration
#Import(DefaultModule.class)
public class ServerModule extends HandlerModule
{
#Bean
public UserVerficationActionHandler getUserVerificationActionActionHandler()
{
return new UserVerficationActionHandler();
}
}
public class UserVerficationActionHandler implements ActionHandler<UserVerficationAction, UserVerficationActionResult>
{
#Autowired
private UserService userService;
private Provider<HttpServletRequest> requestProvider;
#Inject
public UserVerficationActionHandler()
{
}
public UserVerficationActionResult execute(UserVerficationAction action, ExecutionContext context) throws ActionException
{
....
}
#Inject
public Provider<HttpServletRequest> setRequestProvider()
{
return requestProvider;
}
}
-------ActionHandler Ends--------
Can somebody let me know Is it possible to do SetterInjection this way?
Second thing, if above is possible then will I be getting current request object using this method?
Thanks in advance.
Bhavesh.