package com.indeed.proctor.webapp.controllers; import com.google.common.collect.ImmutableMap; import com.indeed.proctor.webapp.model.SessionViewModel; import com.indeed.proctor.webapp.model.WebappConfiguration; import com.indeed.proctor.webapp.views.JsonView; import org.apache.log4j.Logger; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.servlet.View; import java.util.List; import java.util.Map; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; /** * @author parker */ @Controller @RequestMapping({ "/rpc/jobs", "/proctor/rpc/jobs" }) public class BackgroundJobRpcController { private static final Logger LOGGER = Logger.getLogger(BackgroundJobRpcController.class); private final WebappConfiguration configuration; private final BackgroundJobManager manager; private final BackgroundJobFactory factory; @Autowired public BackgroundJobRpcController(final BackgroundJobManager manager, final WebappConfiguration configuration, final BackgroundJobFactory factory) { this.manager = manager; this.configuration = configuration; this.factory = factory; } @RequestMapping(value = "/status", method = RequestMethod.GET) public View doGetJobStatus(@RequestParam("id") final long jobId) { final BackgroundJob job = manager.getJobForId(jobId); if(job == null) { final String msg = "Failed to identify job for " + jobId; final JsonResponse<String> err = new JsonResponse<String>(msg, false, msg); return new JsonView(err); } else { final Future future = job.getFuture(); Object outcome = null; final long timeout = 100; final TimeUnit unit = TimeUnit.MILLISECONDS; try { if(future != null) { outcome = future.get(timeout, unit); } else { outcome = null; } } catch (InterruptedException exp) { LOGGER.warn("Interrupted during BackgroundJob.future.get(" + timeout + ", " + unit + ")"); } catch (TimeoutException exp) { // Expected if Future is not complete, no need to log anything } catch (ExecutionException exp) { // bummer... outcome = null; LOGGER.error("Exception during BackgroundJob.future.get(" + timeout + ", " + unit + ")", exp); } final Map<String, Object> result = buildJobJson(job, outcome); final JsonResponse<Map> response = new JsonResponse<Map>(result, true, null); return new JsonView(response); } } @RequestMapping(value = "/list", method = RequestMethod.GET) public String doGetJobList(final Model model) { final List<BackgroundJob> jobs = manager.getRecentJobs(); model.addAttribute("session", SessionViewModel.builder() .setUseCompiledCSS(configuration.isUseCompiledCSS()) .setUseCompiledJavaScript(configuration.isUseCompiledJavaScript()) .build()); model.addAttribute("jobs", jobs); return "jobs"; } @RequestMapping(value = "/cancel", method = RequestMethod.GET) public View doCancelJob(@RequestParam("id") final long jobId) { final BackgroundJob job = manager.getJobForId(jobId); if(job == null) { final String msg = "Failed to identify job for " + jobId; final JsonResponse<String> err = new JsonResponse<String>(msg, false, msg); return new JsonView(err); } else { if(job.getFuture() != null) { job.getFuture().cancel(true); } final Map<String, Object> result = buildJobJson(job); final JsonResponse<Map> response = new JsonResponse<Map>(result, true, null); return new JsonView(response); } } @RequestMapping(value = "/test", method = RequestMethod.GET) public View submitTestJob(@RequestParam(value = "ms", defaultValue = "1000") final long ms) { final long start = System.currentTimeMillis(); final long interval = 100; // log every 100 milliseconds final BackgroundJob<Boolean> job = factory.createBackgroundJob( "Sleeping for a total of " + ms + " ms", BackgroundJob.JobType.JOB_TEST, new BackgroundJobFactory.Executor<Boolean>() { @Override public Boolean execute(final BackgroundJob job) throws Exception { final long endms = start + ms; while (true) { long now = System.currentTimeMillis(); long sleepms = Math.min(interval, endms - now); if( sleepms > 0) { final double elapsed_sec = (now - start) / 1000.0; job.log(String.format("Elapsed = %.3f seconds, sleeping for %s ms", elapsed_sec, sleepms)); Thread.sleep(sleepms); } else { break; } } job.addUrl("http://www.indeed.com", "Indeed.com"); job.addUrl("http://www.google.com", "Google", "_blank"); return Boolean.TRUE; } } ); manager.submit(job); final JsonResponse<Map> response = new JsonResponse<Map>(buildJobJson(job), true, null); return new JsonView(response); } public static Map<String,Object> buildJobJson(final BackgroundJob job) { return buildJobJson(job, null); } public static Map<String,Object> buildJobJson(final BackgroundJob job, final Object outcome) { final ImmutableMap.Builder<String,Object> builder = ImmutableMap.builder(); builder.put("jobId", job.getId()) .put("status", job.getStatus()) .put("log", job.getLog()) .put("title", job.getTitle()) .put("running", job.isRunning()); if(outcome != null) { builder.put("outcome", outcome); } builder.put("urls", job.getUrls()); builder.put("endMessage", job.getEndMessage()); return builder.build(); } }