All Downloads are FREE. Search and download functionalities are using the official Maven repository.

loci.messaging.ConnectionsBase.scala Maven / Gradle / Ivy

The newest version!
package loci
package messaging

import communicator._
import compatibility.jdkCollectionConverters._

import java.util.concurrent.{ConcurrentHashMap, ConcurrentLinkedQueue}
import java.util.concurrent.atomic.AtomicBoolean
import java.util.concurrent.locks.ReentrantLock

import scala.collection.mutable
import scala.util.{Failure, Success, Try}

object ConnectionsBase {
  type Protocol = ProtocolCommon with Bidirectional
}

trait ConnectionsBase[R, M] {
  protected def terminatedException =
    new ConnectionException("remote connection terminated")

  protected class BaseState {
    private val running = new AtomicBoolean(false)
    private val terminated = new AtomicBoolean(false)

    def run() = running.set(true)
    def terminate() = terminated.set(true)
    def isRunning = running.get && !terminated.get
    def isTerminated = terminated.get

    private[ConnectionsBase] val messages = mutable.ListBuffer.empty[(R, M)]
    private[ConnectionsBase] val listeners = mutable.ListBuffer.empty[Listening]
    private[ConnectionsBase] val remotes = new ConcurrentLinkedQueue[R]
    private[ConnectionsBase] val connections =
      new ConcurrentHashMap[R, Connection[ConnectionsBase.Protocol]]
  }

  protected val state: BaseState

  private val doRemoteJoined = Notice.Stream[R]

  private val doRemoteLeft = Notice.Stream[R]

  private val doTerminated = Notice.Steady[List[R]]

  private val doReceive = Notice.Stream[(R, M)]

  val remoteJoined: Notice.Stream[R] = doRemoteJoined.notice

  val remoteLeft: Notice.Stream[R] = doRemoteLeft.notice

  val terminated: Notice.Steady[List[R]] = doTerminated.notice

  val receive: Notice.Stream[(R, M)] = doReceive.notice

  def remotes: List[R] = state.remotes.asScala.toList

  def isRunning: Boolean = state.isRunning

  def isTerminated: Boolean = state.isTerminated

  def isConnected(remote: R): Boolean =
    state.connections containsKey remote

  def run(): Unit =
    sync {
      if (!state.isTerminated && !state.isRunning) {
        logging.trace("connection system started")

        state.messages foreach doReceive.fire
        state.messages.clear()
        state.run()
      }
    }

  def terminate(): Unit =
    sync {
      if (!state.isTerminated) {
        logging.trace("connection system terminated")

        val remotes = state.remotes.asScala.toList
        val connections = state.connections.asScala.toSeq
        val listeners = state.listeners.toSeq

        state.terminate()

        afterSync {
          connections foreach { case (_, connection) => connection.close() }
          listeners foreach { _.stopListening() }
          doTerminated.set(remotes)

          state.remotes.clear()
          state.connections.clear()
          state.listeners.clear()
        }
      }
    }

  def disconnect(remote: R): Unit =
    if (!state.isTerminated)
      Option(state.connections get remote) foreach { _.close() }

  def send(remote: R, message: M): Unit =
    if (!state.isTerminated)
      state.connections get remote match {
        case null =>
          logging.warn(s"message not sent to unconnected remote $remote: $message")
        case connection =>
          connection.send(serializeMessage(message))
      }
    else
      logging.warn(s"message not sent after connection system shutdown to remote $remote: $message")

  private def doBufferedReceive(remote: R, message: M): Unit =
    if (!state.isTerminated) {
      if (state.isRunning)
        doReceive.fire(remote -> message)
      else
        sync {
          if (state.isRunning)
            doReceive.fire(remote -> message)
          else
            state.messages += remote -> message
        }
    }

  private val syncLock = new ReentrantLock

  private val syncHandlers = new ThreadLocal[mutable.ListBuffer[() => Unit]] {
    override def initialValue = mutable.ListBuffer.empty
  }

  protected def sync[T](body: => T): T = {
    syncLock.lock
    try body
    finally {
      syncLock.unlock
      if (syncLock.getHoldCount == 0) {
        val handlers = syncHandlers.get
        if (handlers.nonEmpty) {
          val result = handlers.result()
          handlers.clear()
          result foreach { _.apply() }
        }
      }
    }
  }

  protected def afterSync[T](handler: => T): Unit =
    if (syncLock.getHoldCount == 0)
      throw new UnsupportedOperationException(
        "current thread does not hold the synchronization lock")
    else
      syncHandlers.get += { () => handler }


  protected def deserializeMessage(message: MessageBuffer): Try[M]

  protected def serializeMessage(message: M): MessageBuffer


  protected def addListening(listening: Listening): Try[Unit] =
    sync {
      if (!isTerminated) {
        state.listeners += listening
        Success(())
      }
      else
        Failure(terminatedException)
    }

  protected def addConnection(
      remote: R, connection: Connection[ConnectionsBase.Protocol]): Try[Unit] =
    sync {
      if (!isTerminated) {
        logging.info(s"established connection to remote $remote")

        state.connections.put(remote, connection)
        state.remotes.add(remote)

        afterSync { doRemoteJoined.fire(remote) }

        var receiveHandler: Notice[_] = null
        var closedHandler: Notice[_] = null

        receiveHandler = connection.receive foreach {
          deserializeMessage(_) map { doBufferedReceive(remote, _) }
        }

        closedHandler = connection.closed foreach { _ =>
          if (receiveHandler != null)
            receiveHandler.remove()
          if (closedHandler != null)
            closedHandler.remove()
          removeConnection(remote)
        }

        if (!connection.open) {
          receiveHandler.remove()
          closedHandler.remove()
          removeConnection(remote)
          Failure(terminatedException)
        }
        else
          Success(())
      }
      else
        Failure(terminatedException)
    }

  protected def removeConnection(remote: R): Unit =
    sync {
      if (state.connections containsKey remote) {
        logging.info(s"terminated connection to remote $remote")

        state.remotes.remove(remote)
        state.connections.remove(remote)
        afterSync { doRemoteLeft.fire(remote) }
      }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy