/*==========================================================================*\ | $Id: JobBase.java,v 1.7 2012/01/05 19:49:57 stedwar2 Exp $ |*-------------------------------------------------------------------------*| | Copyright (C) 2008-2011 Virginia Tech | | This file is part of Web-CAT. | | Web-CAT is free software; you can redistribute it and/or modify | it under the terms of the GNU Affero General Public License as published | by the Free Software Foundation; either version 3 of the License, or | (at your option) any later version. | | Web-CAT is distributed in the hope that it will be useful, | but WITHOUT ANY WARRANTY; without even the implied warranty of | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | GNU General Public License for more details. | | You should have received a copy of the GNU Affero General Public License | along with Web-CAT; if not, see <http://www.gnu.org/licenses/>. \*==========================================================================*/ package org.webcat.jobqueue; import org.webcat.core.Application; import org.webcat.woextensions.ECAction; import static org.webcat.woextensions.ECAction.run; import org.webcat.woextensions.WCEC; import com.webobjects.eoaccess.EOGeneralAdaptorException; import com.webobjects.eocontrol.*; import com.webobjects.foundation.*; import er.extensions.eof.ERXEOAccessUtilities; // ------------------------------------------------------------------------- /** * This is the abstract base class for all "job" entities across all * Web-CAT subsystems. It is designed to be used with EO-style horizontal * inheritance. The corresponding EOModel definition provides the common * fields that all concrete subclasses will contain, although subclasses * can certainly add more as necessary. To create a job subclass, create * an entity the normal way, then set its parent entity to JobBase. To * generate SQL for the corresponding table, build off of the * {@link JobQueueDatabaseUpdates#createJobBaseTable(org.webcat.dbupdate.Database,String)} * method, which will generate the inherited field definitions for you. * * @author Stephen Edwards * @author Last changed by $Author: stedwar2 $ * @version $Revision: 1.7 $, $Date: 2012/01/05 19:49:57 $ */ public abstract class JobBase extends _JobBase { //~ Constructors .......................................................... // ---------------------------------------------------------- /** * Creates a new JobBase object. */ public JobBase() { super(); } //~ Public Methods ........................................................ // ---------------------------------------------------------- @Override public void setProgress(double value) { if (Double.isNaN(value) || Double.isInfinite(value) || value < 0.0) { value = 0.0; } else if (value > 1.0) { if (value <= 100.0) { value /= 100.0; } else { value = 1.0; } } super.setProgress(value); } // ---------------------------------------------------------- /** * A convenience method to get the job's current progress as an integer * percentage. * * @return the percentage of job progress completed */ public int progressPercentage() { return (int) (progress() * 100 + 0.5); } // ---------------------------------------------------------- /** * Checks to see if this job is available for running, and if so, * allocates it to the given worker. This sets the {@link #worker()} * relation to point to the worker thread on success, and returns true. * If the job is not available, it returns false. Note that if the * method succeeds, the EC containing this job will have its changes * saved as part of the process, in order to commit the new value of * worker() to the database. * * @param withWorker The worker thread that wishes to take on this job * @return True if the worker has been allocated this job, or false * if this worker cannot be given the job (because it has been * cancelled or paused, or because it has already been allocated * to another worker). */ public boolean volunteerToRun(WorkerDescriptor withWorker) { if (isCancelled() || !isReady()) { return false; } EOEditingContext ec = editingContext(); try { if (ec != null && worker() == null) { setSuspensionReason(null); setWorkerRelationship(withWorker); ec.saveChanges(); if (worker() == withWorker) { workerThread = (WorkerThread) Thread.currentThread(); return true; } } } catch (EOGeneralAdaptorException e) { // assume optimistic locking failure if (ec != null) { ec.revert(); } } return false; } // ---------------------------------------------------------- /** * Overridden to indicate that the job queue needs to be notified that a * job that wasn't ready has now become ready when the changes are made to * the editing context. */ @Override public void setIsReady(boolean value) { if (!isReady() && value) { log.debug("isReady for job " + id() + " transitioning to true; " + "setting flag to notify queue on next save"); queueNeedsNotified = true; } super.setIsReady(value); } // ---------------------------------------------------------- private void notifyQueueOfReadyJob() { log.debug(entityName() + " queue descriptor increasing job count to " + "trigger job dispatch"); queueNeedsNotified = false; run(new ECAction() { public void action() { QueueDescriptor queue = QueueDescriptor.descriptorFor( ec, entityName()); boolean saved = false; while (!saved) { try { queue.setNewestEntryId(id().longValue()); ec.saveChanges(); saved = true; } catch (EOGeneralAdaptorException e) { if (ERXEOAccessUtilities.isOptimisticLockingFailure(e)) { queue = (QueueDescriptor) ERXEOAccessUtilities.refetchFailedObject(ec, e); } } } log.debug(entityName() + " queue newest id now " + queue.newestEntryId()); }}); } // ---------------------------------------------------------- @Override public void didInsert() { super.didInsert(); if (queueNeedsNotified) { notifyQueueOfReadyJob(); } } // ---------------------------------------------------------- /** * Monitor the cancellation state of the job so that we can notify the * worker thread that the user wants to cancel this job. */ @Override public void didUpdate() { super.didUpdate(); if (!alreadyCancelled && isCancelled() && workerThread != null) { alreadyCancelled = true; workerThread.cancelJob(); workerThread = null; } if (queueNeedsNotified) { notifyQueueOfReadyJob(); } } // ---------------------------------------------------------- public String toString() { return userPresentableDescription(); } //~ Protected Methods ..................................................... //~ Static/instance variables ............................................. private transient boolean alreadyCancelled = false; private transient boolean queueNeedsNotified = false; private transient WorkerThread workerThread = null; }