From d31e56404c52f9165fa0a6dc84f7a4c9fb268dc8 Mon Sep 17 00:00:00 2001
From: Otto Ringhofer <otto.ringhofer@gmail.com>
Date: Sun, 18 Mar 2018 12:43:02 +0100
Subject: [PATCH] code sent by Alexander Schrei

  developed at Scala Coding Session - March 2018
  changed to simple project, import of "org.vaadin.addons" % "vaactor"
---
 build.sbt                                     |  25 +++++
 project/build.properties                      |   1 +
 project/plugins.sbt                           |   1 +
 .../scala_vienna/raffle/RaffleComponent.scala | 102 ++++++++++++++++++
 .../scala_vienna/raffle/RaffleServer.scala    |  88 +++++++++++++++
 .../scala_vienna/raffle/RaffleServlet.scala   |  19 ++++
 .../org/scala_vienna/raffle/RaffleUI.scala    |  18 ++++
 7 files changed, 254 insertions(+)
 create mode 100644 build.sbt
 create mode 100644 project/build.properties
 create mode 100644 project/plugins.sbt
 create mode 100644 src/main/scala/org/scala_vienna/raffle/RaffleComponent.scala
 create mode 100644 src/main/scala/org/scala_vienna/raffle/RaffleServer.scala
 create mode 100644 src/main/scala/org/scala_vienna/raffle/RaffleServlet.scala
 create mode 100644 src/main/scala/org/scala_vienna/raffle/RaffleUI.scala

diff --git a/build.sbt b/build.sbt
new file mode 100644
index 0000000..87e020b
--- /dev/null
+++ b/build.sbt
@@ -0,0 +1,25 @@
+name := "vaadin-raffle"
+
+version := "0.1"
+
+scalaVersion := "2.12.4"
+
+resolvers ++= Seq(
+  "vaadin-addons" at "http://maven.vaadin.com/vaadin-addons"
+)
+
+val vaadinVersion = "8.3.1"
+val akkaVersion = "2.5.11"
+libraryDependencies ++= Seq(
+  "org.vaadin.addons" % "vaactor" % "1.0.2",
+  "javax.servlet" % "javax.servlet-api" % "3.1.0" % "provided",
+  "com.vaadin" % "vaadin-server" % vaadinVersion,
+  "com.vaadin" % "vaadin-client-compiled" % vaadinVersion,
+  "com.vaadin" % "vaadin-themes" % vaadinVersion,
+  "com.vaadin" % "vaadin-push" % vaadinVersion,
+  "com.typesafe.akka" %% "akka-actor" % akkaVersion
+)
+
+containerLibs in Jetty := Seq("org.eclipse.jetty" % "jetty-runner" % "9.3.21.v20170918" intransitive())
+
+enablePlugins(JettyPlugin)
diff --git a/project/build.properties b/project/build.properties
new file mode 100644
index 0000000..5517665
--- /dev/null
+++ b/project/build.properties
@@ -0,0 +1 @@
+sbt.version = 1.1.1
\ No newline at end of file
diff --git a/project/plugins.sbt b/project/plugins.sbt
new file mode 100644
index 0000000..a99c689
--- /dev/null
+++ b/project/plugins.sbt
@@ -0,0 +1 @@
+addSbtPlugin("com.earldouglas" % "xsbt-web-plugin" % "4.0.2")
diff --git a/src/main/scala/org/scala_vienna/raffle/RaffleComponent.scala b/src/main/scala/org/scala_vienna/raffle/RaffleComponent.scala
new file mode 100644
index 0000000..7dd301a
--- /dev/null
+++ b/src/main/scala/org/scala_vienna/raffle/RaffleComponent.scala
@@ -0,0 +1,102 @@
+package org.scala_vienna.raffle
+
+import org.scala_vienna.raffle.RaffleServer._
+import org.vaadin.addons.vaactor.Vaactor.VaactorComponent
+import org.vaadin.addons.vaactor.VaactorUI
+import com.vaadin.data.provider.{ DataProvider, ListDataProvider }
+import com.vaadin.server.Sizeable
+import com.vaadin.ui.themes.ValoTheme
+import com.vaadin.ui._
+import scala.collection.JavaConverters._
+
+class RaffleComponent(override val vaactorUI: VaactorUI, title: String) extends CustomComponent with VaactorComponent {
+  /** Contains list of chatroom menbers */
+  val participantsList = new java.util.ArrayList[String]()
+  val participantsDataProvider: ListDataProvider[String] = DataProvider.ofCollection[String](participantsList)
+  val participantsPanel: ListSelect[String] = new ListSelect("Raffle Participants", participantsDataProvider) {
+    setWidth(100, Sizeable.Unit.PIXELS)
+  }
+
+  /** Contains list of messages from chatroom */
+  //val chatList = new java.util.ArrayList[ChatServer.Statement]()
+  //val chatDataProvider: ListDataProvider[ChatServer.Statement] = DataProvider.ofCollection[ChatServer.Statement](chatList)
+  //val chatPanel: Grid[ChatServer.Statement] = new Grid[ChatServer.Statement]("Chat", chatDataProvider) {
+  //  addColumn(d => d.name)
+  //  addColumn(d => d.msg)
+  //  setWidth(400, Sizeable.Unit.PIXELS)
+  //}
+
+  /** Contains username */
+  val userName = new TextField()
+
+  val loginPanel: HorizontalLayout = new HorizontalLayout {
+    setSpacing(true)
+    addComponents(
+      userName,
+      new Button("Enter Raffle", _ => { RaffleServer.raffleServer ! Subscribe(Client(userName.getValue, self)) })
+    )
+  }
+
+
+  /** Contains user interface for login/logout and sending of messages */
+  val userPanel = new Panel(
+    new VerticalLayout {
+      setSpacing(true)
+      setMargin(true)
+      addComponents(
+        new HorizontalLayout {
+          setSpacing(true)
+          addComponents(loginPanel)
+        })
+    }
+  )
+
+  val startButton = new Button("Start", _ => { RaffleServer.raffleServer ! StartRaffle })
+
+  val winnerLabel: Label = new Label {
+    setValue("Winner: ")
+    addStyleName(ValoTheme.LABEL_H2)
+  }
+
+  startButton.setVisible(false)
+
+  setCompositionRoot(new VerticalLayout {
+
+    addComponents(
+      new Label {
+        setValue(title)
+        addStyleName(ValoTheme.LABEL_H1)
+      },
+      new HorizontalLayout {
+        setSpacing(true)
+        addComponents(
+          new VerticalLayout {
+            setSpacing(true)
+            addComponents(userPanel)
+          },
+          participantsPanel)
+      },
+      winnerLabel,
+      startButton)
+  })
+
+
+  /** Receive function, is called in context of VaadinUI (via ui.access) */
+  override def receive: PartialFunction[Any, Unit] = {
+    // User entered chatroom, update member list
+    case Enter(participants) =>
+
+      participantsList.clear()
+      participantsList.addAll(participants.asJava)
+      participantsDataProvider.refreshAll()
+
+    case SubscriptionFailure(error) =>
+      Notification.show(error, Notification.Type.WARNING_MESSAGE)
+
+    case YouAreCoordinator => startButton.setVisible(true)
+
+    case Result(name) => {
+      winnerLabel.setValue(s"Winner: $name")
+    }
+  }
+}
diff --git a/src/main/scala/org/scala_vienna/raffle/RaffleServer.scala b/src/main/scala/org/scala_vienna/raffle/RaffleServer.scala
new file mode 100644
index 0000000..1337025
--- /dev/null
+++ b/src/main/scala/org/scala_vienna/raffle/RaffleServer.scala
@@ -0,0 +1,88 @@
+package org.scala_vienna.raffle
+
+import org.vaadin.addons.vaactor.VaactorServlet
+
+import akka.actor.{ Actor, ActorRef, Props }
+
+import scala.util.Random
+
+object RaffleServer {
+
+  /** Clients handled by chat room
+    *
+    * @param name  name of user
+    * @param actor actorref for communication
+    */
+  case class Client(name: String, actor: ActorRef)
+
+  /** Subscribe client to chatroom, processed by chatroom
+    *
+    * @param client enters chatroom
+    */
+  case class Subscribe(client: Client)
+
+  case class SubscriptionSuccess(name: String)
+
+  case class SubscriptionFailure(error: String)
+
+  case class SubscriptionCancelled(name: String)
+
+  case class Participants(names: Seq[String])
+
+  case class Enter(participants: List[String])
+
+  case object YouAreCoordinator
+
+  case object StartRaffle
+
+  case class Result(name: String)
+
+
+  case object RequestMembers
+
+  /** ActoRef of chatroom actor */
+  val raffleServer: ActorRef = VaactorServlet.system.actorOf(Props[ServerActor], "raffleServer")
+
+  /** Actor handling chatroom */
+  class ServerActor extends Actor {
+
+    // List of participants
+    private var participants = Map.empty[String, Client]
+
+    /** Process received messages */
+    def receive: Receive = {
+      // Subscribe from client
+      case Subscribe(client) =>
+        // no name, reply with failure
+        if (client.name.isEmpty)
+          sender ! SubscriptionFailure("Empty name not valid")
+        // duplicate name, reply with failure
+        else if (participants.contains(client.name))
+          sender ! SubscriptionFailure(s"Name '${ client.name }' already subscribed")
+        // add client to chatroom, reply with success, brodcast Enter to clients
+        else {
+          if (client.name == "stevan") {
+            sender ! YouAreCoordinator
+          }
+          participants += client.name -> client
+          //sender ! SubscriptionSuccess(client.name)
+          broadcast(Enter(participants.keys.toList))
+        }
+      case StartRaffle => {
+        val winner = Random.nextInt(participants.size)
+        broadcast(Result(participants.keys.toSeq(winner)))
+      }
+      // RequestMembers from client, send member list to sending client
+      //case RequestMembers =>
+      //  sender ! Participants(participants.keySet.toList)
+    }
+
+    /** Send message to every client in chatroom
+      *
+      * @param msg message
+      */
+    def broadcast(msg: Any): Unit = participants foreach { _._2.actor ! msg }
+
+  }
+
+}
diff --git a/src/main/scala/org/scala_vienna/raffle/RaffleServlet.scala b/src/main/scala/org/scala_vienna/raffle/RaffleServlet.scala
new file mode 100644
index 0000000..f994203
--- /dev/null
+++ b/src/main/scala/org/scala_vienna/raffle/RaffleServlet.scala
@@ -0,0 +1,19 @@
+package org.scala_vienna.raffle
+
+import javax.servlet.annotation.WebServlet
+
+import org.vaadin.addons.vaactor.VaactorServlet
+import com.vaadin.annotations.VaadinServletConfiguration
+
+/** Define servlet, url pattern and ui-class to start
+  *
+  */
+@WebServlet(
+  urlPatterns = Array("/*"),
+  asyncSupported = true
+)
+@VaadinServletConfiguration(
+  productionMode = false,
+  ui = classOf[RaffleUI]
+)
+class RaffleServlet extends VaactorServlet
diff --git a/src/main/scala/org/scala_vienna/raffle/RaffleUI.scala b/src/main/scala/org/scala_vienna/raffle/RaffleUI.scala
new file mode 100644
index 0000000..37c378f
--- /dev/null
+++ b/src/main/scala/org/scala_vienna/raffle/RaffleUI.scala
@@ -0,0 +1,18 @@
+package org.scala_vienna.raffle
+
+import org.vaadin.addons.vaactor.VaactorUI
+import com.vaadin.annotations.Push
+import com.vaadin.server.VaadinRequest
+import com.vaadin.shared.communication.PushMode
+import com.vaadin.shared.ui.ui.Transport
+
+@Push(
+  value = PushMode.AUTOMATIC,
+  transport = Transport.WEBSOCKET
+)
+class RaffleUI extends VaactorUI {
+
+  override def init(request: VaadinRequest): Unit =
+    setContent(new RaffleComponent(this, "Vaactor raffle"))
+
+}