Search code examples
javajakarta-eejerseypayara

Java jersey glassfish (payara) rest: get status on running async method


I've got this project I'm working on which requires a lot of processing and time and I wish to start the process with an API call to a backend.

Right now I have Jersey and Jackson with Maven in a development environment using Payara as the server.

I'm trying to make it so that the user can just press 'start' in a web interface to start the processing of the long task in the background and JSON requests can just fetch status updates until the task is finished.

Here is my (pretty awful) test code that I'm using to try and get an idea of the process. It doesn't matter too much if the task is shared between all users or not as the project is just a prototype.

ExecutorService task = Executors
        .newCachedThreadPool(new ThreadFactoryBuilder().setNameFormat("long-running-resource-executor-%d")
                .setUncaughtExceptionHandler(new JerseyProcessingUncaughtExceptionHandler()).build());

AsyncResponse asyncBla;

@GET
@Path("/expensive")
public void getExpensive(@Suspended final AsyncResponse async) {

    asyncBla = async;

    task.submit(new Runnable() {

        @Override
        public void run() {
            try {
                Thread.sleep(10000);
                System.out.println("Ba-boop");
            } catch (InterruptedException e) {
                System.out.println("barp");
            }
            // async.resume("Started");

        }

    });

    async.resume("Started");
}

@GET
@Path("/expensive/status")
public String getExpensiveStatus() {

    if (asyncBla == null)
        return "Nope.";

    return asyncBla.isDone();
}

Solution

  • Here's something you could try:

    @Singleton
    @Path("expensive-task")
    public class ExpensiveTaskResource {
    
        private ExecutorService executor;
    
        private Future<String> futureResult;
    
        @PostConstruct
        public void onCreate() {
            this.executor = Executors.newSingleThreadExecutor();
        }
    
        @POST
        public Response startTask() {
            futureResult = executor.submit(new ExpensiveTask());
            return Response.status(Status.ACCEPTED).build();
        }
    
        @GET
        public Response getResult() throws ExecutionException, InterruptedException {
            if (futureResult != null && futureResult.isDone()) {
                return Response.status(Status.OK).entity(futureResult.get()).build();
            } else {
                return Response.status(Status.FORBIDDEN).entity("Try later").build();
            }
        }
    
        @PreDestroy
        public void onDestroy() {
            this.executor.shutdownNow();
        }
    }
    
    public class ExpensiveTask implements Callable<String> {
    
        @Override
        public String call() throws Exception {
    
            try {
                Thread.sleep(10000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
    
            return "Task completed";
        }
    }