/*
* (C) Copyright 2012 Nuxeo SA (http://nuxeo.com/) and contributors.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the GNU Lesser General Public License
* (LGPL) version 2.1 which accompanies this distribution, and is available at
* http://www.gnu.org/licenses/lgpl.html
*
* This library 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.
*
* Contributors:
* Florent Guillaume
*/
package org.nuxeo.ecm.core.work.api;
import java.io.Serializable;
import java.util.List;
import org.nuxeo.ecm.core.api.DocumentLocation;
import org.nuxeo.ecm.core.api.IdRef;
import org.nuxeo.ecm.core.work.AbstractWork;
import org.nuxeo.ecm.core.work.api.WorkManager.Scheduling;
/**
* A {@link Work} instance gets scheduled and executed by a {@link WorkManager}
* .
* <p>
* Its {@link #work} method runs when a slot in a queue becomes available, note
* however that it can be suspended at any time (before initial execution, or
* during execution).
* <p>
* A {@link Work} instance has an id that is used by the queuing mechanisms to
* determine uniqueness. It also has a category that is used to choose which
* queue should execute it. It can report its status and progress.
* <p>
* A {@link Work} instance is Serializable because it must be able to save its
* computation state on interruption, and be reconstructed again with the saved
* state to continue execution at a later time. Because of this, the instance
* itself may change over time and be executed on a different JVM than the one
* that constructed it initially.
* <p>
* A {@link Work} instance must have an id, which is used for equality
* comparisons and as a key for persistent queues.
* <p>
* Implementors are strongly advised to inherit from {@link AbstractWork}.
*
* @see AbstractWork
* @since 5.6
*/
public interface Work extends Serializable {
/**
* The running state of a {@link Work} instance.
* <p>
* The following transitions between states are possible:
* <ul>
* <li>UNKNOWN -> SCHEDULED</li> (unknown from the manager point of view)
* <li>SCHEDULED -> CANCELED (is never scheduled or run)
* <li>SCHEDULED -> RUNNING
* <li>RUNNING -> COMPLETED
* <li>RUNNING -> FAILED
* <li>RUNNING -> SCHEDULED (is suspended and persisted)
* </ul>
*/
enum State {
/**
* Work instance is not currently referenced in work manager
* @since 5.9.4
*/
UNKNOWN,
/**
* Work instance is scheduled to run later.
*/
SCHEDULED,
/**
* Work instance was canceled.
* <p>
* This happens if:
* <ul>
* <li>it is never scheduled because another instance with the same id
* was already scheduled or running (when scheduled with
* {@link Scheduling#IF_NOT_SCHEDULED},
* {@link Scheduling#IF_NOT_RUNNING}, or
* {@link Scheduling#IF_NOT_RUNNING_OR_SCHEDULED}),
* <li>it is never run because it was scheduled after commit and the
* transaction rolled back,
* <li>it is never run because it was scheduled, but another work with
* the same id was scheduled with {@link Scheduling#CANCEL_SCHEDULED}.
* </ul>
*/
CANCELED,
/**
* Work instance is running.
*/
RUNNING,
/**
* Work instance has completed normally.
*/
COMPLETED,
/**
* Work instance has completed with an exception.
*/
FAILED,
}
/**
* A progress report about a work instance.
* <p>
* Progress can be expressed as a percentage, or with a current and total
* count.
* <ul>
* <li>26.2% (percent not indeterminate)</li>
* <li>12/345 (current not indeterminate)</li>
* <li>?/345 (percent and current indeterminate but total non-zero)</li>
* <li>? (percent and current indeterminate and total zero)</li>
* </ul>
*
* @since 5.6
*/
public static class Progress implements Serializable {
private static final long serialVersionUID = 1L;
public static long CURRENT_INDETERMINATE = -1;
public static float PERCENT_INDETERMINATE = -1F;
public static final Progress PROGRESS_INDETERMINATE = new Progress(
PERCENT_INDETERMINATE);
public static final Progress PROGRESS_0_PC = new Progress(0F);
public static final Progress PROGRESS_100_PC = new Progress(100F);
protected final float percent;
protected final long current;
protected final long total;
/**
* Constructs a {@link Progress} as a percentage.
*
* @param percent the percentage, a float between 0 and 100, or
* {@link #PERCENT_INDETERMINATE}
*/
public Progress(float percent) {
this.percent = percent > 100F ? 100F : percent;
current = CURRENT_INDETERMINATE;
total = 0;
}
/**
* Constructs a {@link Progress} as a current and total count.
*
* @param current the current count or {@link #CURRENT_INDETERMINATE}
* @param total the total count
*/
public Progress(long current, long total) {
percent = PERCENT_INDETERMINATE;
this.current = current;
this.total = total;
}
public float getPercent() {
return percent;
}
public long getCurrent() {
return current;
}
public long getTotal() {
return total;
}
public boolean getIsWithPercent() {
return percent != PERCENT_INDETERMINATE;
}
public boolean getIsWithCurrentAndTotal() {
return current != CURRENT_INDETERMINATE;
}
public boolean getIsIndeterminate() {
return percent == PERCENT_INDETERMINATE
&& current == CURRENT_INDETERMINATE;
}
@Override
public String toString() {
return getClass().getSimpleName()
+ "("
+ (percent == PERCENT_INDETERMINATE ? "?"
: Float.valueOf(percent))
+ "%, "
+ (current == CURRENT_INDETERMINATE ? "?"
: Long.valueOf(current)) + "/" + total + ")";
}
}
/**
* Runs the work instance and does all the transaction management and retry.
* <p>
* Usually only implemented by {@link AbstractWork}, which should be
* subclassed instead of implementing {@link #run}.
*/
void run();
/**
* This method should implement the actual work done by the {@link Work}
* instance.
* <p>
* It should periodically update its progress through {@link #setProgress}.
* <p>
* To allow for suspension by the {@link WorkManager}, it should
* periodically call {@link #isSuspending}, and if {@code true} call
* {@link #suspended} return early with saved state data.
* <p>
* Clean up can by implemented by {@link #cleanUp()}.
*
* @see #isSuspending
* @see #suspended
* @see #cleanUp
*/
void work() throws Exception;
/**
* The work id.
* <p>
* The id is used for equality comparisons, and as a key in persistent
* queues.
*
* @return the work id, which must not be {@code null}
*
* @since 5.8
*/
String getId();
/**
* This method is called after {@link #work} is done, in a finally block,
* whether work completed normally or was in error or was interrupted.
*
* @param ok {@code true} if the work completed normally
* @param e the exception, if available
*/
void cleanUp(boolean ok, Exception e);
/**
* CALLED BY THE WORK MANAGER (not user code) when it requests that this
* work instance be suspended.
*
* @since 5.8
*/
void setWorkInstanceSuspending();
/**
* Checks if a suspend has been requested for this work instance by the work
* manager.
* <p>
* If {@code true}, then state should be saved, {@link #suspended()} should
* be called, and the {@link #work()} method should return.
*
* @since 5.8
*/
boolean isSuspending();
/**
* Must be called by {@link Work} implementations to advertise that state
* saving is done, when {@link #isSuspending()} returned {@code true}. After
* this is called, the {@link #work()} method should return.
*
* @since 5.8
*/
void suspended();
/**
* CALLED BY THE WORK MANAGER (not user code) to check if this work instance
* really suspended.
*
* @since 5.8
*/
boolean isWorkInstanceSuspended();
/**
* CALLED BY THE WORK MANAGER (not user code) to set this work instance's
* state.
*
* @since 5.8
*/
void setWorkInstanceState(State state);
/**
* CALLED BY THE WORK MANAGER (not user code) to get this work instance's
* state.
* <p>
* Used only to get the final state of a completed instance (
* {@link State#COMPLETED}, {@link State#FAILED} or {@link State#CANCELED}).
*
* @since 5.8
*/
State getWorkInstanceState();
/**
* DO NOT USE THIS - gets the state of this work instance.
* <p>
* This should not be used because for non in-memory persistence, the work
* instance gets serialized and deserialized for running and when retrieved
* after completion, and therefore the original instance cannot get updated
* after the original scheduling.
*
* @return the state
* @deprecated since 5.8, use {@link WorkManager#getWorkState} instead
*/
@Deprecated
State getState();
/**
* Gets the category for this work.
* <p>
* Used to choose an execution queue.
*
* @return the category, or {@code null} for the default
*/
String getCategory();
/**
* Gets a human-readable name for this work instance.
*
* @return a human-readable name
*/
String getTitle();
/**
* Gets a human-readable status for this work instance.
*
* @return a human-readable status
*/
String getStatus();
/**
* Gets the time at which this work instance was first scheduled.
*
* @return the scheduling time (milliseconds since epoch)
*/
long getSchedulingTime();
/**
* Gets the time at which this work instance was first started.
*
* @return the start time (milliseconds since epoch), or {@code 0} if not
* stated
*/
long getStartTime();
/**
* Gets the time at which this work instance was completed, suspended or
* failed.
*
* @return the completion time (milliseconds since epoch), or {@code 0} if
* not completed
*/
long getCompletionTime();
/**
* CALLED BY THE WORK MANAGER (not user code) to set the start time on the
* work instance.
*
* @since 5.9.2
*/
void setStartTime();
/**
* This method should be called periodically by the actual work method when
* it knows of its progress.
*
* @param progress the progress
* @see Progress#Progress(float)
* @see Progress#Progress(long, long)
*/
void setProgress(Progress progress);
/**
* Gets a progress report for this work instance.
*
* @return a progress report, not {@code null}
*/
Progress getProgress();
/**
* Gets the user on behalf of which this work is done.
* <p>
* This is informative only.
*
* @return the user id, or {@code null}
*/
String getUserId();
/**
* Gets the document impacted by the work.
* <p>
* Returns {@code null} if the work isn't about a single document.
*
* @return the document, or {@code null}. This is always a
* {@link DocumentLocation} with an {@link IdRef}
* @since 5.8
*/
DocumentLocation getDocument();
/**
* Gets the documents impacted by the work.
* <p>
* Returns {@code null} if the work isn't about documents.
*
* @return the documents, or an empty list. List elements are always a
* {@link DocumentLocation} with an {@link IdRef}
* @since 5.8
*/
List<DocumentLocation> getDocuments();
/**
* Returns {@code true} if {@link #getDocument} is only the root of a set of
* documents on which this Work instance will act.
*
* @return {@code true} if a whole tree is impacted
* @since 5.8
*/
boolean isDocumentTree();
/**
* Returns the schedule path
*
* @since 5.8
*/
WorkSchedulePath getSchedulePath();
/**
* Set the schedule path, internal usage
*
* @since 5.8
*/
void setSchedulePath(WorkSchedulePath path);
}