/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.stanbol.commons.jobs.web.resources; import static javax.ws.rs.core.MediaType.TEXT_HTML; import java.net.URI; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; import javax.ws.rs.DELETE; import javax.ws.rs.GET; import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.WebApplicationException; import javax.ws.rs.core.Context; import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import org.apache.felix.scr.annotations.Component; import org.apache.felix.scr.annotations.Property; import org.apache.felix.scr.annotations.Reference; import org.apache.felix.scr.annotations.Service; import org.apache.stanbol.commons.web.base.resource.BaseStanbolResource; import org.apache.stanbol.commons.jobs.api.Job; import org.apache.stanbol.commons.jobs.api.JobInfo; import org.apache.stanbol.commons.jobs.api.JobManager; import org.apache.stanbol.commons.jobs.api.JobResult; import org.apache.stanbol.commons.jobs.impl.JobInfoImpl; import org.apache.stanbol.commons.web.viewable.Viewable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Manages Background Jobs * * @author enridaga * */ @Component @Service(Object.class) @Property(name = "javax.ws.rs", boolValue = true) @Path("/jobs") public class JobsResource extends BaseStanbolResource { private Logger log = LoggerFactory.getLogger(getClass()); @Context protected HttpHeaders headers; //private JobInfo info = null; @Reference private JobManager jobManager; @GET public Response get(){ return Response.ok(new Viewable("index",new ResultData() {})).build(); } /** * GET info about a Background Job * * @param id * @return Response */ @GET @Path("/{jid}") public Response get(@PathParam("jid") String id) { log.info("Called get() with id {}", id); // No id if(id == null || id.equals("")){ return Response.status(Response.Status.BAD_REQUEST).build(); } JobManager m = jobManager; // If the job exists if (m.hasJob(id)) { log.info("Found job with id {}", id); Future<?> f = m.ping(id); //this.info = new JobInfoImpl(); final JobInfo info = new JobInfoImpl(); if(f.isDone()){ // The job is finished if(f.isCancelled()){ // NOTE: Canceled jobs should never exist. // The web service remove any deleted process from the manager // If a process have been canceled programmatically, it cannot be managed by the service anymore // (except for DELETE) log.warn("Job with id {} have been canceled. Returning 404 Not found.", id); return Response.status(Response.Status.NOT_FOUND).build(); }else{ // Job is complete info.setFinished(); info.addMessage("You can remove this job using DELETE"); } }else{ // the job exists but it is not complete info.setRunning(); info.addMessage("You can interrupt this job using DELETE"); } // Returns 200, the job exists info.setOutputLocation(getPublicBaseUri() + m.getResultLocation(id)); if(isHTML()){ // Result as HTML return Response.ok(new Viewable("info", new JobsResultData(info))).build(); }else{ // Result as application/json, text/plain return Response.ok(info).build(); } } else { log.info("No job found with id {}", id); return Response.status(Response.Status.NOT_FOUND).build(); } } public class JobsResultData extends ResultData{ private JobInfo ji; public JobsResultData(JobInfo jinfo){ this.ji = jinfo; } public JobInfo getJobInfo(){ return ji; } } private boolean isHTML() { List<MediaType> mediaTypes = headers.getAcceptableMediaTypes(); Set<String> htmlformats = new HashSet<String>(); htmlformats.add(TEXT_HTML); for (MediaType t : mediaTypes) { String strty = t.toString(); log.info("Acceptable is {}", t); if (htmlformats.contains(strty)) { log.debug("Requested format is HTML {}", t); return true; } } return false; } /** * DELETE a background job. This method will find a job, * interrupt it if it is running, and removing it * from the {@see JobManager}. * * @param jid * @return */ @DELETE @Path("/{jid}") public Response delete(@PathParam(value = "jid") String jid){ log.info("Called DELETE ({})", jid); if(!jid.equals("")){ log.info("Looking for test job {}", jid); JobManager m = jobManager; // If the job exists if (m.hasJob(jid)){ log.info("Deleting Job id {}", jid); m.remove(jid); return Response.ok("Job deleted.").build(); }else { log.info("No job found with id {}", jid); return Response.status(Response.Status.NOT_FOUND).build(); } }else{ throw new WebApplicationException(Response.Status.BAD_REQUEST); } } /** * DELETE all background jobs. * * @return */ @DELETE public Response delete(){ log.info("Called DELETE all jobs"); JobManager manager = jobManager; manager.removeAll(); return Response.ok("All jobs have been deleted.").build(); } /** * Creates a new background job to be used to test the service. * This method is for testing the service and to provide a sample implementation * of a long term operation started form a rest service. * * @return */ @GET @Path("/test{jid: (/.*)?}") public Response test(@PathParam(value = "jid") String jid) { log.info("Called GET (create test job)"); // If an Id have been provided, check whether the job has finished and return the result if(!jid.equals("")){ log.info("Looking for test job {}", jid); JobManager m = jobManager; // Remove first slash from param value jid = jid.substring(1); // If the job exists if (m.hasJob(jid)){ log.info("Found job with id {}", jid); Future<?> f = m.ping(jid); if(f.isDone() && (!f.isCancelled())){ /** * We return OK with the result */ Object o; try { o = f.get(); if(o instanceof JobResult){ JobResult result = (JobResult) o; return Response.ok(result.getMessage()).build(); }else{ log.error("Job {} is not a test job", jid); throw new WebApplicationException(Response.Status.NOT_FOUND); } } catch (InterruptedException e) { log.error("Error: ",e); throw new WebApplicationException(Response.Status.INTERNAL_SERVER_ERROR); } catch (ExecutionException e) { log.error("Error: ",e); throw new WebApplicationException(Response.Status.INTERNAL_SERVER_ERROR); } }else{ /** * We return 404 with additional info (Content-Location, the related job resource) * * TODO * Change into json representations */ String location = getPublicBaseUri() + "jobs/" + jid; String info = new StringBuilder().append("Result not ready.\n").append("Job Location: ").append(location).toString(); return Response.status(404).header("Content-Location", location).header("Content-type","text/plain").entity(info).build(); } }else { log.info("No job found with id {}", jid); return Response.status(Response.Status.NOT_FOUND).build(); } }else{ // No id have been provided, we create a new test job JobManager m = jobManager; String id = m.execute(new Job() { @Override public JobResult call() throws Exception { for (int i = 0; i < 30; i++) { try { log.info("Test Process is working"); Thread.sleep(1000); } catch (InterruptedException ie) {} } return new JobResult(){ @Override public String getMessage() { return "This is a test job"; } @Override public boolean isSuccess() { return true; } }; } @Override public String buildResultLocation(String jobId) { return "jobs/test/" + jobId; } }); // This service returns 201 Created on success String location = getPublicBaseUri() + "jobs/" + id; String info = new StringBuilder().append("Job started.\n") .append("Location: ").append(location).toString(); return Response.created(URI.create(location)).header("Content-type","text/plain").entity(info).build(); } } }