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

org.ensime.server.Server.scala Maven / Gradle / Ivy

// Copyright: 2010 - 2016 https://github.com/ensime/ensime-server/graphs
// Licence: http://www.gnu.org/licenses/gpl-3.0.en.html
package org.ensime.server

import java.io._

import scala.concurrent.duration._
import scala.util._
import scala.util.Properties._

import akka.actor._
import akka.actor.SupervisorStrategy.Stop
import akka.http.scaladsl.Http
import akka.http.scaladsl.Http.ServerBinding
import akka.http.scaladsl.server.RouteResult.route2HandlerFlow
import akka.stream.ActorMaterializer
import akka.util.Timeout
import com.google.common.base.Charsets
import com.google.common.io.Files
import org.ensime.api._
import org.ensime.config._
import org.ensime.core._
import org.ensime.server.tcp.TCPServer
import org.ensime.util.Slf4jSetup
import org.slf4j._

class ServerActor(
    config: EnsimeConfig,
    protocol: Protocol,
    interface: String = "127.0.0.1"
) extends Actor with ActorLogging {

  override val supervisorStrategy = OneForOneStrategy() {
    case ex: Exception =>
      log.error(ex, s"Error with monitor actor ${ex.getMessage}")
      self ! ShutdownRequest(s"Monitor actor failed with ${ex.getClass} - ${ex.toString}", isError = true)
      Stop
  }

  def initialiseChildren(): Unit = {

    implicit val config: EnsimeConfig = this.config
    implicit val mat: ActorMaterializer = ActorMaterializer()
    implicit val timeout: Timeout = Timeout(10 seconds)

    val broadcaster = context.actorOf(Broadcaster(), "broadcaster")
    val project = context.actorOf(Project(broadcaster), "project")

    val preferredTcpPort = PortUtil.port(config.cacheDir, "port")
    val shutdownOnLastDisconnect = Environment.shutdownOnDisconnectFlag
    context.actorOf(Props(
      new TCPServer(
        config.cacheDir, protocol, project,
        broadcaster, shutdownOnLastDisconnect, preferredTcpPort
      )
    ), "tcp-server")

    // this is a bit ugly in a couple of ways
    // 1) web server creates handlers in the top domain
    // 2) We have to manually capture the failure to write the port file and lift the error to a failure.
    val webserver = new WebServerImpl(project, broadcaster)(config, context.system, mat, timeout)

    // async start the HTTP Server
    val selfRef = self
    val preferredHttpPort = PortUtil.port(config.cacheDir, "http")
    Http()(context.system).bindAndHandle(webserver.route, interface, preferredHttpPort.getOrElse(0)).onComplete {
      case Failure(ex) =>
        log.error(ex, s"Error binding http endpoint ${ex.getMessage}")
        selfRef ! ShutdownRequest(s"http endpoint failed to bind ($preferredHttpPort)", isError = true)

      case Success(ServerBinding(addr)) =>
        log.info(s"ENSIME HTTP on ${addr.getAddress}")
        try {
          PortUtil.writePort(config.cacheDir, addr.getPort, "http")
        } catch {
          case ex: Throwable =>
            log.error(ex, s"Error initializing http endpoint ${ex.getMessage}")
            selfRef ! ShutdownRequest(s"http endpoint failed to initialise: ${ex.getMessage}", isError = true)
        }
    }(context.system.dispatcher)

    Environment.info foreach log.info
  }

  override def preStart(): Unit = {
    try {
      initialiseChildren()
    } catch {
      case t: Throwable =>
        log.error(t, s"Error during startup - ${t.getMessage}")
        self ! ShutdownRequest(t.toString, isError = true)
    }
  }
  override def receive: Receive = {
    case req: ShutdownRequest =>
      triggerShutdown(req)
  }

  def triggerShutdown(request: ShutdownRequest): Unit = {
    Server.shutdown(context.system, request)
  }

}

object Server {
  Slf4jSetup.init()

  val log = LoggerFactory.getLogger("Server")

  def main(args: Array[String]): Unit = {
    val ensimeFileStr = propOrNone("ensime.config").getOrElse(
      throw new RuntimeException("ensime.config (the location of the .ensime file) must be set")
    )

    val ensimeFile = new File(ensimeFileStr)
    if (!ensimeFile.exists() || !ensimeFile.isFile)
      throw new RuntimeException(s".ensime file ($ensimeFile) not found")

    implicit val config: EnsimeConfig = try {
      EnsimeConfigProtocol.parse(Files.toString(ensimeFile, Charsets.UTF_8))
    } catch {
      case e: Throwable =>
        log.error(s"There was a problem parsing $ensimeFile", e)
        throw e
    }

    val protocol: Protocol = propOrElse("ensime.protocol", "swank") match {
      case "swank" => new SwankProtocol
      case "jerk" => new JerkProtocol
      case other => throw new IllegalArgumentException(s"$other is not a valid ENSIME protocol")
    }

    val system = ActorSystem("ENSIME")
    system.actorOf(Props(new ServerActor(config, protocol)), "ensime-main")
  }

  def shutdown(system: ActorSystem, request: ShutdownRequest): Unit = {
    val t = new Thread(new Runnable {
      def run(): Unit = {
        if (request.isError)
          log.error(s"Shutdown requested due to internal error: ${request.reason}")
        else
          log.info(s"Shutdown requested: ${request.reason}")

        log.info("Shutting down the ActorSystem")
        Try(system.shutdown())

        log.info("Awaiting actor system termination")
        Try(system.awaitTermination())

        log.info("Shutdown complete")
        if (!propIsSet("ensime.server.test")) {
          if (request.isError)
            System.exit(1)
          else
            System.exit(0)
        }
      }
    })
    t.start()
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy