class AfterCompletion {

  public synchronized void doOp(TXState txState, CancelCriterion cancelCriterion) {
    // there should be a transaction timeout that keeps this thread
    // from sitting around forever if the client goes away
    // The above was done by setting afterCompletionCancelled in txState
    // during cleanup. When client departed, the transaction/JTA
    // will be timed out and cleanup code will be executed.
    try {
      waitForExecuteOrCancel(cancelCriterion);
    } catch (RuntimeException | Error ignore) {
      action = Action.CANCEL;
    }
    started = true;
    logger.debug("executing afterCompletion notification");
    try {
      switch (action) {
        case CANCEL:
          txState.doCleanup();
          break;
        case COMMIT:
          txState.doAfterCompletionCommit();
          break;
        case ROLLBACK:
          txState.doAfterCompletionRollback();
          break;
      }
    } catch (RuntimeException exception) {
      this.exception = exception;
    } finally {
      logger.debug("afterCompletion notification completed");
      finished = true;
      notifyAll();
    }
  }

}