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}