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