Discuss New Concept,New Technic,New Tools, Including EAI,BPM,SOA,Tibco,IBM MQ,Tuxedo, Cloud,Hadoop,NoSQL,J2EE,Ruby,Scala,Python, Performance,Scalability,Distributed,HA, Social Network,Machine Learning.

Scala-Tools

Oct 162012
 
 [repost ]Getting started with Scala, Akka and Sbt: the chat example  October 16, 2012  Posted by on October 16, 2012 at 10:34 am Akka, Scala-Tools Tagged with: , , ,  No Responses »

original:http://charless.org/?p=105

Scala is a general purpose programming language designed to express common programming patterns in a concise, elegant, and type-safe way. It smoothly integrates features of object-oriented and functional languages. Scala programs run on the Java VM, are byte code compatible with Java so you can make full use of existing Java libraries or existing application code.

Akka is the platform for the next generation event-driven, scalable and fault-tolerant architectures on the JVM.

Sbt is a build tool for Scala and Java projects that aims to do the basics well.

In this tutorial, I will show you how to quickly setup your environment to build and run the akka-sample-chat, a sample that comes with the akka distribution, that implements a simple chat system.

Requirements 

 

 

Setting up your local environment

Required environment

charless@linux-bash:~$ WORKDIR=/home/charless/Work/
charless@linux-bash:~$ cd $WORKDIR
charless@linux-bash:~/Work$ JAVA_HOME=/opt/jdk1.6.0_24/
charless@linux-bash:~/Work$ $JAVA_HOME/bin/java -version
java version ”1.6.0_24″
Java(TM) SE Runtime Environment (build 1.6.0_24-b07)
Java HotSpot(TM) Server VM (build 19.1-b02, mixed mode)

Install SBT

SBT is a build tool for Scala projects that aims to do the basics well. It requires Java 1.6 or later.

Summary 

  1. Download sbt-launch.jar
  2. Create the launcher
  3. Run sbt

 

Let’s begin by getting the sbt jar:

charless@linux-bash:~$ mkdir -p bin
charless@linux-bash:~$ cd bin
charless@linux-bash:~/bin$ curl -C - -O http://typesafe.artifactoryonline.com/typesafe/ivy-releases/org.scala-tools.sbt/sbt-launch/0.11.0/sbt-launch.jar
(…)

charless@linux-bash:~/bin$ ls
sbt-launch.jar

To create the launcher, create a file named sbt in ~/bin with the content of the following gist

#!/bin/bash
SBT_HOME=”$(cd “$(cd “$(dirname “$0″)”; pwd -P)”; pwd)”
JAVA_OPTS=”-Dfile.encoding=UTF8 -Xmx1536M -Xss1M -XX:+CMSClassUnloadingEnabled -XX:MaxPermSize=256m”
java $JAVA_OPTS -jar “$SBT_HOME/sbt-launch.jar” “$@”
view rawsbtThis Gist is brought to you using Simple Gist Embed.

By using shell commands:

charless@linux-bash:~/bin$ curl -C - -O https://raw.github.com/gist/1298019/55af692619eabc0b3094a7dcbc1172a0042de755/sbt
(…)
charless@linux-bash:~/bin$ chmod ugo+rx sbt

Open a new terminal and type the following command to test sbt (this may take some minutes as the first run downloads all dependencies):

charless@linux-bash:~$ cd /tmp
charless@linux-bash:/tmp$ ~/bin/sbt
(….)

[info] Set current project to default-8c3933 (in build file:/tmp/)

Ctrl-D

charless@linux-bash:/tmp$

 

If you have an error like this one

:: problems summary ::
:::: WARNINGS
[NOT FOUND  ] commons-codec#commons-codec;1.2!commons-codec.jar (0ms)
==== Maven2 Local: tried
file:///home/charless/.m2/repository/commons-codec/commons-codec/1.2/commons-codec-1.2.jar

Make sure to delete the directory of the uncomplete artifact from your maven local directory (“~/.m2/path/to/uncomplete/artifact”) and the”~/.ivy2″ directory; restart then the “sbt” command.

For the example:

rm -rf /home/charless/.m2/repository/commons-codec/commons-codec/1.2
rm -rf ~/.ivy2

 

 

Create the chat application

Summary 

  1. Create the project akka-chat-sample
  2. Run the chat application with  sbt console

 

By default, sbt works purely by convention. sbt will find the following automatically:

  • - Sources in the base directory
  • - Sources in src/main/scala or src/main/java
  • - Tests insrc/test/scala or src/test/java
  • - Data files in src/main/resources or src/test/resources
  • - jars in lib

You can run the project with sbt run or enter the Scala REPL with sbt consolesbt console sets up your project’s classpath so you can try out live Scala examples based on your project’s code.

For our example, we will build the following project in $WORKDIR/akka-sample-chat:

  • build.sbt
  • src/main/scala/ChatServer.scala

First, prepare the project:

charless@linux-bash:~/Work$ mkdir akka-sample-chat
charless@linux-bash:~/Work$ cd akka-sample-chat/
charless@linux-bash:~/Work/akka-sample-chat$ mkdir -p src/main/scala

Create a file named build.sbt in the $WORKDIR/akka-sample-chat directory with the content of the following gist

name := “AkkaSampleChat”
version := “1.0″
scalaVersion := “2.9.1″
resolvers += “Typesafe Repository” at “http://repo.typesafe.com/typesafe/releases/”
libraryDependencies ++= Seq(
  ”se.scalablesolutions.akka” % “akka-actor” % “1.2″,
  ”se.scalablesolutions.akka” % “akka-stm” % “1.2″,
  ”se.scalablesolutions.akka” % “akka-remote” % “1.2″
)

Then, create a file named ChatServer.scala in the $WORKDIR/akka-sample-chat/src/main/scala directory with the content of the following gist

  /**
* Copyright (C) 2009-2010 Scalable Solutions AB <http://scalablesolutions.se>.
*/
  package sample.chat
  import scala.collection.mutable.HashMap
  import akka.actor.{Actor, ActorRef}
  import akka.stm._
  import akka.config.Supervision.{OneForOneStrategy,Permanent}
  import Actor._
  import akka.event.EventHandler
  /******************************************************************************
Akka Chat Client/Server Sample Application
How to run the sample:
1. Fire up two shells. For each of them:
- Step down into to the root of the Akka distribution.
- Set ‘export AKKA_HOME=<root of distribution>.
- Run ‘sbt console’ to start up a REPL (interpreter).
2. In the first REPL you get execute:
- scala> import sample.chat._
- scala> import akka.actor.Actor._
- scala> val chatService = actorOf[ChatService].start()
3. In the second REPL you get execute:
- scala> import sample.chat._
- scala> ClientRunner.run
4. See the chat simulation run.
5. Run it again to see full speed after first initialization.
6. In the client REPL, or in a new REPL, you can also create your own client
- scala> import sample.chat._
- scala> val myClient = new ChatClient(“<your name>”)
- scala> myClient.login
- scala> myClient.post(“Can I join?”)
- scala> println(“CHAT LOG:nt” + myClient.chatLog.log.mkString(“nt”))
That’s it. Have fun.
******************************************************************************/
  /**
* ChatServer’s internal events.
*/
  sealed trait Event
  case class Login(user: String) extends Event
  case class Logout(user: String) extends Event
  case class GetChatLog(from: String) extends Event
  case class ChatLog(log: List[String]) extends Event
  case class ChatMessage(from: String, message: String) extends Event
  /**
* Chat client.
*/
  class ChatClient(val name: String) {
    val chat = Actor.remote.actorFor(“chat:service”, “localhost”, 2552)
    def login = chat ! Login(name)
    def logout = chat ! Logout(name)
    def post(message: String) = chat ! ChatMessage(name, name + “: ” + message)
    def chatLog = (chat !! GetChatLog(name)).as[ChatLog].getOrElse(throw new Exception(“Couldn’t get the chat log from ChatServer”))
  }
  /**
* Internal chat client session.
*/
  class Session(user: String, storage: ActorRef) extends Actor {
    private val loginTime = System.currentTimeMillis
    private var userLog: List[String] = Nil
    EventHandler.info(this, “New session for user [%s] has been created at [%s]“.format(user, loginTime))
    def receive = {
      case msg @ ChatMessage(from, message) =>
        userLog ::= message
        storage ! msg
      case msg @ GetChatLog(_) =>
        storage forward msg
    }
  }
  /**
* Abstraction of chat storage holding the chat log.
*/
  trait ChatStorage extends Actor
  /**
* Memory-backed chat storage implementation.
*/
  class MemoryChatStorage extends ChatStorage {
    self.lifeCycle = Permanent
    private var chatLog = TransactionalVector[Array[Byte]]()
    EventHandler.info(this, “Memory-based chat storage is starting up…”)
    def receive = {
      case msg @ ChatMessage(from, message) =>
        EventHandler.debug(this, “New chat message [%s]“.format(message))
        atomic { chatLog + message.getBytes(“UTF-8″) }
      case GetChatLog(_) =>
        val messageList = atomic { chatLog.map(bytes => new String(bytes, “UTF-8″)).toList }
        self.reply(ChatLog(messageList))
    }
    override def postRestart(reason: Throwable) {
      chatLog = TransactionalVector()
    }
  }
  /**
* Implements user session management.
* <p/>
* Uses self-type annotation (this: Actor =>) to declare that it needs to be mixed in with an Actor.
*/
  trait SessionManagement { this: Actor =>
    val storage: ActorRef // needs someone to provide the ChatStorage
    val sessions = new HashMap[String, ActorRef]
    protected def sessionManagement: Receive = {
      case Login(username) =>
        EventHandler.info(this, “User [%s] has logged in”.format(username))
        val session = actorOf(new Session(username, storage))
        session.start()
        sessions += (username -> session)
      case Logout(username) =>
        EventHandler.info(this, “User [%s] has logged out”.format(username))
        val session = sessions(username)
        session.stop()
        sessions -= username
    }
    protected def shutdownSessions() {
      sessions.foreach { case (_, session) => session.stop() }
    }
  }
  /**
* Implements chat management, e.g. chat message dispatch.
* <p/>
* Uses self-type annotation (this: Actor =>) to declare that it needs to be mixed in with an Actor.
*/
  trait ChatManagement { this: Actor =>
    val sessions: HashMap[String, ActorRef] // needs someone to provide the Session map
    protected def chatManagement: Receive = {
      case msg @ ChatMessage(from, _) => getSession(from).foreach(_ ! msg)
      case msg @ GetChatLog(from) => getSession(from).foreach(_ forward msg)
    }
    private def getSession(from: String) : Option[ActorRef] = {
      if (sessions.contains(from))
        Some(sessions(from))
      else {
        EventHandler.info(this, “Session expired for %s”.format(from))
        None
      }
    }
  }
  /**
* Creates and links a MemoryChatStorage.
*/
  trait MemoryChatStorageFactory { this: Actor =>
    val storage = Actor.actorOf[MemoryChatStorage]
    this.self.startLink(storage) // starts and links ChatStorage
  }
  /**
* Chat server. Manages sessions and redirects all other messages to the Session for the client.
*/
  trait ChatServer extends Actor {
    self.faultHandler = OneForOneStrategy(List(classOf[Exception]),5, 5000)
    val storage: ActorRef
    EventHandler.info(this, “Chat server is starting up…”)
    // actor message handler
    def receive: Receive = sessionManagement orElse chatManagement
    // abstract methods to be defined somewhere else
    protected def chatManagement: Receive
    protected def sessionManagement: Receive
    protected def shutdownSessions()
    override def postStop() {
      EventHandler.info(this, “Chat server is shutting down…”)
      shutdownSessions()
      self.unlink(storage)
      storage.stop()
    }
  }
  /**
* Class encapsulating the full Chat Service.
* Start service by invoking:
* <pre>
* val chatService = Actor.actorOf[ChatService].start()
* </pre>
*/
  class ChatService extends
    ChatServer with
    SessionManagement with
    ChatManagement with
    MemoryChatStorageFactory {
    override def preStart() {
      remote.start(“localhost”, 2552);
      remote.register(“chat:service”, self) //Register the actor with the specified service id
    }
  }
  /**
* Test runner starting ChatService.
*/
  object ServerRunner {
    def main(args: Array[String]) { ServerRunner.run() }
    def run() {
      actorOf[ChatService].start()
    }
  }
  /**
* Test runner emulating a chat session.
*/
  object ClientRunner {
    def main(args: Array[String]) { ClientRunner.run() }
    def run() {
      val client1 = new ChatClient(“jonas”)
      client1.login
      val client2 = new ChatClient(“patrik”)
      client2.login
      client1.post(“Hi there”)
      println(“CHAT LOG:nt” + client1.chatLog.log.mkString(“nt”))
      client2.post(“Hello”)
      println(“CHAT LOG:nt” + client2.chatLog.log.mkString(“nt”))
      client1.post(“Hi again”)
      println(“CHAT LOG:nt” + client1.chatLog.log.mkString(“nt”))
      client1.logout
      client2.logout
    }
  }

By using shell commands:

charless@linux-bash:~/Work/akka-sample-chat$ curl -C - -O https://raw.github.com/gist/1299718/5647388fb11e7db4c19bac46154e073bab395c26/build.sbt
(…)
charless@linux-bash:~/Work/akka-sample-chat$ cd src/main/scala/
charless@linux-bash:~/Work/akka-sample-chat/src/main/scala$ curl -C - -O https://raw.github.com/gist/1299742/db527df6babc07827293f5ffefcd57a47ea8a39b/ChatServer.scala
(…)
charless@linux-bash:~/Work/akka-sample-chat/src/main/scala$ ls
ChatServer.scala
charless@linux-bash:~/Work/akka-sample-chat/src/main/scala$ cd ../../..
charless@linux-bash:~/Work/akka-sample-chat$

Launch the sbt console from the $WORKDIR/akka-sample-chat project directory and type the following commands:

  • import sample.chat._
  • import akka.actor.Actor._
  • val chatService = actorOf[ChatService].start()

To do so, open a new terminal and type the following:

charless@linux-bash:~$ PROJECTDIR=/home/charless/Work/akka-sample-chat/
charless@linux-bash:~$ cd $PROJECTDIR
charless@linux-bash:~/Work/akka-sample-chat$ ~/bin/sbt console
[info] Set current project to AkkaSampleChat (in build file:/home/charless/Work/akka-sample-chat/)
(…)
Welcome to Scala version 2.9.1.final (OpenJDK Server VM, Java 1.6.0_22).

scala› import sample.chat._
import sample.chat._

scala› import akka.actor.Actor._
import akka.actor.Actor._

scala› val chatService = actorOf[ChatService].start()
[INFO]    [10/19/11 11:45 PM] [run-main] [ChatService] Chat server is starting up…
(…)
[INFO]    [10/19/11 11:45 PM] [run-main] [MemoryChatStorage] Memory-based chat storage is starting up…
(…)

scala›

In another terminal, launch the sbt console form the $WORKDIR/akka-sample-chat project directory and run the chat simulation run:

charless@linux-bash:~$ PROJECTDIR=/home/charless/Work/akka-sample-chat/
charless@linux-bash:~$ cd $PROJECTDIR
charless@linux-bash:~/Work/akka-sample-chat$ ~/bin/sbt console
[info] Set current project to AkkaSampleChat (in build file:/home/charless/Work/akka-sample-chat/)
(…)
Welcome to Scala version 2.9.1.final (OpenJDK Server VM, Java 1.6.0_22).

scala› import sample.chat._
import sample.chat._

scala› ClientRunner.run
[GENERIC] [10/19/11 11:57 PM] [RemoteClientStarted(akka.remote.netty.NettyRemoteSupport@acd5d4,localhost/127.0.0.1:2552)]
[GENERIC] [10/19/11 11:57 PM] [RemoteClientConnected(akka.remote.netty.NettyRemoteSupport@acd5d4,localhost/127.0.0.1:2552)]
CHAT LOG:
jonas: Hi there
CHAT LOG:
jonas: Hi there
patrik: Hello
CHAT LOG:
jonas: Hi there
patrik: Hello
jonas: Hi again

scala›

Run it again to see full speed after first initialization, and the chat log growing up.

Conclusion

In this article, we have seen how to install sbt and use it to build and test the akka-sample-chat application.

In a next article, we will see how to work on an akka project by using Eclipse.