001/*
002 * (C) Copyright 2012 Nuxeo SA (http://nuxeo.com/) and others.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *     http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 *
016 * Contributors:
017 *     Florent Guillaume
018 */
019package org.nuxeo.ecm.core.work.api;
020
021import java.io.Serializable;
022import java.util.List;
023
024import org.nuxeo.ecm.core.api.DocumentLocation;
025import org.nuxeo.ecm.core.api.IdRef;
026import org.nuxeo.ecm.core.work.AbstractWork;
027import org.nuxeo.ecm.core.work.api.WorkManager.Scheduling;
028
029/**
030 * A {@link Work} instance gets scheduled and executed by a {@link WorkManager} .
031 * <p>
032 * Its {@link #work} method runs when a slot in a queue becomes available, note however that it can be suspended at any
033 * time (before initial execution, or during execution).
034 * <p>
035 * A {@link Work} instance has an id that is used by the queuing mechanisms to determine uniqueness. It also has a
036 * category that is used to choose which queue should execute it. It can report its status and progress.
037 * <p>
038 * A {@link Work} instance is Serializable because it must be able to save its computation state on interruption, and be
039 * reconstructed again with the saved state to continue execution at a later time. Because of this, the instance itself
040 * may change over time and be executed on a different JVM than the one that constructed it initially.
041 * <p>
042 * A {@link Work} instance must have an id, which is used for equality comparisons and as a key for persistent queues.
043 * <p>
044 * Implementors are strongly advised to inherit from {@link AbstractWork}.
045 *
046 * @see AbstractWork
047 * @since 5.6
048 */
049public interface Work extends Serializable {
050
051    /**
052     * The running state of a {@link Work} instance.
053     * <p>
054     * The following transitions between states are possible:
055     * <ul>
056     * <li>UNKNOWN -> SCHEDULED</li> (unknown from the manager point of view)
057     * <li>SCHEDULED -> CANCELED (is never scheduled or run)
058     * <li>SCHEDULED -> RUNNING
059     * <li>RUNNING -> COMPLETED
060     * <li>RUNNING -> FAILED
061     * <li>RUNNING -> SCHEDULED (is suspended and persisted)
062     * </ul>
063     */
064    enum State {
065        /**
066         * Work instance is not currently referenced in work manager
067         *
068         * @since 5.9.4
069         */
070        UNKNOWN,
071        /**
072         * Work instance is scheduled to run later.
073         */
074        SCHEDULED,
075        /**
076         * Work instance was canceled.
077         * <p>
078         * This happens if:
079         * <ul>
080         * <li>it is never scheduled because another instance with the same id was already scheduled or running (when
081         * scheduled with {@link Scheduling#IF_NOT_SCHEDULED}, {@link Scheduling#IF_NOT_RUNNING}, or
082         * {@link Scheduling#IF_NOT_RUNNING_OR_SCHEDULED}),
083         * <li>it is never run because it was scheduled after commit and the transaction rolled back,
084         * <li>it is never run because it was scheduled, but another work with the same id was scheduled with
085         * {@link Scheduling#CANCEL_SCHEDULED}.
086         * </ul>
087         */
088        CANCELED,
089        /**
090         * Work instance is running.
091         */
092        RUNNING,
093        /**
094         * Work instance has completed normally.
095         */
096        COMPLETED,
097        /**
098         * Work instance has completed with an exception.
099         */
100        FAILED,
101    }
102
103    /**
104     * A progress report about a work instance.
105     * <p>
106     * Progress can be expressed as a percentage, or with a current and total count.
107     * <ul>
108     * <li>26.2% (percent not indeterminate)</li>
109     * <li>12/345 (current not indeterminate)</li>
110     * <li>?/345 (percent and current indeterminate but total non-zero)</li>
111     * <li>? (percent and current indeterminate and total zero)</li>
112     * </ul>
113     *
114     * @since 5.6
115     */
116    public static class Progress implements Serializable {
117
118        private static final long serialVersionUID = 1L;
119
120        public static long CURRENT_INDETERMINATE = -1;
121
122        public static float PERCENT_INDETERMINATE = -1F;
123
124        public static final Progress PROGRESS_INDETERMINATE = new Progress(PERCENT_INDETERMINATE);
125
126        public static final Progress PROGRESS_0_PC = new Progress(0F);
127
128        public static final Progress PROGRESS_100_PC = new Progress(100F);
129
130        protected final float percent;
131
132        protected final long current;
133
134        protected final long total;
135
136        /**
137         * Constructs a {@link Progress} as a percentage.
138         *
139         * @param percent the percentage, a float between 0 and 100, or {@link #PERCENT_INDETERMINATE}
140         */
141        public Progress(float percent) {
142            this.percent = percent > 100F ? 100F : percent;
143            current = CURRENT_INDETERMINATE;
144            total = 0;
145        }
146
147        /**
148         * Constructs a {@link Progress} as a current and total count.
149         *
150         * @param current the current count or {@link #CURRENT_INDETERMINATE}
151         * @param total the total count
152         */
153        public Progress(long current, long total) {
154            percent = PERCENT_INDETERMINATE;
155            this.current = current;
156            this.total = total;
157        }
158
159        public float getPercent() {
160            return percent;
161        }
162
163        public long getCurrent() {
164            return current;
165        }
166
167        public long getTotal() {
168            return total;
169        }
170
171        public boolean getIsWithPercent() {
172            return percent != PERCENT_INDETERMINATE;
173        }
174
175        public boolean getIsWithCurrentAndTotal() {
176            return current != CURRENT_INDETERMINATE;
177        }
178
179        public boolean getIsIndeterminate() {
180            return percent == PERCENT_INDETERMINATE && current == CURRENT_INDETERMINATE;
181        }
182
183        @Override
184        public String toString() {
185            return getClass().getSimpleName() + "(" + (percent == PERCENT_INDETERMINATE ? "?" : Float.valueOf(percent))
186                    + "%, " + (current == CURRENT_INDETERMINATE ? "?" : Long.valueOf(current)) + "/" + total + ")";
187        }
188    }
189
190    /**
191     * Runs the work instance and does all the transaction management and retry.
192     * <p>
193     * Usually only implemented by {@link AbstractWork}, which should be subclassed instead of implementing {@link #run}.
194     */
195    void run();
196
197    /**
198     * This method should implement the actual work done by the {@link Work} instance.
199     * <p>
200     * It should periodically update its progress through {@link #setProgress}.
201     * <p>
202     * To allow for suspension by the {@link WorkManager}, it should periodically call {@link #isSuspending}, and if
203     * {@code true} call {@link #suspended} return early with saved state data.
204     * <p>
205     * Clean up can by implemented by {@link #cleanUp()}.
206     *
207     * @see #isSuspending
208     * @see #suspended
209     * @see #cleanUp
210     */
211    void work();
212
213    /**
214     * The work id.
215     * <p>
216     * The id is used for equality comparisons, and as a key in persistent queues.
217     *
218     * @return the work id, which must not be {@code null}
219     * @since 5.8
220     */
221    String getId();
222
223    /**
224     * This method is called after {@link #work} is done, in a finally block, whether work completed normally or was in
225     * error or was interrupted.
226     *
227     * @param ok {@code true} if the work completed normally
228     * @param e the exception, if available
229     */
230    void cleanUp(boolean ok, Exception e);
231
232    /**
233     * CALLED BY THE WORK MANAGER (not user code) when it requests that this work instance be suspended.
234     *
235     * @since 5.8
236     */
237    void setWorkInstanceSuspending();
238
239    /**
240     * Checks if a suspend has been requested for this work instance by the work manager.
241     * <p>
242     * If {@code true}, then state should be saved, {@link #suspended()} should be called, and the {@link #work()}
243     * method should return.
244     *
245     * @since 5.8
246     */
247    boolean isSuspending();
248
249    /**
250     * Must be called by {@link Work} implementations to advertise that state saving is done, when
251     * {@link #isSuspending()} returned {@code true}. After this is called, the {@link #work()} method should return.
252     *
253     * @since 5.8
254     */
255    void suspended();
256
257    /**
258     * CALLED BY THE WORK MANAGER (not user code) to check if this work instance really suspended.
259     *
260     * @since 5.8
261     */
262    boolean isWorkInstanceSuspended();
263
264    /**
265     * CALLED BY THE WORK MANAGER (not user code) to set this work instance's state.
266     *
267     * @since 5.8
268     */
269    void setWorkInstanceState(State state);
270
271    /**
272     * CALLED BY THE WORK MANAGER (not user code) to get this work instance's state.
273     * <p>
274     * Used only to get the final state of a completed instance ( {@link State#COMPLETED}, {@link State#FAILED} or
275     * {@link State#CANCELED}).
276     *
277     * @since 5.8
278     */
279    State getWorkInstanceState();
280
281    /**
282     * DO NOT USE THIS - gets the state of this work instance.
283     * <p>
284     * This should not be used because for non in-memory persistence, the work instance gets serialized and deserialized
285     * for running and when retrieved after completion, and therefore the original instance cannot get updated after the
286     * original scheduling.
287     *
288     * @return the state
289     * @deprecated since 5.8, use {@link WorkManager#getWorkState} instead
290     */
291    @Deprecated
292    State getState();
293
294    /**
295     * Gets the category for this work.
296     * <p>
297     * Used to choose an execution queue.
298     *
299     * @return the category, or {@code null} for the default
300     */
301    String getCategory();
302
303    /**
304     * Gets a human-readable name for this work instance.
305     *
306     * @return a human-readable name
307     */
308    String getTitle();
309
310    /**
311     * Gets a human-readable status for this work instance.
312     *
313     * @return a human-readable status
314     */
315    String getStatus();
316
317    /**
318     * Gets the time at which this work instance was first scheduled.
319     *
320     * @return the scheduling time (milliseconds since epoch)
321     */
322    long getSchedulingTime();
323
324    /**
325     * Gets the time at which this work instance was first started.
326     *
327     * @return the start time (milliseconds since epoch), or {@code 0} if not stated
328     */
329    long getStartTime();
330
331    /**
332     * Gets the time at which this work instance was completed, suspended or failed.
333     *
334     * @return the completion time (milliseconds since epoch), or {@code 0} if not completed
335     */
336    long getCompletionTime();
337
338    /**
339     * CALLED BY THE WORK MANAGER (not user code) to set the start time on the work instance.
340     *
341     * @since 5.9.2
342     */
343    void setStartTime();
344
345    /**
346     * This method should be called periodically by the actual work method when it knows of its progress.
347     *
348     * @param progress the progress
349     * @see Progress#Progress(float)
350     * @see Progress#Progress(long, long)
351     */
352    void setProgress(Progress progress);
353
354    /**
355     * Gets a progress report for this work instance.
356     *
357     * @return a progress report, not {@code null}
358     */
359    Progress getProgress();
360
361    /**
362     * Gets the user on behalf of which this work is done.
363     *
364     * @return the originating username, or {@code null}
365     * @since 8.1
366     */
367    String getOriginatingUsername();
368
369    /**
370     * Gets the document impacted by the work.
371     * <p>
372     * Returns {@code null} if the work isn't about a single document.
373     *
374     * @return the document, or {@code null}. This is always a {@link DocumentLocation} with an {@link IdRef}
375     * @since 5.8
376     */
377    DocumentLocation getDocument();
378
379    /**
380     * Gets the documents impacted by the work.
381     * <p>
382     * Returns {@code null} if the work isn't about documents.
383     *
384     * @return the documents, or an empty list. List elements are always a {@link DocumentLocation} with an
385     *         {@link IdRef}
386     * @since 5.8
387     */
388    List<DocumentLocation> getDocuments();
389
390    /**
391     * Returns {@code true} if {@link #getDocument} is only the root of a set of documents on which this Work instance
392     * will act.
393     *
394     * @return {@code true} if a whole tree is impacted
395     * @since 5.8
396     */
397    boolean isDocumentTree();
398
399    /**
400     * Returns the schedule path
401     *
402     * @since 5.8
403     */
404    WorkSchedulePath getSchedulePath();
405
406    /**
407     * Set the schedule path, internal usage
408     *
409     * @since 5.8
410     */
411    void setSchedulePath(WorkSchedulePath path);
412
413    /**
414     * CALLED BY THE WORK MANAGER (not user code) to get this work instance's result.
415     *
416     * @since 7.4
417     */
418    String getWorkInstanceResult();
419}