/**
* 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.pinsetter.core;
import org.candlepin.auth.Principal;
import org.candlepin.model.JobCurator;
import org.candlepin.pinsetter.core.model.JobStatus;
import org.candlepin.pinsetter.core.model.JobStatus.JobState;
import org.candlepin.pinsetter.tasks.UniqueByOwnerJob;
import com.google.inject.Inject;
import com.google.inject.persist.Transactional;
import com.google.inject.persist.UnitOfWork;
import org.jboss.resteasy.spi.ResteasyProviderFactory;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.quartz.JobKey;
import org.quartz.JobListener;
import org.quartz.SchedulerException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* This component receives events around job status and performs actions to
* allow for the job in question to run outside of a request scope, as well as
* record the status of the job for later retrieval.
*/
public class PinsetterJobListener implements JobListener {
private static Logger log = LoggerFactory.getLogger(PinsetterJobListener.class);
public static final String LISTENER_NAME = "Pinsetter Job Listener";
public static final String PRINCIPAL_KEY = "principal_key";
private JobCurator curator;
// this is a separate unitOfWork and units of work from the actual pinsetter
// job because we want to tie this closer to the quartz execution, rather than
// job execution.
private UnitOfWork unitOfWork;
@Inject
public PinsetterJobListener(JobCurator curator, UnitOfWork unitOfWork) {
this.curator = curator;
this.unitOfWork = unitOfWork;
}
@Override
public String getName() {
return LISTENER_NAME;
}
@Override
public void jobToBeExecuted(JobExecutionContext context) {
Principal principal = (Principal) context.getMergedJobDataMap().get(PRINCIPAL_KEY);
ResteasyProviderFactory.pushContext(Principal.class, principal);
try {
unitOfWork.begin();
updateJob(context);
}
catch (Exception e) {
log.error("jobToBeExecuted encountered a problem. Usually means " +
"there was a problem storing the job status. Job will run.", e);
}
finally {
unitOfWork.end();
}
}
@Override
public void jobExecutionVetoed(JobExecutionContext context) {
// Do nothing sentence
}
@Override
public void jobWasExecuted(JobExecutionContext context,
JobExecutionException exception) {
deleteDetail(context);
try {
unitOfWork.begin();
updateJob(context, exception);
}
catch (Exception e) {
if (UniqueByOwnerJob.class.isAssignableFrom(
context.getJobDetail().getJobClass())) {
log.error("jobWasExecuted encountered a problem on a blocking job." +
" This can block other jobs. Marking finished, if possible", e);
try {
//This time only update the state so it doesn't block other jobs
curator.cancelNoReturn(context.getJobDetail().getKey().getName());
}
catch (Exception ex) {
log.error("Failed again to cancel status: " +
context.getJobDetail().getKey().getName(), ex);
}
}
else {
log.error("jobWasExecuted encountered a problem. Usually means " +
"there was a problem storing the job status. Job finished ok.", e);
}
}
finally {
unitOfWork.end();
ResteasyProviderFactory.popContextData(Principal.class);
}
}
private void updateJob(JobExecutionContext ctx) {
updateJob(ctx, null);
}
@Transactional
private void updateJob(JobExecutionContext ctx, JobExecutionException exc) {
JobStatus status = curator.find(ctx.getJobDetail().getKey().getName());
if (status != null) {
if (exc != null) {
log.error("Job [" + status.getId() + "] failed." , exc);
status.setState(JobState.FAILED);
status.setResult(exc.getMessage());
}
else {
status.update(ctx);
}
curator.merge(status);
}
else {
log.debug("No jobinfo found for job: " + ctx);
}
}
private void deleteDetail(JobExecutionContext cx) {
JobKey key = cx.getJobDetail().getKey();
if (key.getGroup().equals(PinsetterKernel.SINGLE_JOB_GROUP)) {
try {
cx.getScheduler().deleteJob(key);
}
catch (SchedulerException e1) {
// Should fail if the job isn't durable
}
}
}
}