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

codacy.events.EventBus.scala Maven / Gradle / Ivy

The newest version!
package codacy.events

import java.nio.charset.Charset

import CirceVersionSpecific._
import akka.actor.{ActorNotFound, ActorRef, InvalidActorNameException, Props}
import com.spingo.op_rabbit._
import shapeless.{lazily, Lazy}
import io.circe.syntax._
import akka.pattern.ask
import codacy.events.internal.{EventActorSystem, EventLogger}
import com.spingo.op_rabbit.Message.Ack
import com.spingo.op_rabbit.properties.DeliveryModePersistence
import io.circe.{parser, Decoder, Json, Printer}

import scala.concurrent.duration._
import scala.concurrent.Future

trait RabbitPickling {

  implicit def unmarshallerFromDecoder[A](implicit decoder: Decoder[A]) = {
    new RabbitUnmarshaller[A] {
      override def unmarshall(value: Array[Byte], contentType: Option[String], contentEncoding: Option[String]) = {
        circeJsonMarshaller.unmarshall(value, contentType, contentEncoding).as[A].fold(throw _, identity(_))
      }
    }
  }

  implicit lazy val circeJsonMarshaller: RabbitMarshaller[Json] with RabbitUnmarshaller[Json] = {
    new RabbitMarshaller[Json] with RabbitUnmarshaller[Json] {

      private val encoding = "UTF-8"
      private val utf8 = Charset.forName(encoding)

      override protected val contentType = "application/json"
      override protected val contentEncoding = Some(encoding)

      override def marshall(value: Json): Array[Byte] = {
        Printer.noSpaces.copy(dropNullValues = true).print(value).getBytes(utf8)
      }

      override def unmarshall(value: Array[Byte], contentType: Option[String], contentEncoding: Option[String]) = {
        val charset = contentEncoding match {
          case None | Some(`encoding`) => utf8
          case Some(other) => Charset.forName(other)
        }
        parser.parse(new String(value, charset)).fold(throw _, identity(_))
      }

    }
  }
}

sealed trait PublishedEvent

object PublishedEvent {
  private[events] object instance extends PublishedEvent
}

trait EventBus {
  def publish[E <: Event](event: E): Future[PublishedEvent]

  def terminate(): Future[Unit]
}

object EventBus {

  object Empty extends EventBus {
    override def publish[E <: Event](event: E) =
      Future.successful(PublishedEvent.instance)

    override def terminate(): Future[Unit] =
      Future.successful(())
  }
  def empty: EventBus = Empty

  trait Components { self: AsyncRabbitControl.Components =>
    def eventLogger = internal.defaultEventLogger
    lazy val eventBus = {
      eventBusFromRabbitControl(self.asyncRabbitControl, eventLogger)
    }
  }

  /*For implicit dependency injection */
  implicit def eventBusFromRabbitControl(implicit
      rc: Lazy[AsyncRabbitControl],
      logger: EventLogger = internal.defaultEventLogger
  ): EventBus =
    new EventBus with RabbitPickling {
      private lazy val rabbitControl: AsyncRabbitControl = lazily[AsyncRabbitControl]
      implicit private def ectx = rabbitControl.actorSystem.dispatcher

      override def publish[E <: Event](event: E) = {
        val message =
          Message.topic(event.json.asJson, event.pathString, properties = List(DeliveryModePersistence.persistent))
        rabbitControl.rabbitControl
          .flatMap(_.?(message)(4.seconds))
          .collect { case Ack(_) => PublishedEvent.instance }
          .recoverWith { case err =>
            logger.error(s"Publishing message: ${event.pathString} failed: ${err.getMessage}", err)
            Future.failed(err)
          }
      }

      override def terminate(): Future[Unit] = {
        Future {
          rabbitControl.actorSystem.terminate()
        }.map { _ =>
          logger.info(s"Terminated actor system successfully.")
        }.recoverWith { case err =>
          logger.error("Error terminating actor system.", err)
          Future.failed(err)
        }
      }
    }

}

trait ConnectionParamsComponents[M[_]] {
  def connectionParams: M[ConnectionParams]
}

trait AsyncRabbitControl extends Any {
  private[events] def rabbitControl(): Future[ActorRef]
  private[events] def actorSystem: EventActorSystem
}

object AsyncRabbitControl {

  trait Components { self: ConnectionParamsComponents[Future] =>
    def eventActorSystem: EventActorSystem
    lazy val asyncRabbitControl =
      asyncRabbitControlFromActorSystem(eventActorSystem, self)
  }

  implicit def asyncRabbitControlFromActorSystem(implicit
      as: Lazy[EventActorSystem],
      cp: Lazy[ConnectionParamsComponents[Future]]
  ): AsyncRabbitControl = {
    new AsyncRabbitControl {
      private lazy val connectionParams = lazily[ConnectionParamsComponents[Future]].connectionParams

      override private[events] lazy val actorSystem = lazily[EventActorSystem]

      override def rabbitControl() = {
        import actorSystem.dispatcher

        actorSystem
          .actorSelection(actorSystem / ACTOR_NAME)
          .resolveOne(500.millis)
          .recoverWith { case ActorNotFound(_) =>
            connectionParams
              .map { params =>
                actorSystem.actorOf(Props(new RabbitControl(params)), ACTOR_NAME)
              }
              .recoverWith { case _: InvalidActorNameException => // concurrent creation handling
                rabbitControl()
              }
          }
      }
    }
  }

  private lazy val ACTOR_NAME = "RabbitControl"

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy