package uws.job; /* * This file is part of UWSLibrary. * * UWSLibrary is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * UWSLibrary 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with UWSLibrary. If not, see <http://www.gnu.org/licenses/>. * * Copyright 2012-2016 - UDS/Centre de DonnĂ©es astronomiques de Strasbourg (CDS), * Astronomisches Rechen Institut (ARI) */ import java.io.IOException; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.Date; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.Vector; import javax.servlet.ServletOutputStream; import uws.ISO8601Format; import uws.UWSException; import uws.UWSExceptionFactory; import uws.UWSToolBox; import uws.job.manager.ExecutionManager; import uws.job.parameters.UWSParameters; import uws.job.serializer.UWSSerializer; import uws.job.user.JobOwner; import uws.service.UWS; import uws.service.UWSFactory; import uws.service.UWSUrl; import uws.service.file.UWSFileManager; import uws.service.log.UWSLog; import uws.service.log.UWSLog.LogLevel; import uws.service.request.UploadFile; /** * <h3>Brief description</h3> * * <p>Default implementation of a job of the UWS pattern.</p> * * <h3>Some attributes comments</h3> * * <ul> * <li> * The job attributes <i>startTime</i> and <i>endTime</i> are automatically managed by {@link UWSJob}. You don't have to do anything ! * The date/time format is managed automatically by the library and can not be customized since it is imposed by the UWS * protocol definition: ISO-8601. * </li> * <br /> * <li>Once set, the <i>destruction</i> and the <i>executionDuration</i> attributes are automatically managed. That is to say: * <ul> * <li><u>if the destruction time is reached:</u> the job stops and it is destroyed by its job list</li> * <li><u>if the execution duration is elapsed:</u> the job stops and the phase is put to {@link ExecutionPhase#ABORTED ABORTED}.</li> * </ul> * </li> * <br /> * <li> * <u>The <i>owner</i> attribute is set at the job creation and can not be changed after</u> ! If no owner is given at the job creation, * its default value is <i>null</i>. * </li> * <br /> * <li> * If your job is executable, do not forget to set the <i>quote</i> parameter * ONLY by using the {@link #setQuote(long)} method (a negative value or {@link #QUOTE_NOT_KNOWN} value * indicates the quote is not known ; {@link #QUOTE_NOT_KNOWN} is the default value). * </li> * </ul> * * <h3>More details</h3> * * <ul> * <li> * <b>{@link #generateJobId()}:</b> * This function is called at the construction of any {@link UWSJob}. It allows to generate a unique job ID. * By default: time (in milliseconds) + a upper-case letter (A, B, C, ....). * <u>If you want customizing the job ID of your jobs</u>, you need to override this function or to use the new function * {@link #UWSJob(JobOwner, UWSParameters, String)}. * </li> * <br /> * <li> * <b>{@link #clearResources()}:</b> * This method is called <u>only at the destruction of the job</u>. * By default, the job is stopped (if running), thread resources are freed, * the job is removed from its jobs list and result/error files are deleted. * </li> * <br /> * <li> * <b>{@link #setPhaseManager(JobPhase)}:</b> * Lets customizing the default behaviors of all the execution phases for any job instance. * For more details see {@link JobPhase}. * </li> * <br /> * <li> * <b>{@link #addObserver(JobObserver)}:</b> * An instance of any kind of AbstractJob can be observed by objects which implements {@link JobObserver} (i.e. {@link uws.service.UWSService}). * Observers are notified at any change of the execution phase. * </li> * </ul> * * @author Grégory Mantelet (CDS;ARI) * @version 4.2 (01/2016) */ public class UWSJob extends SerializableUWSObject { private static final long serialVersionUID = 1L; /* ********* */ /* CONSTANTS */ /* ********* */ /** Name of the parameter <i>ACTION</i>. */ public static final String PARAM_ACTION = "ACTION"; /** Name of the DELETE action. */ public static final String ACTION_DELETE = "DELETE"; /** Name of the parameter <i>jobId</i>. */ public static final String PARAM_JOB_ID = "jobId"; /** Name of the parameter <i>runId</i>. */ public static final String PARAM_RUN_ID = "runId"; /** Name of the parameter <i>owner</i>. */ public static final String PARAM_OWNER = "owner"; /** Name of the parameter <i>phase</i>. */ public static final String PARAM_PHASE = "phase"; /** Value of the parameter <i>phase</i> which starts the job. */ public static final String PHASE_RUN = "RUN"; /** Value of the parameter <i>phase</i> which aborts the job. */ public static final String PHASE_ABORT = "ABORT"; /** Name of the parameter <i>quote</i>. */ public static final String PARAM_QUOTE = "quote"; /** Name of the parameter <i>startTime</i>. */ public static final String PARAM_START_TIME = "startTime"; /** Name of the parameter <i>endTime</i>. */ public static final String PARAM_END_TIME = "endTime"; /** Name of the parameter <i>executionDuration</i>. */ public static final String PARAM_EXECUTION_DURATION = "executionDuration"; /** Name of the parameter <i>destructionTime</i>. */ public static final String PARAM_DESTRUCTION_TIME = "destruction"; /** Name of the parameter <i>errorSummary</i>. */ public static final String PARAM_ERROR_SUMMARY = "error"; /** Name of the parameter <i>otherParameters</i>. */ public static final String PARAM_PARAMETERS = "parameters"; /** Name of the parameter <i>results</i>. */ public static final String PARAM_RESULTS = "results"; /** Default value of {@link #owner} if no ID are given at the job creation. */ public final static String ANONYMOUS_OWNER = "anonymous"; /** Default date format pattern. * @deprecated Replaced by {@link ISO8601Format}.*/ @Deprecated public static final String DEFAULT_DATE_FORMAT = "yyyy-MM-dd'T'HH:mm:ss.SSSZ"; /** The quote value that indicates the quote of this job is not known. */ public static final long QUOTE_NOT_KNOWN = -1; /** The duration that implies an unlimited execution duration. */ public final static long UNLIMITED_DURATION = 0; /* ********* */ /* VARIABLES */ /* ********* */ /** The last generated job ID. <b>It SHOULD be used ONLY by the function {@link #generateJobId()} !</b> */ protected static String lastId = System.currentTimeMillis() + "A"; /** The identifier of the job (it MUST be different from any other job).<BR /> * <i><u>Note:</u> It is assigned automatically at the job creation in any job constructor * by the function {@link #generateJobId()}. * To change the way this ID is generated or its format you must override this function.</i> */ protected final String jobId; /** The identifier of the creator of this job.<BR /> * <i><u>Note:</u> This object will not exist for all invocations of the UWS conformant protocol, * but only in cases where the access to the service is authenticated.</i> */ protected final JobOwner owner; /** The jobs list which is supposed to managed this job. */ private JobList myJobList = null; /** * <p>The current phase of the job.</p> * <i><u>Remember:</u> A job is treated as a state machine thanks to this attribute. * <ul> * <li>A successful job will normally progress through the {@link ExecutionPhase#PENDING PENDING}, * {@link ExecutionPhase#QUEUED QUEUED}, {@link ExecutionPhase#EXECUTING EXECUTING}, {@link ExecutionPhase#COMPLETED COMPLETED} * phases in that order.</li> * <li>At any time before the {@link ExecutionPhase#COMPLETED COMPLETED} phase a job may either be {@link ExecutionPhase#ABORTED ABORTED} * or may suffer an {@link ExecutionPhase#ERROR ERROR}.</li> * <li>If the UWS reports an {@link ExecutionPhase#UNKNOWN UNKNOWN} phase, then all the client can do is re-query the phase until a known phase is reported.</li> * <li>A UWS may place a job in a {@link ExecutionPhase#HELD HELD} phase on receipt of a PHASE=RUN request it for some reason the job cannot be immediately queued * - in this case it is the responsibility of the client to request PHASE=RUN again at some later time.</li> * </ul></i> */ private JobPhase phase; /** The used date formatter. * @deprecated Replaced by {@link ISO8601Format}. */ @Deprecated public static final DateFormat dateFormat = new SimpleDateFormat(DEFAULT_DATE_FORMAT); /** * This time (in seconds) predicts when the job is likely to complete.<br /> * <b>It CAN NOT be changed after the job creation !<br /> * <i>By default if no ID is given, {@link #quote} is set to {@link #QUOTE_NOT_KNOWN} (= {@value #QUOTE_NOT_KNOWN}).</i></b> */ private long quote = QUOTE_NOT_KNOWN; /** The time at which the job execution started. */ private Date startTime = null; /** The time at which the job execution ended. */ private Date endTime = null; /** <p>This error summary gives a human-readable error message for the underlying job.</p> * <i><u>Note:</u> This object is intended to be a detailed error message, and consequently, * might be a large piece of text such as a stack trace.</i> */ protected ErrorSummary errorSummary = null; /** This is a list of all results of this job. */ protected Map<String,Result> results; /** List of all input parameters (UWS standard and non-standard parameters). */ protected final UWSParameters inputParams; /** The thread to start for executing the job. */ protected transient JobThread thread = null; /** The time (in ms) to wait the end of the thread after an interruption. */ protected long waitForStop = 1000; /** Objects which want to be notified at each modification of the execution phase of this job. */ private Vector<JobObserver> observers = new Vector<JobObserver>(); /** If this job has been restored, this attribute should be set with the date of its restoration. */ private final Date restorationDate; /* ************ */ /* CONSTRUCTORS */ /* ************ */ /** * <p>Builds a job with no owner from a map of all parameters (UWS and additional parameters).</p> * * <p><i><u>Note:</u> if the parameter {@link UWSJob#PARAM_PHASE} (</i>phase<i>) is given with the value {@link UWSJob#PHASE_RUN} * the job execution starts immediately after the job has been added to a job list or after {@link #applyPhaseParam(JobOwner)} is called.</i></p> * * @param params UWS standard and non-standard parameters. * * @see UWSJob#UWSJob(JobOwner, UWSParameters) */ public UWSJob(final UWSParameters params){ this(null, params); } /** * <p>Builds a job of the given owner and from a map of all parameters (UWS and additional parameters).</p> * * <p><i><u>Note:</u> if the parameter {@link #PARAM_PHASE} (</i>phase<i>) is given with the value {@link #PHASE_RUN} * the job execution starts immediately after the job has been added to a job list or after {@link #applyPhaseParam(JobOwner)} is called.</i></p> * * @param owner Job.owner ({@link #PARAM_OWNER}). * @param params UWS standard and non-standard parameters. * * @see UWSParameters#init() */ public UWSJob(JobOwner owner, final UWSParameters params){ this.owner = owner; phase = new JobPhase(this); results = new HashMap<String,Result>(); inputParams = params; inputParams.init(); jobId = generateJobId(); restorationDate = null; // Move all uploaded files in a location related with this job: Iterator<UploadFile> files = inputParams.getFiles(); while(files.hasNext()){ try{ files.next().move(this); }catch(IOException ioe){} } } /** * <p>Builds a job of the given owner and from a map of all parameters (UWS and additional parameters). * The given HTTP request ID will be used as Job ID if not already used by another job.</p> * * <p><i><u>Note:</u> if the parameter {@link #PARAM_PHASE} (</i>phase<i>) is given with the value {@link #PHASE_RUN} * the job execution starts immediately after the job has been added to a job list or after {@link #applyPhaseParam(JobOwner)} is called.</i></p> * * @param owner Job.owner ({@link #PARAM_OWNER}). * @param params UWS standard and non-standard parameters. * @param requestID ID of the HTTP request which has initiated the creation of this job. * <i>Note: if NULL, empty or already used, a job ID will be generated thanks to {@link #generateJobId()}.</i> * * @see UWSParameters#init() * * @since 4.2 */ public UWSJob(JobOwner owner, final UWSParameters params, final String requestID){ this.owner = owner; phase = new JobPhase(this); results = new HashMap<String,Result>(); inputParams = params; inputParams.init(); // Set the Job ID with the value of the HTTP request ID (if not already used by a job): synchronized(lastId){ if (requestID == null || requestID.trim().length() == 0 || lastId.equals(requestID)) jobId = generateJobId(); else{ jobId = requestID; lastId = requestID; } } restorationDate = null; // Move all uploaded files in a location related with this job: Iterator<UploadFile> files = inputParams.getFiles(); while(files.hasNext()){ try{ files.next().move(this); }catch(IOException ioe){} } } /** * <p><b>CONSTRUCTOR TO USE TO RESTORE A JOB whatever is its phase.</b></p> * * <p>Builds a job of the given owner with all the given parameter.</p> * * <p><i> * <u>Note:</u> The job phase is automatically set in function of the last parameters (startTime, endTime, results and error). * Only the following execution phase are possible: PENDING, ABORTED, ERROR and COMPLETED. * </i></p> * * @param jobID The ID of this job (NOT NULL). * @param owner Its owner. * @param params UWS standard and non-standard parameters. * @param quote Its quote (in seconds). * @param startTime Its start time if it has already been started. * @param endTime Its end time if it is already finished. * @param results Its results (if phase=COMPLETED). * @param error Its error (if phase=ERROR). * * @throws NullPointerException If the given ID is NULL. */ public UWSJob(final String jobID, final JobOwner owner, final UWSParameters params, final long quote, final long startTime, final long endTime, final List<Result> results, final ErrorSummary error) throws NullPointerException{ if (jobID == null) throw new NullPointerException("Missing job ID => impossible to build a Job without a valid ID!"); this.jobId = jobID; this.owner = owner; this.quote = quote; if (startTime > 0) this.startTime = new Date(startTime); if (endTime > 0) this.endTime = new Date(endTime); this.results = new HashMap<String,Result>(); if (results != null){ for(Result r : results){ if (r != null) this.results.put(r.getId(), r); } } errorSummary = error; this.phase = new JobPhase(this); inputParams = params; params.init(); ExecutionPhase p = ExecutionPhase.PENDING; if (startTime > 0 && endTime > 0){ if (this.results.isEmpty() && this.errorSummary == null) p = ExecutionPhase.ABORTED; else if (!this.results.isEmpty()) p = ExecutionPhase.COMPLETED; else if (this.errorSummary != null) p = ExecutionPhase.ERROR; } if (phase != null){ try{ setPhase(p, true); }catch(UWSException ue){ // Can never append because the "force" parameter is true! } } restorationDate = new Date(); } /** * <p>This function lets generating a unique ID.</p> * * <p><i><b>By default:</b> System.currentTimeMillis()+UpperCharacter (UpperCharacter: one upper-case character chosen in order to guarantee the unicity of the ID: A, B, C, ....)</i></p> * * <p><i><u>note: </u> DO NOT USE in this function any of the following functions: {@link #getLogger()}, * {@link #getFileManager()} and {@link #getFactory()}. All of them will return NULL, because this job does not * yet know its jobs list (which is needed to know the UWS and so, all of the objects returned by these functions).</i></p> * * @return A unique job identifier. */ protected String generateJobId(){ synchronized(lastId){ String generatedId = System.currentTimeMillis() + "A"; if (lastId != null){ while(lastId.equals(generatedId)) generatedId = generatedId.substring(0, generatedId.length() - 1) + (char)(generatedId.charAt(generatedId.length() - 1) + 1); } lastId = generatedId; return generatedId; } } /** * <p>Gets the value of the specified parameter.</p> * * <p><i><u>note:</u> No case sensitivity for the UWS parameters ON THE CONTRARY TO the names of the additional parameters (which are case sensitive).</i></p> * * @param name Name of the parameter to get. * * @return Its value or <i>null</i> if there is no parameter with the given name or if the value is <i>null</i>. * * @see UWSParameters#get(String) */ public Object getParameter(String name){ if (name == null || name.trim().isEmpty()) return null; name = name.trim(); if (name.equalsIgnoreCase(PARAM_JOB_ID)) return jobId; else if (name.equalsIgnoreCase(PARAM_OWNER)) return owner; else if (name.equalsIgnoreCase(PARAM_PHASE)) return phase.getPhase(); else if (name.equalsIgnoreCase(PARAM_QUOTE)) return quote; else if (name.equalsIgnoreCase(PARAM_START_TIME)) return startTime; else if (name.equalsIgnoreCase(PARAM_END_TIME)) return endTime; else return inputParams.get(name); } /** * <p>Looks for an additional parameters which corresponds to the Execution Phase. If it exists and:</p> * <ul> * <li> is equals to {@link UWSJob#PHASE_RUN RUN} => remove it from the attribute {@link #inputParams} and start the job.</li> * <li> is equals to {@link UWSJob#PHASE_ABORT ABORT} => remove it from the attribute {@link #inputParams} and abort the job.</li> * <li> is another value => the attribute is though removed from the attribute {@link #inputParams} but nothing is done.</li> * </ul> * * @param user The user who asks to apply the phase parameter (start/abort). (may be NULL) * * @throws UWSException If it is impossible the state of this job (into EXECUTING or ABORTED) * or if the given user is not allowed to execute this job. * * @see UWSParameters#hasInputPhase() * @see UWSParameters#getInputPhase() * @see #start() * @see #abort() */ public void applyPhaseParam(final JobOwner user) throws UWSException{ synchronized(inputParams){ if (inputParams.hasInputPhase()){ String inputPhase = inputParams.getInputPhase(); if (inputPhase.equalsIgnoreCase(PHASE_RUN)){ // Forbids the execution if the user has not the required permission: if (user != null && !user.equals(owner) && !user.hasExecutePermission(this)) throw new UWSException(UWSException.PERMISSION_DENIED, UWSExceptionFactory.executePermissionDenied(user, jobId)); start(); }else if (inputPhase.equalsIgnoreCase(PHASE_ABORT)){ // Forbids the execution if the user has not the required permission: if (user != null && !user.equals(owner) && !user.hasExecutePermission(this)) throw new UWSException(UWSException.PERMISSION_DENIED, UWSExceptionFactory.executePermissionDenied(user, jobId)); abort(); } } } } /* ***************** */ /* GETTERS & SETTERS */ /* ***************** */ /** * Gets the file manager used in this job. * * @return Its file manager or <i>null</i> if this job is not into a {@link JobList} or if this jobs list is not into a {@link UWS}. * * @see JobList#getUWS() * @see uws.service.UWS#getFileManager() */ public final UWSFileManager getFileManager(){ if (myJobList != null && myJobList.getUWS() != null) return myJobList.getUWS().getFileManager(); else return null; } /** * Gets the logger of its UWS or a default one if the job list or the UWS is unknown. * * @return A logger. * * @see JobList#getUWS() * @see uws.service.UWS#getLogger() * @see UWSToolBox#getDefaultLogger() */ public UWSLog getLogger(){ if (myJobList != null && myJobList.getUWS() != null) return myJobList.getUWS().getLogger(); else return UWSToolBox.getDefaultLogger(); } /** * Gets the factory to use to create the thread to execute when this job starts. * * @return The factory to use to create a {@link JobThread}. */ public final UWSFactory getFactory(){ if (myJobList != null && myJobList.getUWS() != null) return myJobList.getUWS().getFactory(); else return null; } /** * Gets the date of the restoration of this job. * * @return Date of its restoration. */ public final Date getRestorationDate(){ return restorationDate; } /** * Gets the phase in which this job is now. * * @return The current phase of this job. * * @see JobPhase#getPhase() */ public final ExecutionPhase getPhase(){ return phase.getPhase(); } /** * <p>Sets the current phase of this job.</p> * * <p><b><u>IMPORTANT:</u></b> * <ul><li>The order of all phases must be respected:<i> BY DEFAULT</i> <BR /> {@link ExecutionPhase#PENDING PENDING} ---> {@link ExecutionPhase#QUEUED QUEUED} ---> {@link ExecutionPhase#EXECUTING EXECUTING} ---> {@link ExecutionPhase#COMPLETED COMPLETED}.</li> * <li>The only way to go to the {@link ExecutionPhase#EXECUTING EXECUTING} phase is by sending a POST query with the value {@link UWSJob#PHASE_RUN RUN} for the parameter {@link UWSJob#PARAM_PHASE PHASE}.</li> * <li>The only way to go to the {@link ExecutionPhase#ABORTED ABORTED} phase is by sending a POST query with the value {@link UWSJob#PHASE_ABORT ABORT} for the parameter {@link UWSJob#PARAM_PHASE PHASE}.</li> * <li>The start time and the end time are set automatically when the phase is set to {@link ExecutionPhase#EXECUTING EXECUTING} and {@link ExecutionPhase#COMPLETED COMPLETED}, {@link ExecutionPhase#ABORTED ABORTED} or {@link ExecutionPhase#ERROR ERROR}</li> *</ul></p> * * @param p The phase to set for this job. * * @throws UWSException If the given phase does not respect the job's phases order. * * @see #setPhase(ExecutionPhase, boolean) */ public final void setPhase(ExecutionPhase p) throws UWSException{ setPhase(p, false); } /** * <p>Sets the current phase of this job, respecting or not the imposed order.</p> * * <p><b><u>IMPORTANT:</u></b> * <ul><li><b><u>If the parameter <i>force</i> is <i>false</i></u></b>, the order of all phases must be respected:<BR /> {@link ExecutionPhase#PENDING PENDING} ---> {@link ExecutionPhase#QUEUED QUEUED} ---> {@link ExecutionPhase#EXECUTING EXECUTING} ---> {@link ExecutionPhase#COMPLETED COMPLETED}.</li> * <li>The only way to go to the {@link ExecutionPhase#EXECUTING EXECUTING} phase is by sending a POST query with the value {@link UWSJob#PHASE_RUN RUN} for the parameter {@link UWSJob#PARAM_PHASE PARAM_PHASE}.</li> * <li>The only way to go to the {@link ExecutionPhase#ABORTED ABORTED} phase is by sending a POST query with the value {@link UWSJob#PHASE_ABORT ABORT} for the parameter {@link UWSJob#PARAM_PHASE PARAM_PHASE}.</li> * <li>The start time and the end time are set automatically when the phase is set to {@link ExecutionPhase#EXECUTING EXECUTING} and {@link ExecutionPhase#COMPLETED COMPLETED}, {@link ExecutionPhase#ABORTED ABORTED} or {@link ExecutionPhase#ERROR ERROR}</li> *</ul></p> * * @param p The phase to set for this job. * @param force <i>true</i> to impose the given execution phase, <i>false</i> to take into account the order of all phases. * * @throws UWSException If the given phase does not respect the job's phases order. * * @see JobPhase#setPhase(ExecutionPhase, boolean) * @see JobPhase#isFinished() * @see ExecutionManager#remove(UWSJob) * @see #notifyObservers(ExecutionPhase) */ public final void setPhase(ExecutionPhase p, boolean force) throws UWSException{ synchronized(phase){ ExecutionPhase oldPhase = phase.getPhase(); phase.setPhase(p, force); if (!force) getLogger().logJob(LogLevel.INFO, this, "CHANGE_PHASE", "The job \"" + getJobId() + "\" goes from " + oldPhase + " to " + p, null); // Notify the execution manager: if (phase.isFinished() && getJobList() != null) getJobList().getExecutionManager().remove(this); // Notify all the observers: notifyObservers(oldPhase); } } /** * <p>Gets the phase manager of this job.</p> * * <p><i><u>Note:</u> The phase manager manages all the transitions between all the execution phases.</i></p> * * @return Its phase manager. */ public final JobPhase getPhaseManager(){ return phase; } /** * <p>Sets the phase manager of this job.</p> * * <p><i><u>Note:</u> The phase manager manages all the transitions between all the execution phases.</i></p> * * @param jobPhase Its new phase manager (if <i>null</i> this function does nothing). */ public final void setPhaseManager(JobPhase jobPhase){ if (jobPhase != null){ synchronized(phase){ phase = jobPhase; } } } /** * Gets the time at which the job execution has started. * * @return The start time of the execution of this job. */ public final Date getStartTime(){ return startTime; } /** * Sets the time at which the job execution has started. * * @param newDateTime The start time of the execution of this job. */ protected final void setStartTime(Date newDateTime){ startTime = newDateTime; } /** * Gets the time at which the job execution has finished. * * @return The end time of the execution of this job. */ public final Date getEndTime(){ return endTime; } /** * Sets the time at which the job execution has finished. * * @param newDateTime The end time of the execution of this job. */ protected final void setEndTime(Date newDateTime){ endTime = newDateTime; // Save the owner jobs list: if (phase.isFinished() && owner != null && getJobList() != null && getJobList().getUWS() != null && getJobList().getUWS().getBackupManager() != null) getJobList().getUWS().getBackupManager().saveOwner(owner); // Log the end of this job: getLogger().logJob(LogLevel.INFO, this, "END", "Job \"" + jobId + "\" ended with the status " + phase, null); } /** * Gets the duration (in seconds) for which this job shall run. * * @return The execution duration of this job. * * @see UWSParameters#getExecutionDuration() */ public final long getExecutionDuration(){ return inputParams.getExecutionDuration(); } /** * <p>Sets the duration (in seconds) for which this job shall run ONLY IF the job can updated (considering its current execution phase, see {@link JobPhase#isJobUpdatable()}).</p> * * <p><i><u>Note:</u> A duration of 0 (or less) implies unlimited execution duration.</i></p> * * @param executionDuration The execution duration of this job. * * @see UWSParameters#set(String, Object) */ public final void setExecutionDuration(long executionDuration){ if (phase.isJobUpdatable()){ try{ inputParams.set(PARAM_EXECUTION_DURATION, executionDuration); }catch(UWSException ue){ ; } } } /** * Gets the instant when the job shall be destroyed. * * @return The destruction time of this job. * * @see UWSParameters#getDestructionTime() */ public final Date getDestructionTime(){ return inputParams.getDestructionTime(); } /** * <p> * Sets the instant when the job shall be destroyed ONLY IF the job can updated (considering its current execution phase, see {@link JobPhase#isJobUpdatable()}). * If known the jobs list is notify of this destruction time update. * </p> * * @param destructionTime The destruction time of this job. <i>MUST NOT be NULL</i> * * @see JobList#updateDestruction(UWSJob) * @see UWSParameters#set(String, Object) */ public final void setDestructionTime(Date destructionTime){ if (destructionTime != null && phase.isJobUpdatable()){ try{ inputParams.set(PARAM_DESTRUCTION_TIME, destructionTime); if (myJobList != null) myJobList.updateDestruction(this); }catch(UWSException ue){ getLogger().logJob(LogLevel.WARNING, this, "SET_DESTRUCTION", "Can not set the destruction time of the job \"" + getJobId() + "\" to \"" + destructionTime + "\"!", ue); } } } /** * Gets the error that occurs during the execution of this job. * * @return A summary of the error. */ public final ErrorSummary getErrorSummary(){ return errorSummary; } /** * <p>Sets the error that occurs during the execution of this job.</p> * * <p><b><u>IMPORTANT:</u> This function will have no effect if the job is finished, that is to say if the current phase is * {@link ExecutionPhase#ABORTED ABORTED}, {@link ExecutionPhase#ERROR ERROR} or {@link ExecutionPhase#COMPLETED COMPLETED}.</i>.</b></p> * * @param errorSummary A summary of the error. <i>MUST NOT be NULL</i> * * @throws UWSException If the job execution is finished that is to say if the phase is ABORTED, ERROR or COMPLETED. * * @see #isFinished() */ public final void setErrorSummary(ErrorSummary errorSummary) throws UWSException{ if (errorSummary == null) return; else if (!isFinished()) this.errorSummary = errorSummary; else{ getLogger().logJob(LogLevel.ERROR, this, "SET_ERROR", "Can not set an error summary when the job is finished (or not yet started)! The current phase is: " + getPhase() + " ; the summary of the error to set is: \"" + errorSummary.message + "\".", null); throw new UWSException(UWSException.NOT_ALLOWED, UWSExceptionFactory.jobModificationForbidden(jobId, getPhase(), "ERROR SUMMARY")); } } /** * Gets the ID of this job (this ID <b>MUST</b> be unique). * * @return The job ID (unique). */ public final String getJobId(){ return jobId; } /** * <p>Gets the RunID of this job given by the UWS user (presumed to be the owner of this job). * This ID isn't the one used to access to this job thanks to the jobs list: it is more likely a label/name than an ID => it is not unique.</p> * * <p><b><u>Warning:</u> This ID may be used by other jobs BUT their job id (cf {@link UWSJob#getJobId()}) must be different.</b></p> * * @return The Run ID (a kind of job name/label). * * @see UWSParameters#getRunId() */ public final String getRunId(){ return inputParams.getRunId(); } /** * <p>Sets the RunID of this job ONLY IF the job can updated (considering its current execution phase, see {@link JobPhase#isJobUpdatable()}).</p> * * @param name Its name/label. * * @see JobPhase#isJobUpdatable() * * @see UWSParameters#set(String, Object) */ public final void setRunId(String name){ if (!phase.isFinished()){ try{ inputParams.set(PARAM_RUN_ID, name); }catch(UWSException ue){ ; } } } /** * Gets the owner of this job. * * @return The owner. */ public final JobOwner getOwner(){ return owner; } /** * Get the quote attribute of this job. * * @return The estimated duration of the job execution (in seconds). */ public final long getQuote(){ return quote; } /** * <p>Sets the quote attribute of this job ONLY IF the job can updated (considering its current execution phase, see {@link JobPhase#isJobUpdatable()}).</p> * * @param nbSeconds The estimated duration of the job execution (in seconds). * * @see JobPhase#isJobUpdatable() */ public final void setQuote(long nbSeconds){ if (!phase.isFinished()) quote = nbSeconds; } /** * Gets the list of parameters' name. * * @return The additional parameters of this job. * * @see UWSParameters#getNames() */ public final Set<String> getAdditionalParameters(){ return inputParams.getAdditionalParameters().keySet(); } /** * Gets the number of additional parameters. * * @return Number of additional parameters. */ public final int getNbAdditionalParameters(){ return inputParams.getAdditionalParameters().size(); } /** * Gets the value of the specified additional parameter. * * @param paramName The name of the parameter whose the value is wanted. * @return The value of the specified parameter or <i>null</i> if it doesn't exist. */ public final Object getAdditionalParameterValue(String paramName){ return inputParams.getAdditionalParameters().get(paramName); } /** * Adds or updates the specified parameter with the given value ONLY IF the job can be updated (considering its current execution phase, see {@link JobPhase#isJobUpdatable()}). * * @param paramName The name of the parameter to add or to update. * @param paramValue The (new) value of the specified parameter. * * @return <ul><li><i>true</i> if the parameter has been successfully added/updated,</li> * <li><i>false</i> otherwise <i>(particularly if paramName=null or paramName="" or paramValue=null)</i>.</li></ul> * * @throws UWSException If a parameter value is incorrect. * * @see JobPhase#isJobUpdatable() */ public final boolean addOrUpdateParameter(String paramName, Object paramValue) throws UWSException{ return addOrUpdateParameter(paramName, paramValue, null); } /** * Adds or updates the specified parameter with the given value ONLY IF the job can be updated (considering its current execution phase, see {@link JobPhase#isJobUpdatable()}). * * @param paramName The name of the parameter to add or to update. * @param paramValue The (new) value of the specified parameter. * @param user The user who asks for this update. * * @return <ul><li><i>true</i> if the parameter has been successfully added/updated,</li> * <li><i>false</i> otherwise <i>(particularly if paramName=null or paramName="" or paramValue=null)</i>.</li></ul> * * @throws UWSException If a parameter value is incorrect. * * @since 4.1 * * @see JobPhase#isJobUpdatable() */ public final boolean addOrUpdateParameter(String paramName, Object paramValue, final JobOwner user) throws UWSException{ if (paramValue != null && !phase.isFinished()){ // Set the parameter: inputParams.set(paramName, paramValue); // If it is a file or an array containing files, they must be moved in a location related to this job: try{ if (paramValue instanceof UploadFile) ((UploadFile)paramValue).move(this); else if (paramValue.getClass().isArray()){ for(Object o : (Object[])paramValue){ if (o != null && o instanceof UploadFile) ((UploadFile)o).move(this); } } }catch(IOException ioe){ getLogger().logJob(LogLevel.WARNING, this, "MOVE_UPLOAD", "Can not move an uploaded file in the job \"" + jobId + "\"!", ioe); return false; } // Apply the retrieved phase: applyPhaseParam(user); return true; }else return false; } /** * <p>Adds or updates the given parameters ONLY IF the job can be updated (considering its current execution phase, see {@link JobPhase#isJobUpdatable()}).</p> * * <p>At the end of this function, the method {@link #applyPhaseParam(JobOwner)} is called so that if there is an additional parameter {@link #PARAM_PHASE} with the value: * <ul> * <li>{@link UWSJob#PHASE_RUN RUN} then the job is starting and the phase goes to {@link ExecutionPhase#EXECUTING EXECUTING}.</li> * <li>{@link UWSJob#PHASE_ABORT ABORT} then the job is aborting.</li> * <li>otherwise the parameter {@link UWSJob#PARAM_PHASE PARAM_PHASE} is removed from {@link UWSJob#inputParams inputParams} and nothing is done.</li> * </ul></p> * * @param params A list of parameters to add/update. * @return <ul><li><i>true</i> if all the given parameters have been successfully added/updated,</li> * <li><i>false</i> if some parameters have not been managed.</li></ul> * * @throws UWSException If a parameter value is incorrect. * * @see #addOrUpdateParameters(UWSParameters, JobOwner) */ public boolean addOrUpdateParameters(UWSParameters params) throws UWSException{ return addOrUpdateParameters(params, null); } /** * <p>Adds or updates the given parameters ONLY IF the job can be updated (considering its current execution phase, see {@link JobPhase#isJobUpdatable()}).</p> * * <p>At the end of this function, the method {@link #applyPhaseParam(JobOwner)} is called so that if there is an additional parameter {@link #PARAM_PHASE} with the value: * <ul> * <li>{@link UWSJob#PHASE_RUN RUN} then the job is starting and the phase goes to {@link ExecutionPhase#EXECUTING EXECUTING}.</li> * <li>{@link UWSJob#PHASE_ABORT ABORT} then the job is aborting.</li> * <li>otherwise the parameter {@link UWSJob#PARAM_PHASE PARAM_PHASE} is removed from {@link UWSJob#inputParams inputParams} and nothing is done.</li> * </ul></p> * * @param params The UWS parameters to update. * @param user The user who asks for this update. * * @return <ul><li><i>true</i> if all the given parameters have been successfully added/updated,</li> * <li><i>false</i> if some parameters have not been managed.</li></ul> * * @throws UWSException If a parameter value is incorrect or if the given user can not update or execute this job. * * @see JobPhase#isJobUpdatable() * @see #applyPhaseParam(JobOwner) */ public boolean addOrUpdateParameters(UWSParameters params, final JobOwner user) throws UWSException{ // The job can be modified ONLY IF in PENDING phase: if (!phase.isJobUpdatable()) throw new UWSException(UWSException.FORBIDDEN, "Forbidden parameters modification: the job is not any more in the PENDING phase!"); // Forbids the update if the user has not the required permission: if (user != null && !user.equals(owner) && !user.hasWritePermission(this)) throw new UWSException(UWSException.PERMISSION_DENIED, UWSExceptionFactory.writePermissionDenied(user, false, getJobId())); // Load all parameters: String[] updated = inputParams.update(params); // If the destruction time has been updated, the modification must be propagated to the jobs list: Object newValue; for(String updatedParam : updated){ // CASE DESTRUCTION_TIME: update the thread dedicated to the destruction: if (updatedParam.equals(PARAM_DESTRUCTION_TIME)){ if (myJobList != null) myJobList.updateDestruction(this); } // DEFAULT: test whether the parameter is a file, and if yes, move it in a location related to this job: else{ newValue = inputParams.get(updatedParam); if (newValue != null && newValue instanceof UploadFile){ try{ ((UploadFile)newValue).move(this); }catch(IOException ioe){ getLogger().logJob(LogLevel.WARNING, this, "MOVE_UPLOAD", "Can not move an uploaded file in the job \"" + jobId + "\"!", ioe); inputParams.remove(updatedParam); } } } } // Apply the retrieved phase: applyPhaseParam(user); return (updated.length == params.size()); } /** * Removes the specified additional parameter ONLY IF the job can be updated (considering its current execution phase, see {@link JobPhase#isJobUpdatable()}). * * @param paramName The name of the parameter to remove. * * @return <i>true</i> if the parameter has been successfully removed, <i>false</i> otherwise. * * @see JobPhase#isJobUpdatable() * @see UWSParameters#remove(String) */ public final boolean removeAdditionalParameter(String paramName){ if (phase.isFinished() || paramName == null) return false; else{ // Remove the parameter from the map: Object removed = inputParams.remove(paramName); // If the parameter value was an uploaded file, delete it physically: if (removed != null && removed instanceof UploadFile){ try{ ((UploadFile)removed).deleteFile(); }catch(IOException ioe){ getLogger().logJob(LogLevel.WARNING, this, "MOVE_UPLOAD", "Can not delete the uploaded file \"" + paramName + "\" of the job \"" + jobId + "\"!", ioe); } } return true; } } /** * Gets the results list of this job. * * @return An iterator on the results list. */ public final Iterator<Result> getResults(){ return results.values().iterator(); } /** * Gets the specified result. * * @param resultId ID of the result to return. * * @return The corresponding result. */ public final Result getResult(String resultId){ return results.get(resultId); } /** * Gets the total number of results. * * @return The number of results. */ public final int getNbResults(){ return results.size(); } /** * <p>Adds the given result in the results list of this job.</p> * * <p><b><u>IMPORTANT:</u> This function will throw an error if the job is finished.</b></p> * * @param res The result to add (<b>not null</b>). * * @return <i>true</i> if the result has been successfully added, <i>false</i> otherwise (for instance, if a result has the same ID). * * @throws UWSException If the job execution is finished that is to say if the phase is ABORTED, ERROR or COMPLETED. * * @see #isFinished() */ public boolean addResult(Result res) throws UWSException{ if (res == null) return false; else if (isFinished()){ UWSException ue = new UWSException(UWSException.NOT_ALLOWED, UWSExceptionFactory.jobModificationForbidden(getJobId(), getPhase(), "RESULT")); getLogger().logJob(LogLevel.ERROR, this, "ADD_RESULT", "Can not add the result \"" + res.getId() + "\" to the job \"" + getJobId() + "\": this job is already finished (or not yet started). Current phase: " + getPhase(), ue); throw ue; }else{ synchronized(results){ if (results.containsKey(res.getId())) return false; else{ results.put(res.getId(), res); return true; } } } } /** * Gets the execution manager of this job, if any. * * @return Its execution manager (may be <i>null</i>). */ public final ExecutionManager getExecutionManager(){ return getJobList().getExecutionManager(); } /** * Gets its jobs list, if known. * * @return Its jobs list (may be <i>null</i>). */ public final JobList getJobList(){ return myJobList; } /** * <p>Sets its jobs list.</p> * * <p><i><u>note 1:</u> a job can change its jobs list ONLY WHILE PENDING !</i></p> * <p><i><u>note 2:</u> this job is removed from its previous job list, if there is one.</i></p> * <p><i><u>note 3:</u> this job is NOT automatically added into the new jobs list. Indeed, this function should be called by {@link JobList#addNewJob(UWSJob)}.</i></p> * * @param jobList Its new jobs list. <i><u>note:</u> if NULL, nothing is done !</i> * * @throws IllegalStateException If this job is not PENDING. * * @see JobList#removeJob(String) * @see JobList#getJob(String) */ protected final void setJobList(final JobList jobList) throws IllegalStateException{ if (jobList == null) return; else if (myJobList != null && jobList.equals(myJobList)) return; else if (myJobList == null || phase.getPhase() == ExecutionPhase.PENDING){ if (myJobList != null && myJobList.getJob(jobId) != null) myJobList.removeJob(jobId); myJobList = jobList; }else throw new IllegalStateException("Impossible to move a job (here: " + jobId + ") from a jobs list (here: " + ((myJobList == null) ? "null" : myJobList.getName()) + ") to another (here: " + ((jobList == null) ? "null" : jobList.getName()) + ") if the job is not PENDING !"); } /** * Gets the UWS URL of this job in function of its jobs list. * * @return Its corresponding UWSUrl. * * @see JobList#getUrl() * @see UWSUrl#jobSummary(String, String) */ public final UWSUrl getUrl(){ if (myJobList != null){ UWSUrl url = myJobList.getUrl(); if (url != null) return url.jobSummary(myJobList.getName(), jobId); } return null; } /* ******************** */ /* EXECUTION MANAGEMENT */ /* ******************** */ /** * Gets the time to wait for the end of the thread after an interruption. * * @return The time to wait for the end of the thread (a negative or null value means no wait for the end of the thread). */ public final long getTimeToWaitForEnd(){ return waitForStop; } /** * Sets the time to wait for the end of the thread after an interruption. * * @param timeToWait The new time to wait for the end of the thread (a negative or null value means no wait for the end of the thread). */ public final void setTimeToWaitForEnd(long timeToWait){ waitForStop = timeToWait; } /** * <p>Starts the job by using the execution manager if any.</p> * * @throws UWSException */ public final void start() throws UWSException{ start(getJobList() != null); } /** * <p>Starts the job.</p> * * <p><i><u>Note:</u> This function does nothing if the job is already running !</i></p> * * @param useManager <i>true</i> to let the execution manager deciding whether the job starts immediately or whether it must be put in a queue until enough resources are available, <i>false</i> to start the execution immediately. * * @throws NullPointerException If this job is not associated with a job list or the associated job list is not part of a UWS service or if no thread is created. * @throws UWSException If there is an error while changing the execution phase or when starting the corresponding thread. * * @see #isRunning() * @see UWSFactory#createJobThread(UWSJob) * @see ExecutionManager#execute(UWSJob) * @see #setPhase(ExecutionPhase) * @see #isFinished() * @see #startTime */ public void start(boolean useManager) throws UWSException{ // This job must know its jobs list and this jobs list must know its UWS: if (myJobList == null || myJobList.getUWS() == null) throw new IllegalStateException("A UWSJob can not start if it is not linked to a job list or if its job list is not linked to a UWS."); // If already running do nothing: else if (isRunning()) return; // If asked propagate this request to the execution manager: else if (useManager){ getJobList().getExecutionManager().execute(this); }// Otherwise start directly the execution: else{ // Create its corresponding thread: thread = getFactory().createJobThread(this); if (thread == null) throw new NullPointerException("Missing job work! The thread created by the factory is NULL => The job can't be executed!"); // Change the job phase: setPhase(ExecutionPhase.EXECUTING); // Set the start time: setStartTime(new Date()); // Run the job: thread.start(); (new JobTimeOut()).start(); // Log the start of this job: getLogger().logJob(LogLevel.INFO, this, "START", "Job \"" + jobId + "\" started.", null); } } /** * Stop/Cancel this job when its maximum execution duration has been reached. * * @author Grégory Mantelet (CDS;ARI) * @version 4.1 (09/2014) */ protected final class JobTimeOut extends Thread { public JobTimeOut(){ super(JobThread.tg, "TimeOut_" + jobId); } @Override public void run(){ long maxDuration = getExecutionDuration(); if (thread != null && thread.isAlive() && maxDuration != UNLIMITED_DURATION && maxDuration > 0){ try{ thread.join(maxDuration * 1000); if (!isFinished()) UWSJob.this.abort(); }catch(InterruptedException ie){ /* Not needed to report any interruption while waiting. */ }catch(UWSException ue){ getLogger().logJob(LogLevel.WARNING, UWSJob.this, "EXECUTING", "Unexpected error while waiting the end of the execution of the job \"" + jobId + "\" (thread ID: " + thread.getId() + ")!", ue); } } } } /** * <p>Tells whether the job is still running.</p> * * <p><i><u>Note:</u> This function tests the execution phase (see {@link JobPhase#isExecuting()}) AND the status of the thread (see {@link #isStopped()}).</i></p> * * @return <i>true</i> if the job is still running, <i>false</i> otherwise. * * @see JobPhase#isExecuting() * @see #isStopped() */ public final boolean isRunning(){ return phase.isExecuting() && !isStopped(); } /** * <p>Tells whether the job is already finished (completed, aborted, error, ...).</p> * * <p><i><u>Note:</u> This function test the execution phase (see {@link JobPhase#isFinished()}) AND the status of the thread (see {@link #isStopped()})</i></p> * * @return <i>true</i> if the job is finished, <i>false</i> otherwise. * * @see JobPhase#isFinished() * @see #isStopped() */ public final boolean isFinished(){ return phase.isFinished() && isStopped(); } /** * <p>Stops immediately the job, sets its phase to {@link ExecutionPhase#ABORTED ABORTED} and sets its end time.</p> * * <p><b><u>IMPORTANT:</u> If the thread does not stop immediately the phase and the end time are not modified. However it can be done by calling one more time {@link #abort()}. * Besides you should check that you test regularly the interrupted flag of the thread in {@link JobThread#jobWork()} !</b></p> * * @throws UWSException If there is an error while changing the execution phase. * * @see #stop() * @see #isStopped() * @see #setPhase(ExecutionPhase) * @see #setEndTime(Date) */ public void abort() throws UWSException{ // Interrupt the corresponding thread: stop(); if (isStopped()){ if (!phase.isFinished()){ // Try to change the phase: setPhase(ExecutionPhase.ABORTED); // Set the end time: setEndTime(new Date()); }else if ((thread == null || (thread != null && !thread.isAlive())) && phase.getPhase() != ExecutionPhase.ABORTED) throw new UWSException(UWSException.BAD_REQUEST, UWSExceptionFactory.incorrectPhaseTransition(getJobId(), phase.getPhase(), ExecutionPhase.ABORTED)); }else getLogger().logJob(LogLevel.WARNING, this, "ABORT", "Abortion of the job \"" + getJobId() + "\" asked but not yet effective (after having waited " + waitForStop + "ms)!", null); } /** * <p>Stops immediately the job, sets its error summary, sets its phase to {@link ExecutionPhase#ERROR} and sets its end time.</p> * * <p><b><u>IMPORTANT:</u> If the thread does not stop immediately the phase, the error summary and the end time are not modified. * However it can be done by calling one more time {@link #error(ErrorSummary)}. * Besides you should check that you test regularly the interrupted flag of the thread in {@link JobThread#jobWork()} !</b></p> * * @param error The error that has interrupted this job. * * @throws UWSException If there is an error while setting the error summary or while changing the phase. * * @see #stop() * @see #isStopped() * @see JobPhase#isFinished() * @see #setErrorSummary(ErrorSummary) * @see #setPhase(ExecutionPhase) * @see #setEndTime(Date) */ public void error(ErrorSummary error) throws UWSException{ // Interrupt the corresponding thread: stop(); if (isStopped()){ if (!phase.isFinished()){ // Set the error summary: setErrorSummary(error); // Try to change phase: setPhase(ExecutionPhase.ERROR); // Set the end time: setEndTime(new Date()); }else if (thread != null && !thread.isAlive()) throw new UWSException(UWSException.BAD_REQUEST, UWSExceptionFactory.incorrectPhaseTransition(jobId, phase.getPhase(), ExecutionPhase.ERROR)); }else getLogger().logJob(LogLevel.WARNING, this, "ERROR", "Stopping of the job \"" + getJobId() + "\" with error asked but not yet effective (after having waited " + waitForStop + "ms)!", null); } /** Used by the thread to known whether the {@link #stop()} method has already been called, and so, that the job is stopping. */ protected boolean stopping = false; /** * Stops the thread that executes the work of this job. */ protected void stop(){ if (!isStopped()){ synchronized(thread){ stopping = true; // Interrupts the thread: thread.interrupt(); // Wait a little for its end: if (waitForStop > 0){ try{ thread.join(waitForStop); }catch(InterruptedException ie){ getLogger().logJob(LogLevel.WARNING, this, "END", "Unexpected InterruptedException while waiting for the end of the execution of the job \"" + jobId + "\" (thread ID: " + thread.getId() + ")!", ie); } } } } } /** * <p>Tells whether the thread is different from <i>null</i>, is not alive or is finished (see {@link JobThread#isFinished()}).</p> * * <p><i><b>Important note:</b> * Having the interrupted flag set to <code>true</code> is not enough to consider the job as stopped. * So, if the job has been interrupted but is still running, it should mean that the {@link JobThread#jobWork()} does not * check the interrupted flag of the thread often enough or not at the right moments. In such case, the job can not be * considered as stopped/aborted - so the phase stays {@link ExecutionPhase#EXECUTING EXECUTING} - until the thread is "unblocked" * and the interruption is detected. * </i></p> * * @return <i>true</i> if the thread is not still running, <i>false</i> otherwise. */ protected final boolean isStopped(){ return thread == null || !thread.isAlive() || thread.isFinished(); } /** * <p>Stops the job if running, removes the job from the execution manager, stops the timer for the execution duration * and may clear all files or any other resources associated to this job.</p> * * <p><i>By default the job is aborted, the {@link UWSJob#thread} attribute is set to null, the timers are stopped and uploaded files, results and the error summary are deleted.</i></p> */ public void clearResources(){ // If still running, abort/stop the job: if (isRunning()){ try{ abort(); }catch(UWSException e){ getLogger().logJob(LogLevel.WARNING, this, "CLEAR_RESOURCES", "Impossible to abort the job \"" + jobId + "\" => trying to stop it...", e); stop(); } } // Remove this job from its execution manager: if (getJobList() != null) getJobList().getExecutionManager().remove(this); thread = null; // Clear all uploaded files: Iterator<UploadFile> files = inputParams.getFiles(); UploadFile upl; while(files.hasNext()){ upl = files.next(); try{ upl.deleteFile(); }catch(IOException ioe){ getLogger().logJob(LogLevel.ERROR, this, "CLEAR_RESOURCES", "Impossible to delete the file uploaded as parameter \"" + upl.paramName + "\" (" + upl.getLocation() + ") of the job \"" + jobId + "\"!", null); } } // Clear all results file: for(Result r : results.values()){ try{ getFileManager().deleteResult(r, this); }catch(IOException ioe){ getLogger().logJob(LogLevel.ERROR, this, "CLEAR_RESOURCES", "Impossible to delete the file associated with the result '" + r.getId() + "' of the job \"" + jobId + "\"!", ioe); } } // Clear the error file: if (errorSummary != null && errorSummary.hasDetail()){ try{ getFileManager().deleteError(errorSummary, this); }catch(IOException ioe){ getLogger().logJob(LogLevel.ERROR, this, "CLEAR_RESOURCES", "Impossible to delete the file associated with the error '" + errorSummary.message + "' of the job \"" + jobId + "\"!", ioe); } } getLogger().logJob(LogLevel.INFO, this, "CLEAR_RESOURCES", "Resources associated with the job \"" + getJobId() + "\" have been successfully freed.", null); } /* ******************* */ /* OBSERVER MANAGEMENT */ /* ******************* */ /** * Lets adding an observer of this job. The observer will be notified each time the execution phase changes. * * @param observer A new observer of this job. * * @return <i>true</i> if the given object has been successfully added as observer of this job, <i>false</i> otherwise. */ public final boolean addObserver(JobObserver observer){ if (observer != null && !observers.contains(observer)){ observers.add(observer); return true; }else return false; } /** * Gets the total number of observers this job has. * * @return Number of its observers. */ public final int getNbObservers(){ return observers.size(); } /** * Gets the observers of this job. * * @return An iterator on the list of its observers. */ public final Iterator<JobObserver> getObservers(){ return observers.iterator(); } /** * Lets removing the given object from the list of observers of this job. * * @param observer The object which must not be considered as observer of this job. * * @return <i>true</i> if the given object is not any more an observer of this job, <i>false</i> otherwise. */ public final boolean removeObserver(JobObserver observer){ return observers.remove(observer); } /** * Lets removing all observers of this job. */ public final void removeAllObservers(){ observers.clear(); } /** * Notifies all the observer of this job that its phase has changed. * * @param oldPhase The former phase of this job. * @throws UWSException If at least one observer can not have been updated. */ public final void notifyObservers(ExecutionPhase oldPhase){ int i = 0; JobObserver observer = null; String errors = null; while(i < observers.size()){ // Gets the observer: if (i == 0 && observer == null) observer = observers.get(i); else if (observer.equals(observers.get(i))){ i++; if (i < observers.size()) observer = observers.get(i); else return; } // Update this observer: try{ observer.update(this, oldPhase, getPhase()); }catch(UWSException ex){ if (errors == null) errors = "\t* " + ex.getMessage(); else errors += "\n\t* " + ex.getMessage(); } } if (errors != null) getLogger().logJob(LogLevel.WARNING, this, "NOTIFY", "Some observers of the job \"" + jobId + "\" can not have been updated:\n" + errors, null); } /* **************** */ /* ERROR MANAGEMENT */ /* **************** */ /** * <p>Gets the error (if any) which has occurred during the job execution.</p> * * <p><i><u>Note:</u> In the case an error summary can not have been published, the job has no error summary. * However the last {@link UWSException} caught during the execution of a {@link JobThread} is saved and is available thanks to {@link JobThread#getError()}. * In that case, the {@link UWSJob#getWorkError() getWorkError()} method can be used to get back the occurred error.</i></p> * * @return The error which interrupts the thread or <i>null</i> if there was no error or if the job is still running. */ public final UWSException getWorkError(){ return (thread == null || !thread.isAlive()) ? null : thread.getError(); } /* ************* */ /* SERIALIZATION */ /* ************* */ @Override public String serialize(UWSSerializer serializer, JobOwner user) throws UWSException, Exception{ if (user != null && !user.equals(getOwner()) && !user.hasReadPermission(this)) throw new UWSException(UWSException.PERMISSION_DENIED, UWSExceptionFactory.readPermissionDenied(user, false, getJobId())); return serializer.getJob(this, true); } /** * Serializes the specified attribute of this job by using the given serializer. * * @param attributes All the given attributes (may be <i>null</i> or empty). * @param serializer The serializer to use. * * @return The serialized job attribute (or the whole job if <i>attributes</i> is an empty array or is <i>null</i>). * * @throws Exception If there is an unexpected error during the serialization. * * @see UWSSerializer#getJob(UWSJob, String[], boolean) */ public String serialize(String[] attributes, UWSSerializer serializer) throws Exception{ return serializer.getJob(this, attributes, true); } /** * Serializes the specified attribute of this job in the given output stream by using the given serializer. * * @param output The output stream in which the job attribute must be serialized. * @param attributes The name of the attribute to serialize (if <i>null</i>, the whole job will be serialized). * @param serializer The serializer to use. * * @throws Exception If there is an unexpected error during the serialization. * * @see #serialize(String[], UWSSerializer) */ public void serialize(ServletOutputStream output, String[] attributes, UWSSerializer serializer) throws UWSException, IOException, Exception{ String errorMsgPart = null; if (attributes == null || attributes.length <= 0) errorMsgPart = "the job \"" + getJobId() + "\""; else errorMsgPart = "the given attribute \"" + attributes[0] + "\" of the job \"" + getJobId() + "\""; if (output == null) throw new NullPointerException("Missing serialization output stream when serializing " + errorMsgPart + "!"); String serialization = serialize(attributes, serializer); if (serialization == null){ getLogger().logJob(LogLevel.ERROR, this, "SERIALIZE", "Error while serializing " + errorMsgPart + ": NULL was returned.", null); throw new UWSException(UWSException.INTERNAL_SERVER_ERROR, "Incorrect serialization value (=NULL) ! => impossible to serialize " + errorMsgPart + "."); }else{ output.print(serialization); output.flush(); } } @Override public String toString(){ return "JOB {jobId: " + jobId + "; phase: " + phase + "; runId: " + getRunId() + "; ownerId: " + owner + "; executionDuration: " + getExecutionDuration() + "; destructionTime: " + getDestructionTime() + "; quote: " + quote + "; NbResults: " + results.size() + "; " + ((errorSummary != null) ? errorSummary.toString() : "No error") + " }"; } @Override public int hashCode(){ return jobId.hashCode(); } /** * <p>2 instances of AbstractJob are equals ONLY IF their ID are equals.</p> * * <p><i><u>Note:</u> If the given object is not an AbstractJob, FALSE is returned.</i></p> * * @see java.lang.Object#equals(java.lang.Object) */ @Override public boolean equals(Object anotherJob){ if (anotherJob instanceof UWSJob) return jobId.equals(((UWSJob)anotherJob).jobId); else return false; } }