2

I am implementing CRUD operations for a resource using JAX-RS. During my first use case it a blocking (synchronous) request i.e in a single threaded execution the client get the response back.

Use case 1:

@Path("/resourceService")
public class CRUDService {

    @GET
    @Path("/{param}")
    public Response getResource(@PathParam("param") String id) {

        Resource res = SomeBean().getResource(id);
        return Response.status(200).entity(res).build();

    }

}

Use case 2:

Now I have to implement the same request but it is a non-blocking (asynchronous) request such that as soon as the request lands on the server I have to return a Response to the server as an ACKNOWLEDGEMENT i.e a Response object with a ACCEPTED (202) status code and after that resume the initial request processing (get the resource by id in this case).

The response of this request (resource retrieved) is sent to the client by sending a new POST request whose content is the resource retrieved and then the client will return the response of this POST request with success status code. (NOTE: Here client does not bean browser, both client and server are machines as in a REST Api being called).

@Path("/resourceService")
public class CRUDService {

    @GET
    @Path("/{param}")
    public Response getResource(@PathParam("param") String id) {

        // ACKNOWLEDGEMENT response built and sent back to clinet   
        return Response.status(202).build();

        // Do the request handling i.e resource retrieval 
        Resource res = SomeBean().getResource(id);
        Resonse response = Response.status(200).entity(res).build();    

        // Create a HTTP POST Request and set the body as response object

        HttpClient client = HttpClientBuilder.create().build();
        HttpPost post = new HttpPost(url);
        post.setEntity(response);
        HttpResponse response = client.execute(post); 


    }

}

I read about Asynchronous JAX-RS and java.util.concurrent package.

If I do it asynchronously like launch a new thread to do the request handling, how to send a first time response of ACK back. Ex:

@POST
   @Consumes("application/json")
   @Produces("application/json")
   public void getResource(@PathParam("param") String id,
                      final @Suspended AsyncResponse response) {
      new Thread() {
         public void run() {
            Resource res = SomeBean().getResource(id);
            response.resume(resource);
         }
      }.start();
   }
}

How can this call flow be achieved using JAX-RS ??

EDIT: I tried as mentioned in your answer, however I am getting an exception:

Server log:

2016 - 07 - 25 12: 22: 01, 521 ERROR[io.undertow.request](
 default task - 1) UT005023: Exception handling request to / resourceService: org.jboss.resteasy.spi.UnhandledException: java.lang.NoSuchMethodError: my.ow.dummy.package.CRUDService$1. < init > (Lmy / own / dummy / package / CRUDService;) V
at org.jboss.resteasy.core.ExceptionHandler.handleApplicationException(ExceptionHandler.java: 76)[resteasy - jaxrs - 3.0 .6.Final.jar: ]
at org.jboss.resteasy.core.ExceptionHandler.handleException(ExceptionHandler.java: 212)[resteasy - jaxrs - 3.0 .6.Final.jar: ]
at org.jboss.resteasy.core.SynchronousDispatcher.writeException(SynchronousDispatcher.java: 149)[resteasy - jaxrs - 3.0 .6.Final.jar: ]
at org.jboss.resteasy.core.SynchronousDispatcher.invoke(SynchronousDispatcher.java: 372)[resteasy - jaxrs - 3.0 .6.Final.jar: ]
at org.jboss.resteasy.core.SynchronousDispatcher.invoke(SynchronousDispatcher.java: 179)[resteasy - jaxrs - 3.0 .6.Final.jar: ]
at org.jboss.resteasy.plugins.server.servlet.ServletContainerDispatcher.service(ServletContainerDispatcher.java: 220)[resteasy - jaxrs - 3.0 .6.Final.jar: ]
at org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher.service(HttpServletDispatcher.java: 56)[resteasy - jaxrs - 3.0 .6.Final.jar: ]
at org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher.service(HttpServletDispatcher.java: 51)[resteasy - jaxrs - 3.0 .6.Final.jar: ]
at javax.servlet.http.HttpServlet.service(HttpServlet.java: 790)[jboss - servlet - api_3 .1 _spec - 1.0 .0.Final.jar: 1.0 .0.Final]
at io.undertow.servlet.handlers.ServletHandler.handleRequest(ServletHandler.java: 85)[undertow - servlet - 1.0 .0.Final.jar: 1.0 .0.Final]
at io.undertow.servlet.handlers.security.ServletSecurityRoleHandler.handleRequest(ServletSecurityRoleHandler.java: 61)[undertow - servlet - 1.0 .0.Final.jar: 1.0 .0.Final]
at io.undertow.servlet.handlers.ServletDispatchingHandler.handleRequest(ServletDispatchingHandler.java: 36)[undertow - servlet - 1.0 .0.Final.jar: 1.0 .0.Final]
at org.wildfly.extension.undertow.security.SecurityContextAssociationHandler.handleRequest(SecurityContextAssociationHandler.java: 78)
at io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java: 25)[undertow - core - 1.0 .0.Final.jar: 1.0 .0.Final]
at io.undertow.servlet.handlers.security.SSLInformationAssociationHandler.handleRequest(SSLInformationAssociationHandler.java: 113)[undertow - servlet - 1.0 .0.Final.jar: 1.0 .0.Final]
at io.undertow.security.handlers.AuthenticationCallHandler.handleRequest(AuthenticationCallHandler.java: 52)[undertow - core - 1.0 .0.Final.jar: 1.0 .0.Final]
at io.undertow.security.handlers.AbstractConfidentialityHandler.handleRequest(AbstractConfidentialityHandler.java: 45)[undertow - core - 1.0 .0.Final.jar: 1.0 .0.Final]
at io.undertow.servlet.handlers.security.ServletConfidentialityConstraintHandler.handleRequest(ServletConfidentialityConstraintHandler.java: 61)[undertow - servlet - 1.0 .0.Final.jar: 1.0 .0.Final]
at io.undertow.servlet.handlers.security.CachedAuthenticatedSessionHandler.handleRequest(CachedAuthenticatedSessionHandler.java: 70)[undertow - servlet - 1.0 .0.Final.jar: 1.0 .0.Final]
at io.undertow.security.handlers.SecurityInitialHandler.handleRequest(SecurityInitialHandler.java: 76)[undertow - core - 1.0 .0.Final.jar: 1.0 .0.Final]
at io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java: 25)[undertow - core - 1.0 .0.Final.jar: 1.0 .0.Final]
at org.wildfly.extension.undertow.security.jacc.JACCContextIdHandler.handleRequest(JACCContextIdHandler.java: 61)
at io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java: 25)[undertow - core - 1.0 .0.Final.jar: 1.0 .0.Final]
at io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java: 25)[undertow - core - 1.0 .0.Final.jar: 1.0 .0.Final]
at io.undertow.servlet.handlers.ServletInitialHandler.handleFirstRequest(ServletInitialHandler.java: 240)[undertow - servlet - 1.0 .0.Final.jar: 1.0 .0.Final]
at io.undertow.servlet.handlers.ServletInitialHandler.dispatchRequest(ServletInitialHandler.java: 227)[undertow - servlet - 1.0 .0.Final.jar: 1.0 .0.Final]
at io.undertow.servlet.handlers.ServletInitialHandler.access$000(ServletInitialHandler.java: 73)[undertow - servlet - 1.0 .0.Final.jar: 1.0 .0.Final]
at io.undertow.servlet.handlers.ServletInitialHandler$1.handleRequest(ServletInitialHandler.java: 146)[undertow - servlet - 1.0 .0.Final.jar: 1.0 .0.Final]
at io.undertow.server.Connectors.executeRootHandler(Connectors.java: 168)[undertow - core - 1.0 .0.Final.jar: 1.0 .0.Final]
at io.undertow.server.HttpServerExchange$1.run(HttpServerExchange.java: 687)[undertow - core - 1.0 .0.Final.jar: 1.0 .0.Final]
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java: 1145)[rt.jar: 1.7 .0 _40]
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java: 615)[rt.jar: 1.7 .0 _40]
at java.lang.Thread.run(Thread.java: 724)[rt.jar: 1.7 .0 _40]
Caused by: java.lang.NoSuchMethodError: my.ow.dummy.package.CRUDService$1. < init > (Lmy / own / dummy / package / CRUDService;) V at my.ow.dummy.package.CRUDService.handlePutRequest(CRUDService.java: 78)[classes: ]
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)[rt.jar: 1.7 .0 _40]
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java: 57)[rt.jar: 1.7 .0 _40]
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java: 43)[rt.jar: 1.7 .0 _40]
at java.lang.reflect.Method.invoke(Method.java: 606)[rt.jar: 1.7 .0 _40]
at org.jboss.resteasy.core.MethodInjectorImpl.invoke(MethodInjectorImpl.java: 137)[resteasy - jaxrs - 3.0 .6.Final.jar: ]
at org.jboss.resteasy.core.ResourceMethodInvoker.invokeOnTarget(ResourceMethodInvoker.java: 280)[resteasy - jaxrs - 3.0 .6.Final.jar: ]
at org.jboss.resteasy.core.ResourceMethodInvoker.invoke(ResourceMethodInvoker.java: 234)[resteasy - jaxrs - 3.0 .6.Final.jar: ]
at org.jboss.resteasy.core.ResourceMethodInvoker.invoke(ResourceMethodInvoker.java: 221)[resteasy - jaxrs - 3.0 .6.Final.jar: ]
at org.jboss.resteasy.core.SynchronousDispatcher.invoke(SynchronousDispatcher.java: 356)[resteasy - jaxrs - 3.0 .6.Final.jar: ]
 ...29 more

This exception was not coming earlier, it started after I added the new Thread() part. Any idea?

4
  • 1
    Well, first things first, you can't have any code after a return statement, so that second block of code is invalid Commented Jul 20, 2016 at 16:33
  • You can't send POST request from a server to a client, unless both client and server are actually servers. Or you can use websocket, where both endpoints can send multiple messages without need of request/response Commented Jul 20, 2016 at 16:41
  • @jan.supol: Yes, both are servers. Commented Jul 20, 2016 at 19:00
  • @cricket_007: I know that. But that's what a dummy code I wrote explaining the call flow I want to achieve asynchronously. It may be done using a background thread. Commented Jul 20, 2016 at 19:02

2 Answers 2

1

In the case that both client and server are actually servers, you can create a resource method with some argument that would get the uri to send the Resource to, for instance by @HeaderParam, if initial request needs to be GET :

@GET
@Path("/{param}")
public Response getResource(@PathParam("param") String id, @HeaderParam(
"SendBackUri") String uri) {
    new Thread() {
       public void run() {
          Resource res = SomeBean().getResource(id);
          ClientBuilder.newClient().target(uri).request().buildPost(Entity.entity(res, MediaType.WILDCARD_TYPE)).invoke();
       }
    }.start();
    return Response.status(202).build();
}

EDIT: You can use ExecutorService:

@GET
@Path("/{param}")
public Response getResource(@PathParam("param") String id, @HeaderParam(
"SendBackUri") String uri) {
    executorService.execute(new Runnable() {
       public void run() {
          Resource res = SomeBean().getResource(id);
          Response response = ClientBuilder.newClient().target(uri).request().buildPost(Entity.entity(res, MediaType.WILDCARD_TYPE)).invoke();
          //deal with response
       }
    });
    return Response.status(202).build();
}

Whatever is in the runmethod of either Runnable or Thread is executed in a separate thread. Of course, in this thread, you can deal with Response from the POST request and you can react somehow based on returned status code if needed.


Your case of having two servers communicating with each other is possible, though not very common. If the client side is just a client, you can do asynchronous client request, such as:

Future<Response> f = ClientBuilder.newClient().target(uri).request().async().get();
//do some client specific stuff which you probably do when receive 202;
Response r = f.get(); //wait for response
//do whatever you would do with the resource

For server side, you can use your asynchronous method

public void getResource(@PathParam("param") String id,
                  final @Suspended AsyncResponse response)

as you have it.

Sign up to request clarification or add additional context in comments.

7 Comments

So the response of the POST request sent can also be handled in the same thread ? The new POST request will start a new thread ? I am not sure how threads will be managed here.
Also, shall we use ExecutorService for threads instead of starting a thread directly ?
Added to the answer
My server1 will receive the response in form of ACK. Now after that a POST request must land on it containing the response of initial request. How to achieve that call flow on server1 ?? Shall the POST request land on a separate Servlet or on the same class sending request
I am not sure I quite understand. The post request needs to target the uri of some resource on server1, given by the @HeaderParam in our example. The post request needs to contain both the data (Entity) and id, either in a form of path parameter (again) or some other param, so that server1 knows what the POSTed entity refers to.
|
1

If you want to return ACK to client and then want to process your request further, the following code snippet should work.

public static void main(String args[]) {
    Runnable thread = () -> {
        // you code goes here
        // which you want to execute in a new thread
    };
    // return response code / ACK
}

Here, the newly created thread will start execution and the main function will carry on with its execution without waiting for the thread to complete its execution.

You can have a similar implementation in your JAX-RS implementation.

2 Comments

Could you elaborate why can't we return the response through another request ? Something like in @jan.supol 's answer send a POST request ?
Actually, you can with the help of a new request when there is a server machine on the other end, as stated in your scenario. So going by my answer, you can start a new thread using Runnable and let that thread handle its work while you return an ACK and the thread can later POST data when it is ready.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.