/**
  * 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;
 }
 /**
  * Complete a transaction. If the transaction belongs to the given transaction context a
  * rollback/commit will be attempted.
  *
  * @param transaction A <code>Transaction</code>.
  * @param transactionContext A <code>TransactionContext</code>.
  * @throws HeuristicMixedException
  * @throws HeuristicRollbackException
  * @throws RollbackException
  */
 private void completeXA(final Transaction transaction, final TransactionContext context) {
   if (isSetXAContext() && getXAContext().equals(context)) {
     unsetXAContext();
     if (transaction.isRollbackOnly()) {
       XA_LOGGER.logInfo("Rolling transaction-{0}.", context);
       transaction.rollback();
     } else {
       XA_LOGGER.logInfo("Committing transaction-{0}.", context);
       transaction.commit();
     }
   } else {
     switch (context.getType()) {
       case REQUIRES_NEW:
       case REQUIRED:
       case SUPPORTED:
         break;
       case NEVER:
         XA_LOGGER.logInfo("No transaction participation-{0}.", context);
         break;
       default:
         LOGGER.logFatal("Unknown transaction type.");
         XA_LOGGER.logFatal("Unknown transaction type.");
         Assert.assertUnreachable("Unknown transaction type.");
     }
   }
 }
 /** @see com.thinkparity.network.NetworkConnection#connect() */
 @Override
 public void connect() throws NetworkException {
   logger.logTraceId();
   logger.logInfo("{0} - Connect.", getId());
   Exception lastX = null;
   connected = false;
   for (final NetworkProxy proxy : proxies) {
     this.proxy = proxy;
     try {
       connectViaProxy();
       setSocketOptions();
       setSocketStreams();
       logger.logInfo("{0} - Connected.", getId());
       logger.logDebug(
           "{0} - Local:  {1}:{2}",
           getId(), socket.getLocalAddress().getHostAddress(), socket.getLocalPort());
       final InetSocketAddress remoteSocketAddress =
           (InetSocketAddress) socket.getRemoteSocketAddress();
       logger.logDebug(
           "{0} - Remote:  {1}:{2}",
           getId(),
           remoteSocketAddress.getAddress().getHostAddress(),
           remoteSocketAddress.getPort());
       connected = true;
       break;
     } catch (final SocketException sx) {
       lastX = sx;
     } catch (final IOException iox) {
       lastX = iox;
     }
   }
   if (false == connected) {
     throw new NetworkException(lastX);
   }
 }
 /** Stop the stream service. */
 public void stop() {
   logger.logTraceId();
   logger.logInfo("Stopping stream service.");
   synchronized (this) {
     stopImpl();
   }
   logger.logInfo("Stream service stopped.");
 }
 /** Start the stream service. */
 public void start() {
   logger.logTraceId();
   logger.logInfo("Starting stream service.");
   synchronized (this) {
     startImpl();
   }
   logger.logInfo("Stream service started.");
 }
 /** Unset the current transaction context. */
 private void unsetXAContext() {
   final TransactionContext currentContext = getXAContext();
   if (null == currentContext) {
     LOGGER.logFatal("Context not set.");
     XA_LOGGER.logFatal("Context not set.");
   } else {
     XA_CONTEXT.remove(getXAThread());
   }
 }
 /** @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.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;
       }
     }
   }
 }
 /** @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);
   }
 }
  /** Stop the backup service. */
  private void stopImpl() {
    logger.logInfo("Stopping backup service .");
    try {
      eventHandler.stop();

      final WorkspaceModel workspaceModel = WorkspaceModel.getInstance();
      workspaceModel.close(workspace);

      logger.logInfo("Backup service stopped.");
    } catch (final Throwable t) {
      throw new BackupException(t, "Failed to stop backup service.");
    }
  }
 /**
  * Set the transaction context.
  *
  * @param context A <code>TransactionContext</code>.
  */
 private void setXAContext(final TransactionContext context) {
   final TransactionContext currentContext = getXAContext();
   if (null == currentContext) {
     XA_CONTEXT.put(getXAThread(), context);
   } else {
     LOGGER.logFatal(
         "Context already set.{0}  current:{1}{0}  context:{2}",
         Separator.SystemNewLine, currentContext, context);
     XA_LOGGER.logFatal(
         "Context already set.{0}  current:{1}{0}  context:{2}",
         Separator.SystemNewLine, currentContext, context);
   }
 }
 /**
  * @see
  *     com.thinkparity.ophelia.model.util.configuration.ReconfigureListener#reconfigure(com.thinkparity.ophelia.model.util.configuration.ReconfigureEvent)
  */
 @Override
 public void reconfigure(final ReconfigureEvent<Configuration> event) {
   if (event.isReconfigured(CFG_NAME_INTERRUPT_THRESHOLD)) {
     logger.logInfo("Reconfiguring session reaper's interrupt threshold.");
     setInterruptThreshold(event.getCurrent());
   } else if (event.isReconfigured(CFG_NAME_TIMEOUT)) {
     logger.logInfo("Reconfiguring session reaper's timeout.");
     setTimeout(event.getCurrent());
   } else if (event.isReconfigured(CFG_NAME_TIMEOUT_MARGIN)) {
     logger.logInfo("Reconfiguring session reaper's timeout margin.");
     setTimeoutMargin(event.getCurrent());
   }
 }
  /**
   * @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);
      }
    }
  }
  /**
   * Set socket options pulled from the network configuration.
   *
   * @throws SocketException
   */
  private void setSocketOptions() throws SocketException {
    final NetworkConfiguration configuration = networkImpl.getConfiguration();
    final Integer soLinger = configuration.getSoLinger(protocol, address, proxy, this);
    final boolean linger = -1 < soLinger ? true : false;
    logger.logDebug("{0} - linger:{1}", getId(), linger);
    logger.logDebug("{0} - soLinger:{1}", getId(), soLinger);
    final Integer soTimeout = configuration.getSoTimeout(protocol, address, proxy, this);
    logger.logDebug("{0} - soTimeout:{1}", getId(), soTimeout);
    final Boolean tcpNoDelay = configuration.getTcpNoDelay(protocol, address, proxy, this);
    logger.logDebug("{0} - tcpNoDelay:{1}", getId(), tcpNoDelay);

    socket.setSoLinger(linger, linger ? soLinger : -1);
    socket.setSoTimeout(soTimeout);
    socket.setTcpNoDelay(tcpNoDelay);
  }
 /**
  * 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;
   }
 }
 /**
  * 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;
   }
 }
  /** Start the backup service. This involves opening the backup workspace. */
  private void startImpl() {
    logger.logInfo("Starting backup service.");
    try {
      environment = readBackupEnvironment();
      workspace = readBackupWorkspace();
      Assert.assertNotNull(environment, "Backup environment not yet initialized.");
      Assert.assertNotNull(workspace, "Backup workspace not yet initialized.");
      final WorkspaceModel workspaceModel = WorkspaceModel.getInstance();
      if (!workspaceModel.isInitialized(workspace)) {
        workspaceModel.initialize(
            new ProcessAdapter() {
              @Override
              public void beginProcess() {}

              @Override
              public void beginStep(final Step step, final Object data) {}

              @Override
              public void determineSteps(final Integer steps) {}

              @Override
              public void endProcess() {}

              @Override
              public void endStep(final Step step) {}
            },
            new InitializeMediator() {
              public Boolean confirmRestorePremium() {
                return null;
              }

              public Boolean confirmRestoreStandard() {
                return null;
              }
            },
            workspace,
            null);
      }
      eventHandler.start(getModelFactory());
      logger.logInfo("Backup service started.");
    } catch (final Throwable t) {
      throw new BackupException(t, "Failed to start backup service.");
    }
  }
 /** Stop stream service implementation. */
 private void stopImpl() {
   logger.logTraceId();
   try {
     server.stop(Boolean.TRUE);
   } catch (final Throwable t) {
     throw new StreamException(t);
   } finally {
     server = null;
   }
 }
 /**
  * Obtain the annotated transaction type for the method. If the transaction type is not defined on
  * the method; attempt to retreive it from the class. If no transaction annotation is found
  * assert.
  *
  * @param method A <code>Method</code>.
  * @return A <code>TransactionType</code>.
  */
 private TransactionType extractXAType(final Method method) {
   ThinkParityTransaction transaction = method.getAnnotation(ThinkParityTransaction.class);
   if (null == transaction) {
     transaction = method.getDeclaringClass().getAnnotation(ThinkParityTransaction.class);
   }
   if (null == transaction) {
     LOGGER.logFatal(
         "Method {0} of class {1} does not define transactional behaviour.",
         method.getName(), method.getDeclaringClass().toString());
     XA_LOGGER.logFatal(
         "Method {0} of class {1} does not define transactional behaviour.",
         method.getName(), method.getDeclaringClass().toString());
   }
   Assert.assertNotNull(
       transaction,
       "Method {0} of class {1} does not define transactional behaviour.",
       method.getName(),
       method.getDeclaringClass().toString());
   return transaction.value();
 }
 /** Start stream service implementation. */
 private void startImpl() {
   logger.logTraceId();
   try {
     server =
         new StreamServer(
             new File(properties.getProperty(JivePropertyNames.THINKPARITY_STREAM_ROOT)),
             environment);
     server.start();
   } catch (final Throwable t) {
     server = null;
     throw new StreamException(t);
   }
 }
 /** @see com.thinkparity.network.NetworkConnection#disconnect() */
 @Override
 public void disconnect() {
   logger.logTraceId();
   logger.logInfo("{0} - Disconnect", getId());
   if (isConnected()) {
     try {
       if (Boolean.FALSE == protocol.isSecure()) {
         socket.shutdownInput();
       }
     } catch (final IOException iox) {
       logger.logWarning(iox, "{0} - Error disconnecting.", getId());
     } finally {
       try {
         if (Boolean.FALSE == protocol.isSecure()) {
           socket.shutdownOutput();
         }
       } catch (final IOException iox) {
         logger.logWarning(iox, "{0} - Error disconnecting.", getId());
       } finally {
         try {
           socket.close();
         } catch (final IOException iox) {
           logger.logWarning(iox, "{0} - Error disconnecting.", getId());
         } finally {
           socket = null;
           input = null;
           output = null;
           logger.logInfo("{0} - Disconnected", getId());
           connected = false;
         }
       }
     }
   } else {
     logger.logWarning("{0} - Is not connected.", getId());
   }
 }
 /**
  * Rollback a transaction. If the transaction belongs to the context; a rollback will be
  * attempted. If it does not; depending on the context type; the transaction's rollback only flag
  * will be set.
  *
  * @param transaction A <code>Transaction</code>.
  * @param transactionContext A <code>TransactionContext</code>.
  */
 private void rollbackXA(final Transaction transaction, final TransactionContext context) {
   switch (context.getType()) {
     case REQUIRES_NEW:
     case REQUIRED:
       XA_LOGGER.logInfo("Set rollback only-{0}.", context);
       transaction.setRollbackOnly();
       break;
     case NEVER:
       XA_LOGGER.logInfo("No transaction participation-{0}.", context);
       break;
     case SUPPORTED:
       if (transaction.isActive()) {
         XA_LOGGER.logInfo("Set rollback only-{0}.", context);
         transaction.setRollbackOnly();
       } else {
         XA_LOGGER.logTrace("Transaction for {0} is not active.", context);
       }
       break;
     default:
       LOGGER.logFatal("Unknown transaction type.");
       XA_LOGGER.logFatal("Unknown transaction type.");
       Assert.assertUnreachable("Unknown transaction type.");
   }
 }
 /** @see com.thinkparity.network.NetworkConnection#getId() */
 @Override
 public String getId() {
   logger.logTraceId();
   return id;
 }
 /**
  * Obtain the proxy.
  *
  * @return A <code>NetworkProxy</code>.
  */
 NetworkProxy getProxy() {
   logger.logTraceId();
   return proxy;
 }
 /**
  * Obtain the protocol.
  *
  * @return A <code>NetworkProtocol</code>.
  */
 NetworkProtocol getProtocol() {
   logger.logTraceId();
   return protocol;
 }
 /** @see com.thinkparity.network.NetworkConnection#getAddress() */
 public NetworkAddress getAddress() {
   logger.logTraceId();
   return address;
 }
 /** @see com.thinkparity.network.NetworkConnection#isConnected() */
 @Override
 public Boolean isConnected() {
   logger.logTraceId();
   return connected && socket.isConnected();
 }
 /** @see com.thinkparity.network.NetworkConnection#getSocket() */
 @Override
 public Socket getSocket() {
   logger.logTraceId();
   return socket;
 }
 /**
  * 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.");
     }
   }
 }