I wanted to extend or customize my existing rest apis. In my rest the Service calls the Handler which is supposed to contain business logic (as a product) and I need a way to extend or customize this (per client customization). Please advise how to go about doing this. Attached is a simple hello world setup I have, it would be great it a hook into this 'DefaultGreetingsHandler.greetUser' can be shown.
package com.myapi.greetings.rest;
import javax.inject.Inject;
import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import com.myapi.greetings.handler.GreetingsHandler;
import com.myapi.greetings.model.Greeting;
#Path("/greetings")
public class GreetingsService {
#Inject
GreetingsHandler handler;
#GET
#Path("/{name}")
public Response getCustomMessage(#PathParam("name") String name) {
String output = handler.greetUser(name);
return Response.status(200).entity(output).build();
}
}
I need a hook in the Handler.greetUser to be able to customize per client
package com.myapi.greetings.handler;
import javax.inject.Inject;
import com.myapi.greetings.dao.GreetingsDao;
public class DefaultGreetingsHandler implements GreetingsHandler {
#Inject
GreetingsDao dao;
#Override
public String greetUser(String name) {
return dao.getGreeting() + name;
}
}
Its a simple maven based rest project in java. Please refer to following link for the complete setup zip file.
https://1drv.ms/u/s!AulFHVWnSJeFaqIB0JHKzo95DbU
Appreciate the help.
Create a New Class MyApplicationBinder
import org.glassfish.hk2.utilities.binding.AbstractBinder;
public class MyApplicationBinder extends AbstractBinder {
#Override
protected void configure() {
bind(GreetingsHandler.class).to(GreetingsHandler.class);
}
}
and then Register it in the main application class
#ApplicationPath("webapi")
public class MyApp extends ResourceConfig{
public MyApp() {
register(new MyApplicationBinder());
packages(true, "com.myapi.greetings.rest");
}
}
This should make your application work .
Related
I have an application based on Jersey JAX-RS. I need to refactor the event handler and therefore also write a test for it.
I'm trying to do this with the JerseyTest Framework. I created a configuration to extend ResourceConfig, but when I use the target () call the handler is not called.
I will present the situation using code.
Here is an example Resource class:
package com.my.page;
import org.glassfish.hk2.api.messaging.Topic;
import com.my.core.entity.Link;
import com.my.core.location.LinkHitLocationFactory;
import com.my.core.service.LinkService;
import com.my.core.service.link.LinkFinder;
import com.my.core.service.link.LinkFinderFactory;
import com.my.event.LinkHitEvent;
import com.my.exception.FragmentNotFoundException;
import javax.annotation.security.PermitAll;
import javax.inject.Inject;
import javax.ws.rs.*;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.Response;
#PermitAll
#Path("/")
public class LinkResource {
#Inject
private LinkService linkService;
#Inject
private Topic<LinkHitEvent> linkHitPublisher;
#Inject
private LinkFinderFactory linkFinderFactory;
#Inject
private LinkHitLocationFactory linkHitLocationFactory;
#GET
#Path("/{fragment:[^ ]{1,32}}")
public Response redirect(
#PathParam("fragment") String fragment,
#HeaderParam("Range") String range,
#HeaderParam("User-Agent") String userAgent,
#Context HttpHeaders headers) throws Exception {
LinkFinder linkFinder = linkFinderFactory.getLinkFinder(fragment);
Link link = linkFinder.getLink(fragment);
if (link.isExpired()) {
throw new FragmentNotFoundException(fragment);
}
linkService.insertHit();
linkHitPublisher.publish(new LinkHitEvent(link));
return handlerFactory.getHandler(link).handleGet(link, range).build();
}
}
Event test:
package com.my.page;
import org.glassfish.hk2.extras.events.internal.TopicDistributionModule;
import org.glassfish.hk2.utilities.binding.AbstractBinder;
import org.glassfish.jersey.server.ResourceConfig;
import org.glassfish.jersey.test.JerseyTest;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import pl.comvision.hk2.events.ThreadedEventDistributorService;
import com.my.client.CallbackTargetBuilder;
import com.my.core.entity.Link;
import com.my.core.mapper.LinkMapper;
import com.my.core.service.LinkService;
import com.my.page.resource.LinkResource;
import javax.ws.rs.core.Application;
import javax.ws.rs.core.Response;
import static javax.ws.rs.core.Response.Status.TEMPORARY_REDIRECT;
import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.*;
#RunWith(MockitoJUnitRunner.class)
public class CallbackEventTest extends JerseyTest {
#Mock
private LinkMapper linkMapper;
#Mock
private LinkService linkService;
private CallbackTargetBuilder callbackTargetBuilder;
private final String callbackUrl = "";
#Override
protected Application configure() {
this.callbackTargetBuilder = spy(new CallbackTargetBuilder(this.callbackUrl));
ResourceConfig config = new ResourceConfig(LinkResource.class);
config.register(new TopicDistributionModule());
config.register(new AbstractBinder() {
#Override
protected void configure() {
addActiveDescriptor(ThreadedEventDistributorService.class).setRanking(100);
}
});
config.register(new EventsContainerListener(CallbackEventHandler.class));
config.register(new AbstractBinder() {
#Override
protected void configure() {
bind(linkMapper).to(LinkMapper.class);
bind(linkService).to(LinkService.class);
bind(mock(LinkService.class)).to(LinkService.class);
bind("").to(String.class).named("varPath");
bind("127.0.0.1").to(String.class).named("requestIP");
bind(callbackTargetBuilder).to(CallbackTargetBuilder.class);
}
});
return config;
}
#Test
public void publish_event() {
Link link = mock(Link.class);
when(link.getUrl()).thenReturn("example");
when(link.getName()).thenReturn("test");
when(linkMapper.getByName(anyString())).thenReturn(link);
Response response = target("/testY").property("jersey.config.client.followRedirects", false).request().get();
assertEquals(TEMPORARY_REDIRECT.getStatusCode(), response.getStatus());
verify(callbackTargetBuilder).build();
}
}
For testing purposes, I only injected callbackTargetBuilder into the handler, and called the build method on it to verify the call:
package com.my.page;
import org.glassfish.hk2.api.messaging.MessageReceiver;
import org.glassfish.hk2.api.messaging.SubscribeTo;
import org.jvnet.hk2.annotations.Service;
import com.my.client.CallbackTargetBuilder;
import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;
import javax.ws.rs.client.Client;
import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.client.Entity;
import javax.ws.rs.core.Form;
import javax.ws.rs.core.MediaType;
#Service
#Singleton
#MessageReceiver
public class CallbackEventHandler {
#Named("callbackUrl")
private String url;
#Inject
private CallbackTargetBuilder callbackTargetBuilder;
#MessageReceiver
public void handle(#SubscribeTo LinkHitEvent event) {
Form form = new Form();
form.param("id", event.getLink().getId().toString());
form.param("name", event.getLink().getName());
callbackTargetBuilder.build();
Client client = ClientBuilder.newClient();
client.target(url).request().post(Entity.entity(form, MediaType.APPLICATION_FORM_URLENCODED_TYPE));
}
}
Edit:
I tried to register dependencies differently, but it does not bring satisfactory results. Each time verification fails:
verify (callbackTargetBuilder) .build ();
Looking for information I found that I can configure the DeploymentContext, but I don't know if this is the right direction.
Edit the second:
A quick test shows that I may have some more basic problem with mocking. Because the call:
verify (linkService) .insertHit (anyObject ());
It also fails.
I will write only for posterity that the above code is correct. The problem was a lot of small bugs in the tested code and how to mock it.
I'm struggling on implementing a generic abstract jaxrs service without duplicating several jaxrs annotations.
So, for example, here is my service and entity structure:
AbstractEntity.java
import javax.xml.bind.annotation.XmlElement;
public abstract class AbstractEntity {
#XmlElement
private String name;
public String getName() {
return name;
}
}
AbstractService.java
import javax.ws.rs.Consumes;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
public class AbstractService<T extends AbstractEntity> {
#POST
#Consumes(MediaType.APPLICATION_XML)
#Produces(MediaType.TEXT_PLAIN)
#Path("/details")
public Response getEntityDetails(final T entity) {
// just return the name of this entity
return Response.ok(entity.getName()).build();
}
}
The implementation is like:
Car.java
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
#XmlRootElement
public class Car extends AbstractEntity {
#XmlElement
private String brand;
public String getBrand() {
return brand;
}
}
CarService.java
import javax.ws.rs.Path;
#Path("/cars")
public class CarService extends AbstractService<Car> {
// should provide the super getEntityDetails method with a Car entity
}
Now i want to POST my car entity to /cars/details to get the details (return the name "A5" which is implemented in the abstract service):
POST /cars/details
<car>
<brand>Audi</brand>
<name>A5</name>
</car>
Unfortunately, when I post it to my service, it says:
JAXRSUtils W .No message body reader found for request class : AbstractEntity, ContentType : application/xml.
WebApplicatio W WebApplicationException has been caught : no cause is available
I can correct it, if I implement my CarService as follows:
CarService.java
import javax.ws.rs.Consumes;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
#Path("/cars")
public class CarService extends AbstractService<Car> {
#POST
#Consumes(MediaType.APPLICATION_XML)
#Produces(MediaType.TEXT_PLAIN)
#Path("/details")
#Override
public Response getEntityDetails(final Car entity) {
return super.getEntityDetails(entity);
}
}
And removing all jaxrs annotations from the abstract service:
AbstractService.java
import javax.ws.rs.core.Response;
public class AbstractService<T extends AbstractEntity> {
public Response getEntityDetails(final T entity) {
// just return the name of this entity
return Response.ok(entity.getName()).build();
}
}
The point is, that I have about 60 of these CarService implementations and I don't want to repeat the getDetails method with all it's jaxrs annotation in each service, because it's always the same (the same boilerplate).
Any ideas or solutions on that?
potentially you may want to also have a look at more resource-oriented rest libraries to complement your JAX-RS codebase. They can do exactly that (path mappings, sorting, filtering, paging, etc.) without an annotation or abstract base class hell. crnk.io + jsonapi, graphql, falcor are such libraries. (disclaimer: I contribute to the first one)
I saw a tutorial of spring but I have some doubts about it
If I had a interface like this
package com.journaldev.spring.service;
import java.util.List;
import com.journaldev.spring.model.Person;
public interface PersonService {
public void addPerson(Person p);
public void updatePerson(Person p);
public List<Person> listPersons();
public Person getPersonById(int id);
public void removePerson(int id);
}
and a class which implements the interface
package com.journaldev.spring.service;
import java.util.List;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.journaldev.spring.dao.PersonDAO;
import com.journaldev.spring.model.Person;
#Service
public class PersonServiceImpl implements PersonService {
private PersonDAO personDAO;
public void setPersonDAO(PersonDAO personDAO) {
this.personDAO = personDAO;
}
.
.
.
}
and the controller which use the service
package com.journaldev.spring;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import com.journaldev.spring.model.Person;
import com.journaldev.spring.service.PersonService;
#Controller
public class PersonController {
private PersonService personService;
#Autowired(required=true)
#Qualifier(value="personService")
public void setPersonService(PersonService ps){
this.personService = ps;
}
.
.
.
}
Why the controller has a PersonService object (that in an interface) instead of an PersonServiceIml(class which implements the interface) object????
The idea is that designing to interfaces is good practice : What does "program to interfaces, not implementations" mean?
It makes creating a new implementation easy and refactoring is simpler. An interface also ensures mocking is straightforward.
In practice you can get rid of the interface, mockito/powermock etc handle simple classes fine, and in lots of cases you won't need a new implementation or any refactoring.
Not sure whether the question is,
Why to use interface or
How the methods are called of the implemented class.
Assuming that, you want to know how the implemented methods are called, please check your dependency injection file. You will see that you have injected your implemented class.
Say for example, it might look something like this,
<bean id="personService" class="com.somethimg.service.impl. PersonServiceImpl">
</bean>
i have created an Maven Enterprise Application Project in Netbeans. I have created an example-EJB, a Remote and a local interface. Now i want to publish a SOAP webservice by using these EJB, but there is something i'm doing wrong, because in glassfish i can't view the endpoint (with wsdl etc.). But it is listed as a component:
Component Name: RecommendationSOAPService, SHITSTORMRECOMMENDER
Module Name: ShitstormRecommender-ejb-1.0.jar, ShitstormRecommender-ejb-1.0.jar
Type: StatelessSessionBean, StatelessSessionBean
My EJB:
import com.sun.javafx.scene.control.skin.VirtualFlow;
import java.util.ArrayList;
import java.util.List;
import javax.ejb.Stateless;
import javax.ejb.LocalBean;
import org.andy.dto.Recommendation;
import org.andy.dto.RecommendationRequest;
#Stateless(name = "SHITSTORMRECOMMENDER")
public class ShitstormRecommenderEJB implements ShitstormReommenderEJBRemote,
ShitstormRecommenderEJBLocal {
#Override
public List<Recommendation> getRecommendation(RecommendationRequest request) {
Recommendation r1 = new Recommendation("Task 2", 10, "blubb");
Recommendation r2 = new Recommendation("Task 1", 5, "bla");
List<Recommendation> recommendations = new ArrayList<>();
recommendations.add(r1);
recommendations.add(r2);
return recommendations;
}
}
My Remote Interface (Local interface looks like the same only with #local):
#Remote
public interface ShitstormReommenderEJBRemote {
public List<Recommendation> getRecommendation(RecommendationRequest request);
}
My WebService-Class (located in the ejb-Container with the interfaces and the EJB):
package org.andy.service;
import java.util.List;
import javax.ejb.EJB;
import javax.ejb.Stateless;
import javax.jws.WebMethod;
import javax.jws.WebParam;
import javax.jws.WebResult;
import javax.jws.WebService;
import org.andy.dto.Recommendation;
import org.andy.dto.RecommendationRequest;
import org.andy.ejb.ShitstormReommenderEJBRemote;
#WebService(
name= "ShitstormRecommender",
portName = "ShitstormRecommenderPort",
serviceName = "ShitstormRecommenderService",
targetNamespace = "http://shitstormRecommendation.org"
)
#Stateless
public class RecommendationSOAPService{
#EJB
private ShitstormReommenderEJBRemote recommender;
#WebMethod
public String sayCiao(){
return "ciao";
}
#WebMethod
#WebResult(name="recommendations")
public List<Recommendation> getRecommendation(
#WebParam(name="recommendationRequest") RecommendationRequest request) {
return recommender.getRecommendation(request);
}
}
Maybe you can say, what i have to do to bring it to work? I have tried it out from this tutorial. There it seems to be working, but i think i have forgot something.
Thanks a lot for your help!
I am having trouble attaching a ContainerRequstFilter to a very simple JAX-RS rest application inside Glassfish4.
I believe that I've followed the instructions from both the javadoc and various other tutorial sources, but I am now at a complete loss.
The entire source (very short) is below, with the expected functionality:
(login) A user can log in at http://localhost/app/api/login/uname/password and the response is "You are logged in, uname"
(verify) User visits http://localhost/app/api/login/verify/uname and the response is a 401 unauthorized.
The second bit is meant the be implemented as a filter. What actually happens is that the first bit (login) works, and the second bit (verify) completely ignores the filter (including nothing in the logs to indicate that the filter ran). That is, the output is just "Every thing is fine, uname", rather than a 401 error.
What I want to understand is the way to get the filter attached to the verify action. For reference
I'm running glassfish 4.1 build 13
I'm compiling and deploying using gradle, with the deployment action
assassin deploy --force --contextroot /app /path/to/app.war
Glassfish reports that it's using Jersey 2.10.4
Here is the entirety of the source related to the application:
RestApp.java
package test.pack;
import java.util.HashSet;
import java.util.Set;
import javax.ws.rs.ApplicationPath;
import javax.ws.rs.core.Application;
#ApplicationPath("/api")
public class RestApp extends Application
{
#Override
public Set<Class<?>> getClasses()
{
Set<Class<?>> classes = new HashSet<Class<?>>();
classes.add(Login.class);
return classes;
}
}
Login.java
package test.pack;
import javax.ejb.Stateless;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Response;
#Stateless
#Path("login/")
#Produces("text/json")
public class Login
{
#GET
#Path("{username}/{password}")
public Response login(#PathParam("username") String username, #PathParam("password") String password)
{
System.out.println("Logging in");
return Response.ok("You are logged in, " + username).build();
}
#GET
#Path("/verify/{username}")
#Secured
public Response verify(#PathParam("username") String username)
{
System.out.println("Verify");
return Response.ok("Everything is fine, " + username).build();
}
Secured.java
package test.pack;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import javax.ws.rs.NameBinding;
#NameBinding
#Retention(RetentionPolicy.RUNTIME)
#Target({ElementType.TYPE, ElementType.METHOD})
public #interface Secured
{
}
LoggedInFilter.java
package test.pack;
import java.io.IOException;
import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.container.ContainerRequestFilter;
import javax.ws.rs.container.ContainerResponseContext;
import javax.ws.rs.container.ContainerResponseFilter;
import javax.ws.rs.core.Response;
#Secured
public class LoggedInFilter implements ContainerRequestFilter
{
#Override
public void filter(ContainerRequestContext requestContext) throws IOException
{
System.out.println("request");
requestContext.abortWith(Response.status(Response.Status.UNAUTHORIZED).build());
}
}
Ugh. That's embarrassing.
The next tutorial I visited had the solution, which was to register the filter in the RestApp class.
RestApp.java
#ApplicationPath("/api")
public class RestApp extends Application
{
#Override
public Set<Class<?>> getClasses()
{
Set<Class<?>> classes = new HashSet<Class<?>>();
classes.add(Login.class);
return classes;
}
#Override
public Set<Object> getSingletons()
{
Set<Object> singletons = new HashSet<Object>();
singletons.add(new LoggedInFilter());
return singletons;
}
}
I'll leave the answer here rather than deleting the question, since it was only in one of 4 tutorials that I read, and so this might be at least a little interesting.