/**
  * Obtain the sleep duration. Note that the sleep duration can be negative if the session remains
  * offline for a period longer than the last execution/process time plus the timeout.
  *
  * @param timeMillis A <code>long</code>.
  * @param latestProcessTimeMillis A <code>long</code>.
  * @return A <code>long</code>.
  */
 private long getSleepMillis(final long timeMillis, final long latestProcessTimeMillis) {
   final long latest = Math.max(latestExecutionTime, latestProcessTimeMillis);
   logger.logVariable("latest", format(latest));
   final long sleep = latest + timeout - timeMillis;
   logger.logVariable("sleep", formatDuration(sleep));
   return sleep;
 }
 /** @see com.thinkparity.ophelia.model.util.daemon.DaemonJob#invoke() */
 @Override
 public void run() {
   interruptCount = 0;
   while (true) {
     final long now = System.currentTimeMillis();
     logger.logVariable("sessionModel.isOnline()", sessionModel.isOnline());
     final long latestProcessTime = queueModel.getLatestProcessTimeMillis();
     logger.logVariable("latestProcessTime", format(latestProcessTime));
     logger.logVariable("latestExecutionTime", format(latestExecutionTime));
     logger.logVariable("timeout", formatDuration(timeout));
     if (isQueueTimeout(now, latestProcessTime)
         && isExecutionTimeout(now)
         && sessionModel.isOnline()) {
       logger.logInfo("The session has expired and will be reclaimed.");
       try {
         queueModel.stopNotificationClient();
       } finally {
         try {
           sessionModel.logout();
         } finally {
           latestExecutionTime = System.currentTimeMillis();
         }
       }
     } else {
       logger.logInfo("The session has not expired.");
     }
     try {
       final long sleepMillis = getSleepMillis(now, latestProcessTime);
       if (0 > sleepMillis) {
         /* we break out of the reaper because we have been "offline"
          * longer than we'd like */
         logger.logInfo("Terminating session reaper.");
         break;
       } else {
         logger.logInfo("Session reaper sleeping.");
         Thread.sleep(getSleepMillis(now, latestProcessTime));
       }
     } catch (final InterruptedException ix) {
       interruptCount++;
       logger.logError(
           ix,
           "Session reaper interrupted:  {0}/{1}",
           interruptCount,
           DEFAULT_INTERRUPT_THRESHOLD);
       if (interruptCount + 1 > interruptThreshold) {
         interruptCount = 0;
         /* we break out of the reaper because we have encountered
          * too many interrupts (unlikely) */
         logger.logInfo("Terminating session reaper.");
         break;
       }
     }
   }
 }
 /**
  * Begin a transaction within a context if required. The context will define a transaction type
  * and if the type requires a transaction one will be begun.
  *
  * @param transaction A <code>Transaction</code>.
  * @param context A <code>TransactionContext</code>.
  * @throws NamingException
  * @throws NotSupportedException
  */
 private void beginXA(final Transaction transaction, final TransactionContext context) {
   XA_LOGGER.logVariable("transaction", transaction);
   XA_LOGGER.logVariable("context", context);
   /* when the transaction context is set, nothing is done
    *
    * when the transaction context is null, no transaction boundary is
    * currently set, so we need to check whether nor not to begin the
    * transaction based upon the type */
   if (isSetXAContext()) {
     switch (context.getType()) {
       case REQUIRED:
         XA_LOGGER.logInfo("{0}Join {1} with {2}.", "\t\t", context, getXAContext());
         break;
       case REQUIRES_NEW:
         LOGGER.logFatal("New transaction required-{0}", context);
         XA_LOGGER.logFatal("New transaction required-{0}", context);
         Assert.assertUnreachable("New transaction required-{0}", context);
         break;
       case NEVER:
         XA_LOGGER.logInfo("{0}No transaction participation-{1}.", "\t\t", context);
         break;
       case SUPPORTED:
         break;
       default:
         LOGGER.logFatal("Unknown transaction type.");
         XA_LOGGER.logFatal("Unknown transaction type.");
         Assert.assertUnreachable("Unknown transaction type.");
     }
   } else {
     switch (context.getType()) {
       case REQUIRES_NEW:
       case REQUIRED:
         setXAContext(context);
         transaction.begin();
         XA_LOGGER.logInfo("Begin transaction-{0}.", context);
         break;
       case NEVER:
         XA_LOGGER.logInfo("{0}No transaction participation-{1}.", "\t\t", context);
         break;
       case SUPPORTED:
         break;
       default:
         LOGGER.logFatal("Unknown transaction type.");
         XA_LOGGER.logFatal("Unknown transaction type.");
         Assert.assertUnreachable("Unknown transaction type.");
     }
   }
 }
 /**
  * Determine whether or not the last read of the queue was before the time in millis.
  *
  * @param timeMillis A <code>long</code>.
  * @return True if the time minus the last queue process time is greater than the timeout.
  */
 private boolean isQueueTimeout(final long timeMillis, final long latestProcessTimeMillis) {
   final long queueDuration = timeMillis - latestProcessTimeMillis;
   logger.logVariable("queueDuration", formatDuration(queueDuration));
   if (0 > queueDuration) {
     return false;
   } else {
     return (timeout - timeoutMargin) < queueDuration;
   }
 }
 /**
  * Determine whether or not the last execution was before the time in millis.
  *
  * @param timeMillis A <code>long</code>.
  * @return True if the time minus the last execution time is greater than the timeout.
  */
 private boolean isExecutionTimeout(final long timeMillis) {
   final long executionDuration = timeMillis - latestExecutionTime;
   logger.logVariable("executionDuration", formatDuration(executionDuration));
   if (0 > executionDuration) {
     return false;
   } else {
     return (timeout - timeoutMargin) < executionDuration;
   }
 }
 /** @see com.thinkparity.network.NetworkConnection#write(byte[]) */
 @Override
 public void write(final byte[] buffer) throws NetworkException {
   logger.logTraceId();
   logger.logVariable("buffer", buffer);
   ensureConnected();
   try {
     write(buffer, true);
   } catch (final IOException iox) {
     throw new NetworkException(iox);
   }
 }
 /** @see com.thinkparity.network.NetworkConnection#read(byte[]) */
 @Override
 public int read(final byte[] buffer) throws NetworkException {
   logger.logTraceId();
   logger.logVariable("buffer", buffer);
   ensureConnected();
   try {
     return input.read(buffer);
   } catch (final IOException iox) {
     throw new NetworkException(iox);
   }
 }
  /**
   * @see java.lang.reflect.InvocationHandler#invoke(java.lang.Object, java.lang.reflect.Method,
   *     java.lang.Object[])
   */
  public Object invoke(final Object proxy, final Method method, final Object[] args)
      throws Throwable {
    LOGGER.logTrace("Invoking method {0} on {1} in {2}.", method, model, workspace);
    if (null != args && 0 < args.length && LOGGER.isDebugEnabled()) {
      for (int i = 0; i < args.length; i++) LOGGER.logDebug("args[{0}]:{1}", i, args[i]);
    }
    final Object lock;
    switch (extractLock(method)) {
      case NONE:
        lock = new Object();
        break;
      case EXCLUSIVE: // deliberate fall-through
      case LOCAL_READ: // deliberate fall-through
      default:
        lock = workspace;
    }
    synchronized (lock) {
      final Transaction transaction = workspace.getTransaction();
      final TransactionContext transactionContext = newXAContext(method);
      beginXA(transaction, transactionContext);
      try {
        /* if the method is annotated as online, ensure that we are
         * online */
        if (isOnline(method)) model.ensureOnline();
        model.setInvocationContext(
            new ModelInvocationContext() {
              public Object[] getArguments() {
                return null == args ? NO_ARGS : args;
              }

              public Method getMethod() {
                return method;
              }
            });
        final Object metricsContext = newMetricsContext(lock, method);
        ModelInvocationMetrics.begin(metricsContext);
        final Object result = method.invoke(model, args);
        ModelInvocationMetrics.end(metricsContext);
        model.notifyListeners();
        return LOGGER.logVariable("result", result);
      } catch (final InvocationTargetException itx) {
        rollbackXA(transaction, transactionContext);
        throw itx.getTargetException();
      } catch (final Throwable t) {
        rollbackXA(transaction, transactionContext);
        throw t;
      } finally {
        completeXA(transaction, transactionContext);
      }
    }
  }