/** * Copyright (c) 2009 - 2012 Red Hat, Inc. * * This software is licensed to you under the GNU General Public License, * version 2 (GPLv2). There is NO WARRANTY for this software, express or * implied, including the implied warranties of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. You should have received a copy of GPLv2 * along with this software; if not, see * http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt. * * Red Hat trademarks are not licensed under GPLv2. No permission is * granted to use or replicate Red Hat trademarks that are incorporated * in this software or its documentation. */ package org.candlepin.resource; import org.candlepin.auth.Verify; import org.candlepin.common.exceptions.BadRequestException; import org.candlepin.common.exceptions.IseException; import org.candlepin.common.exceptions.NotFoundException; import org.candlepin.config.ConfigProperties; import org.candlepin.model.CandlepinQuery; import org.candlepin.model.JobCurator; import org.candlepin.model.SchedulerStatus; import org.candlepin.pinsetter.core.PinsetterException; import org.candlepin.pinsetter.core.PinsetterKernel; import org.candlepin.pinsetter.core.model.JobStatus; import org.candlepin.pinsetter.core.model.JobStatus.JobState; import org.candlepin.pinsetter.tasks.KingpinJob; import org.candlepin.util.ElementTransformer; import org.candlepin.util.Util; import com.google.inject.Inject; import org.apache.commons.lang.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.xnap.commons.i18n.I18n; import java.util.ArrayList; import java.util.List; import javax.ws.rs.Consumes; import javax.ws.rs.DELETE; import javax.ws.rs.DefaultValue; import javax.ws.rs.GET; import javax.ws.rs.POST; import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; import javax.ws.rs.QueryParam; import javax.ws.rs.core.MediaType; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import io.swagger.annotations.ApiResponse; import io.swagger.annotations.ApiResponses; import io.swagger.annotations.Authorization; /** * JobResource */ @Path("/jobs") @Api(value = "jobs", authorizations = { @Authorization("basic") }) public class JobResource { private JobCurator curator; private PinsetterKernel pk; private I18n i18n; private static Logger log = LoggerFactory.getLogger(JobResource.class); @Inject public JobResource(JobCurator curator, PinsetterKernel pk, I18n i18n) { this.curator = curator; this.pk = pk; this.i18n = i18n; } /** * Returns false if only one of the strings is not empty, otherwise * returns true. * @param owner param1 * @param uuid param2 * @param pname param3 * @return a boolean */ private boolean ensureOnlyOne(String owner, String uuid, String pname) { String[] params = new String[3]; params[0] = owner; params[1] = uuid; params[2] = pname; boolean found = false; for (String s : params) { if (found && !StringUtils.isEmpty(s)) { return false; } else if (!StringUtils.isEmpty(s)) { found = true; } } return true; } @ApiOperation(notes = "Retrieves a list of Job Status", value = "getStatuses", response = JobStatus.class, responseContainer = "list") @ApiResponses({ @ApiResponse(code = 400, message = ""), @ApiResponse(code = 404, message = "") }) @GET @Produces(MediaType.APPLICATION_JSON) public CandlepinQuery<JobStatus> getStatuses( @QueryParam("owner") String ownerKey, @QueryParam("consumer") String uuid, @QueryParam("principal") String principalName) { boolean allParamsEmpty = StringUtils.isEmpty(ownerKey) && StringUtils.isEmpty(uuid) && StringUtils.isEmpty(principalName); // make sure we only specified one if (allParamsEmpty || !ensureOnlyOne(ownerKey, uuid, principalName)) { throw new BadRequestException(i18n.tr("You must specify exactly " + "one of owner key, unit UUID, or principal name.")); } CandlepinQuery<JobStatus> statuses = null; if (!StringUtils.isEmpty(ownerKey)) { statuses = curator.findByOwnerKey(ownerKey); } else if (!StringUtils.isEmpty(uuid)) { statuses = curator.findByConsumerUuid(uuid); } else if (!StringUtils.isEmpty(principalName)) { statuses = curator.findByPrincipalName(principalName); } if (statuses == null) { throw new NotFoundException(""); } return statuses.transform(new ElementTransformer<JobStatus, JobStatus>() { @Override public JobStatus transform(JobStatus jobStatus) { return jobStatus.cloakResultData(true); } }); } @ApiOperation(notes = "Retrieves the Scheduler Status", value = "getSchedulerStatus") @GET @Path("scheduler") @Produces(MediaType.APPLICATION_JSON) public SchedulerStatus getSchedulerStatus() { SchedulerStatus ss = new SchedulerStatus(); try { ss.setRunning(pk.getSchedulerStatus()); } catch (PinsetterException e) { // TODO Auto-generated catch block e.printStackTrace(); } return ss; } @ApiOperation(notes = "Updates the Scheduler Status", value = "setSchedulerStatus") @ApiResponses({ @ApiResponse(code = 500, message = "") }) @POST @Path("scheduler") @Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON) public SchedulerStatus setSchedulerStatus(boolean running) { try { if (running) { pk.unpauseScheduler(); } else { pk.pauseScheduler(); } } catch (PinsetterException pe) { throw new IseException(i18n.tr("Error setting scheduler status")); } return getSchedulerStatus(); } @ApiOperation(notes = "Triggers select asynchronous jobs", value = "schedule") @ApiResponses({ @ApiResponse(code = 400, message = ""), @ApiResponse(code = 500, message = "") }) @POST @Path("schedule/{task}") @Consumes(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON) @SuppressWarnings("unchecked") public JobStatus schedule(@PathParam("task") String task) { /* * at the time of implementing this API, the only jobs that * are permissible are cron jobs. */ String className = "org.candlepin.pinsetter.tasks." + task; try { boolean allowed = false; for (String permissibleJob : ConfigProperties.DEFAULT_TASK_LIST) { if (className.equalsIgnoreCase(permissibleJob)) { allowed = true; // helps ignore case className = permissibleJob; } } if (allowed) { Class taskClass = Class.forName(className); return pk.scheduleSingleJob((Class<? extends KingpinJob>) taskClass, Util.generateUUID()); } else { throw new BadRequestException(i18n.tr("Not a permissible job: {0}. Only {1} are permissible", task, prettyPrintJobs(ConfigProperties.DEFAULT_TASK_LIST))); } } catch (ClassNotFoundException e) { throw new IseException(i18n.tr("Error trying to schedule {0}: {1}", className, e.getMessage())); } catch (PinsetterException e) { throw new IseException(i18n.tr("Error trying to schedule {0}: {1}", className, e.getMessage())); } } private String prettyPrintJobs(String... jobs) { List<String> jobNames = new ArrayList<String>(); for (String job : jobs) { jobNames.add(StringUtils.substringAfterLast(job, ".")); } return StringUtils.join(jobNames, ", "); } @ApiOperation(notes = "Retrieves a single Job Status", value = "getStatus") @GET @Path("/{job_id}") @Produces(MediaType.APPLICATION_JSON) public JobStatus getStatus(@PathParam("job_id") @Verify(JobStatus.class) String jobId, @QueryParam("result_data") @DefaultValue("false") boolean resultData) { JobStatus js = curator.find(jobId); js.cloakResultData(!resultData); return js; } @ApiOperation(notes = "Cancels a Job Status", value = "cancel") @ApiResponses({ @ApiResponse(code = 400, message = ""), @ApiResponse(code = 404, message = "") }) @DELETE @Path("/{job_id}") @Produces(MediaType.APPLICATION_JSON) public JobStatus cancel(@PathParam("job_id") @Verify(JobStatus.class) String jobId) { JobStatus j = curator.find(jobId); if (j.getState().equals(JobState.CANCELED)) { throw new BadRequestException(i18n.tr("job already canceled")); } if (j.isDone()) { throw new BadRequestException(i18n.tr("cannot cancel a job that is in a finished state")); } return curator.cancel(jobId); } @ApiOperation(notes = "Retrieves a Job Status and Removes if finished", value = "getStatusAndDeleteIfFinished") @POST @Path("/{job_id}") @Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.WILDCARD) public JobStatus getStatusAndDeleteIfFinished( @PathParam("job_id") @Verify(JobStatus.class) String jobId) { JobStatus status = curator.find(jobId); if (status != null && status.getState() == JobState.FINISHED) { curator.delete(status); } return status; } }