diff --git a/.gitignore b/.gitignore
index 5fe2dba6..dcf14bec 100644
--- a/.gitignore
+++ b/.gitignore
@@ -99,7 +99,7 @@ fabric.properties
### Intellij Patch ###
# Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721
-# *.iml
+*.iml
# modules.xml
# .idea/misc.xml
# *.ipr
diff --git a/.travis.yml b/.travis.yml
index 159bf074..299277c8 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -3,7 +3,7 @@ env:
global:
- secure: "dRKFshApyl4AUJFX35C3GfeYoV7jFjZirDj9YbNMSegvEMf6eIiapdBP/GfnCYKmOCO9cuZU3p2hUHGNNZYRdDcuP12SlrCVfxDnLkqPgwuZKGm0T0yAsACVJZRD/uNfy5HMiCPMDyT+h9pqMlBj+LsCov46AlaQdmwS9luIjPWNA+Ter17javVpLLzsRH+8Vsh1UtiEk4seIHkhOlxQLaAEvoTr2kRrI048C3aVYY8hDIMNrK/OPiVwF+83ZeXbZL0Q9E+QnmXimAB/lS7VleW4DD1g4JI9NL3HHAJbe3yDHdwRb/fYLX/ZpT0MA0qgYSlxwT2hrpFYQa/HgRUN2jREpSLWxnfKd1QQYXuxTGQaI9WUd+j+8ppvvJZHqdqc8QGMRHrodtdYcDJhYk5Z1nOlkVux+Axt/x4o63RJtZ7dX8JTsHyFW415FhnK56iGfRTLe7aipYuepZcxeydWrEis8+hy7eVuPwxH0qiCHugHRlzyTJZLR9z6peXg0IeDpCULm4Wy7LX/xHSagk4CFbqHghzdMtcaCSn9q7ooFv5RWqHYEq+szAAl/usYN5MuN2g1ZD7T4BooWJdUbntELiLh4yBpAkFaW0VTN1A5N+UK/QdCzFG0Sl6VXYGxU3/93fgbhf7Q1v+Q/H7wc5GrGQy+4R425WCYXilOksFJkWk="
script:
- - './gradlew'
+ - './gradlew clean build -x test javadoc scaladoc reportScoverage'
after_success:
- 'bash scripts/commit_and_upload.sh -r iuginP/pps-17-cw-mp -b master -s latest-docs/scala -d scala'
- 'bash scripts/commit_and_upload.sh -r iuginP/pps-17-cw-mp -b master -s latest-docs/java -d java'
diff --git a/build.gradle b/build.gradle
index af0c26f5..cf49c015 100644
--- a/build.gradle
+++ b/build.gradle
@@ -10,6 +10,7 @@ def scalaStyleOutputPath = "${buildDir.path}$scalaStyleOutput"
allprojects {
apply plugin: 'idea'
apply plugin: 'eclipse'
+ apply plugin: "com.dorongold.task-tree"
repositories {
// Repositories where to find libraries
@@ -112,6 +113,8 @@ buildscript {
}
}
dependencies {
+ classpath "gradle.plugin.com.dorongold.plugins:task-tree:1.3"
+
classpath "gradle.plugin.org.scoverage:gradle-scoverage:2.3.0"
classpath "gradle.plugin.com.github.maiflai:gradle-scalatest:0.22"
@@ -121,4 +124,4 @@ buildscript {
}
}
-defaultTasks 'clean', 'build', 'javadoc', 'scaladoc', 'reportScoverage', 'shadowJar'
+defaultTasks 'clean', 'build', 'javadoc', 'scaladoc', 'reportScoverage'
diff --git a/client/src/main/resources/layouts/roomManagerLayout.fxml b/client/src/main/resources/layouts/roomManagerLayout.fxml
index 57914ed1..6298adcd 100644
--- a/client/src/main/resources/layouts/roomManagerLayout.fxml
+++ b/client/src/main/resources/layouts/roomManagerLayout.fxml
@@ -21,26 +21,26 @@
+ fx:id="tfPrivateCreateRoomName" layoutX="115.0" layoutY="28.0"/>
-
-
-
@@ -55,10 +55,10 @@
-
+
@@ -69,15 +69,15 @@
-
-
diff --git a/client/src/main/scala/it/cwmp/client/ClientMain.scala b/client/src/main/scala/it/cwmp/client/ClientMain.scala
index 606f15bf..f7cd725e 100644
--- a/client/src/main/scala/it/cwmp/client/ClientMain.scala
+++ b/client/src/main/scala/it/cwmp/client/ClientMain.scala
@@ -4,12 +4,13 @@ import akka.actor.{ActorSystem, Props}
import com.typesafe.config.ConfigFactory
import it.cwmp.client.controller.ClientControllerActor
+/**
+ * The client entry point
+ */
object ClientMain extends App {
- val APP_NAME = "ClientApp"
+ val APP_NAME = "CellWarsClient"
private val config = ConfigFactory.parseString("akka.remote.netty.tcp.port=0").withFallback(ConfigFactory.load())
-
val system = ActorSystem(APP_NAME, config)
-
val clientControllerActor = system.actorOf(Props(classOf[ClientControllerActor], system), ClientControllerActor.getClass.getName)
}
diff --git a/client/src/main/scala/it/cwmp/client/GameMain.scala b/client/src/main/scala/it/cwmp/client/GameMain.scala
index 8fc5e82d..0a14ff43 100644
--- a/client/src/main/scala/it/cwmp/client/GameMain.scala
+++ b/client/src/main/scala/it/cwmp/client/GameMain.scala
@@ -15,7 +15,7 @@ object GameMain extends App {
val system = ActorSystem(APP_NAME, config)
- val gameActor = system.actorOf(Props(classOf[GameViewActor]), GameViewActor.getClass.getName)
+ val gameActor = system.actorOf(Props(classOf[GameViewActor], null), GameViewActor.getClass.getName)
import GameViewActor._
diff --git a/client/src/main/scala/it/cwmp/client/controller/ActorAlertManagement.scala b/client/src/main/scala/it/cwmp/client/controller/ActorAlertManagement.scala
new file mode 100644
index 00000000..cdb63afa
--- /dev/null
+++ b/client/src/main/scala/it/cwmp/client/controller/ActorAlertManagement.scala
@@ -0,0 +1,70 @@
+package it.cwmp.client.controller
+
+import akka.actor.Actor.Receive
+import it.cwmp.client.controller.AlertMessages._
+import it.cwmp.client.view.FXAlertsController
+
+/**
+ * A trait that gives autonomous management of alert messages;
+ *
+ * To use it you need to add "alertBehaviour" to your Actor receive
+ *
+ * @author Eugenio Pierfederici
+ * @author contributor Enrico Siboni
+ */
+trait ActorAlertManagement {
+
+ /**
+ * @return the alerts controller
+ */
+ protected def fxController: FXAlertsController
+
+ /**
+ * @return the behaviour that manages alert messages
+ */
+ protected def alertBehaviour: Receive = {
+ case Info(title, message) => onInfoAlertReceived(title, message)
+ case Error(title, message) => onErrorAlertReceived(title, message)
+ }
+
+ /**
+ * Called when a info alert is received
+ *
+ * @param title the title of the info
+ * @param message the message of the info
+ */
+ protected def onInfoAlertReceived(title: String, message: String): Unit =
+ fxController showInfo(title, message)
+
+ /**
+ * Called when an error alert is received
+ *
+ * @param title the title of the error
+ * @param message the message of the error
+ */
+ protected def onErrorAlertReceived(title: String, message: String): Unit =
+ fxController showError(title, message)
+}
+
+/**
+ * A collection of AlertMessages
+ */
+object AlertMessages {
+
+ /**
+ * Tells to show info message
+ *
+ * @param title the title
+ * @param message the message
+ */
+ case class Info(title: String, message: String)
+
+ /**
+ * Tells to show an error message
+ *
+ * @param title the title
+ * @param message the message
+ */
+ case class Error(title: String, message: String)
+
+}
diff --git a/client/src/main/scala/it/cwmp/client/controller/ActorViewVisibilityManagement.scala b/client/src/main/scala/it/cwmp/client/controller/ActorViewVisibilityManagement.scala
new file mode 100644
index 00000000..95f79e69
--- /dev/null
+++ b/client/src/main/scala/it/cwmp/client/controller/ActorViewVisibilityManagement.scala
@@ -0,0 +1,56 @@
+package it.cwmp.client.controller
+
+import akka.actor.Actor.Receive
+import it.cwmp.client.controller.ViewVisibilityMessages.{Hide, Show}
+import it.cwmp.client.view.{FXRunOnUIThread, FXViewController}
+
+/**
+ * A trait that give autonomous management to view visibility
+ *
+ * To use it you need to add "visibilityBehaviour" to your Actor receive
+ *
+ * @author Enrico Siboni
+ */
+trait ActorViewVisibilityManagement extends FXRunOnUIThread {
+
+ /**
+ * @return the visibility controller
+ */
+ protected def fxController: FXViewController
+
+ /**
+ * @return the behaviour that manages alert messages
+ */
+ protected def visibilityBehaviour: Receive = {
+ case Show => onShowGUI()
+ case Hide => onHideGUI()
+ }
+
+ /**
+ * Called when GUI is shown
+ */
+ protected def onShowGUI(): Unit = runOnUIThread(() => fxController showGUI())
+
+ /**
+ * Called when GUI is hidden
+ */
+ protected def onHideGUI(): Unit = runOnUIThread(() => fxController hideGUI())
+}
+
+
+/**
+ * A collection of view visibility messages
+ */
+object ViewVisibilityMessages {
+
+ /**
+ * Shows the underlying graphical interface
+ */
+ case object Show
+
+ /**
+ * Hides the underlying graphical interface
+ */
+ case object Hide
+
+}
\ No newline at end of file
diff --git a/client/src/main/scala/it/cwmp/client/controller/ApiClientActor.scala b/client/src/main/scala/it/cwmp/client/controller/ApiClientActor.scala
new file mode 100644
index 00000000..1e85f17d
--- /dev/null
+++ b/client/src/main/scala/it/cwmp/client/controller/ApiClientActor.scala
@@ -0,0 +1,87 @@
+package it.cwmp.client.controller
+
+import akka.actor.Actor
+import it.cwmp.client.controller.messages.AuthenticationRequests.{LogIn, SignUp}
+import it.cwmp.client.controller.messages.AuthenticationResponses.{LogInFailure, LogInSuccess, SignUpFailure, SignUpSuccess}
+import it.cwmp.client.controller.messages.RoomsRequests.{ServiceCreate, ServiceEnterPrivate, ServiceEnterPublic}
+import it.cwmp.client.controller.messages.RoomsResponses._
+import it.cwmp.services.wrapper.{AuthenticationApiWrapper, RoomsApiWrapper}
+import it.cwmp.utils.Utils.stringToOption
+
+import scala.concurrent.ExecutionContext.Implicits.global
+import scala.language.implicitConversions
+import scala.util.{Failure, Success, Try}
+
+/**
+ * A class that implements the actor that will manage communications with services APIs
+ */
+case class ApiClientActor() extends Actor {
+
+ override def receive: Receive = authenticationBehaviour orElse roomsBehaviour
+
+ /**
+ * @return the behaviour of authenticating user online
+ */
+ private def authenticationBehaviour: Receive = {
+ val authenticationApiWrapper = AuthenticationApiWrapper()
+ //noinspection ScalaStyle
+ import authenticationApiWrapper._
+ {
+ case LogIn(username, password) =>
+ val senderTmp = sender
+ login(username, password).onComplete(replyWith(
+ token => senderTmp ! LogInSuccess(token),
+ exception => senderTmp ! LogInFailure(exception.getMessage)
+ ))
+ case SignUp(username, password) =>
+ val senderTmp = sender
+ signUp(username, password).onComplete(replyWith(
+ token => senderTmp ! SignUpSuccess(token),
+ exception => senderTmp ! SignUpFailure(exception.getMessage)
+ ))
+ }
+ }
+
+ /**
+ * @return the behaviour of managing the rooms online
+ */
+ private def roomsBehaviour: Receive = {
+ val roomApiWrapper = RoomsApiWrapper()
+ //noinspection ScalaStyle
+ import roomApiWrapper._
+ {
+ case ServiceCreate(roomName, playersNumber, token) =>
+ val senderTmp = sender
+ createRoom(roomName, playersNumber)(token).onComplete(replyWith(
+ token => senderTmp ! CreateSuccess(token),
+ exception => senderTmp ! CreateFailure(exception.getMessage)
+ ))
+ case ServiceEnterPrivate(idRoom, address, webAddress, token) =>
+ val senderTmp = sender
+ enterRoom(idRoom, address, webAddress)(token).onComplete(replyWith(
+ _ => senderTmp ! EnterPrivateSuccess,
+ exception => senderTmp ! EnterPrivateFailure(exception.getMessage)
+ ))
+ case ServiceEnterPublic(nPlayer, address, webAddress, token) =>
+ val senderTmp = sender
+ enterPublicRoom(nPlayer, address, webAddress)(token).onComplete(replyWith(
+ _ => senderTmp ! EnterPublicSuccess,
+ exception => senderTmp ! EnterPublicFailure(exception.getMessage)
+ ))
+ }
+ }
+
+ /**
+ * A utility method to match Success or failure of a try and do something with results
+ *
+ * @param onSuccess the action to do on success
+ * @param onFailure the action to do on failure
+ * @param toCheck the try to check
+ * @tparam T the type of the result if present
+ */
+ private def replyWith[T](onSuccess: => T => Unit, onFailure: => Throwable => Unit)
+ (toCheck: Try[T]): Unit = toCheck match {
+ case Success(value) => onSuccess(value)
+ case Failure(ex) => onFailure(ex)
+ }
+}
diff --git a/client/src/main/scala/it/cwmp/client/controller/ClientControllerActor.scala b/client/src/main/scala/it/cwmp/client/controller/ClientControllerActor.scala
index b0088ee1..8fa925cd 100644
--- a/client/src/main/scala/it/cwmp/client/controller/ClientControllerActor.scala
+++ b/client/src/main/scala/it/cwmp/client/controller/ClientControllerActor.scala
@@ -1,270 +1,261 @@
package it.cwmp.client.controller
import akka.actor.{Actor, ActorRef, ActorSystem, Props}
-import com.typesafe.scalalogging.Logger
-import it.cwmp.client.model.PlayerActor._
-import it.cwmp.client.model._
-import it.cwmp.client.view.AlertMessages
-import it.cwmp.client.view.authentication.{AuthenticationViewActor, AuthenticationViewMessages}
-import it.cwmp.client.view.room.{RoomViewActor, RoomViewMessages}
+import it.cwmp.client.controller.AlertMessages.{Error, Info}
+import it.cwmp.client.controller.ClientControllerActor._
+import it.cwmp.client.controller.PlayerActor.{RetrieveAddress, RetrieveAddressResponse, StartGame}
+import it.cwmp.client.controller.ViewVisibilityMessages.{Hide, Show}
+import it.cwmp.client.controller.messages.AuthenticationRequests.{LogIn, SignUp}
+import it.cwmp.client.controller.messages.AuthenticationResponses.{LogInFailure, LogInSuccess, SignUpFailure, SignUpSuccess}
+import it.cwmp.client.controller.messages.Initialize
+import it.cwmp.client.controller.messages.RoomsRequests._
+import it.cwmp.client.controller.messages.RoomsResponses._
+import it.cwmp.client.view.authentication.AuthenticationViewActor
+import it.cwmp.client.view.room.RoomViewActor
+import it.cwmp.client.view.room.RoomViewActor.{ShowToken, WaitingForOthers}
import it.cwmp.model.{Address, Participant}
+import it.cwmp.utils.Logging
import scala.concurrent.Future
import scala.util.{Failure, Success}
/**
- * Questo oggetto contiene tutti i messaggi che questo attore può ricevere.
+ * This class is client controller actor, that will manage interactions between client parts
+ *
+ * @param system the system of this actors
+ * @author Davide Borficchia
+ * @author Eugenio Pierfederici
+ * @author contributor Enrico Siboni
*/
-object ClientControllerMessages {
+case class ClientControllerActor(system: ActorSystem) extends Actor with ParticipantListReceiver with Logging {
- /**
- * Message indicating the need to log into the system.
- * When the system receives it, it sends the request to the authentication online service.
- *
- * @param username identification chosen by the player to access the system
- * @param password password chosen during sign up
- */
- case class AuthenticationPerformSignIn(username: String, password: String)
+ private val UNKNOWN_ERROR = "Unknown Error"
/**
- * Message indicating the need to create a new account.
- * When the system receives it, it sends the request to the authentication online service.
- *
- * @param username identification chosen by the player to register in the system
- * @param password password chosen to authenticate in the system
+ * This actor manages the game.
+ * Those actors for each client connect together in a cluster and manage game developing
*/
- case class AuthenticationPerformSignUp(username: String, password: String)
-
+ private var playerActor: ActorRef = _
+ private var playerAddress: String = _
/**
- * Questo messaggio gestisce la volontà di creare una nuova stanza privata.
- * Quando lo ricevo, invio la richiesta all'attore che gestisce i servizi online delle stanze.
- *
- * @param name è il nome della stanza da creare
- * @param nPlayer è il numero dei giocatori che potranno entrare nella stanza
+ * This actor manages all requests to web services
*/
- case class RoomCreatePrivate(name: String, nPlayer: Int)
+ private var apiClientActor: ActorRef = _
/**
- * Questo messaggio gestisce la volontà di entrare in una stanza privata.
- * Quando lo ricevo, invio la richiesta all'attore che gestisce i servizi online delle stanze.
- *
- * @param idRoom è l'id che identifica la stanza privata
+ * Actor for the management of authentication processes to which the relative messages will be sent.
*/
- case class RoomEnterPrivate(idRoom: String)
+ private var authenticationViewActor: ActorRef = _
/**
- * Questo messaggio gestisce la volontà di entrare in una stanza pubblica.
- * Quando lo ricevo, invio la richiesta all'attore che gestisce i servizi online delle stanze.
- *
- * @param nPlayer è il numero dei partecipanti con i quali si vuole giocare
+ * This actor manages the view of rooms
*/
- case class RoomEnterPublic(nPlayer: Int)
+ private var roomViewActor: ActorRef = _
-}
+ private var jwtToken: String = _
-object ClientControllerActor {
- def apply(system: ActorSystem): ClientControllerActor = new ClientControllerActor(system)
-}
+ override def preStart(): Unit = {
+ super.preStart()
-/**
- * Questa classe rappresenta l'attore del controller del client che ha il compito
- * di fare da tramite tra le view e i model.
- *
- * @param system è l'[[ActorSystem]] che ospita gli attori che dovranno comunicare tra di loro
- * @author Davide Borficchia
- * @author Eugenio Pierfederici
- */
-class ClientControllerActor(system: ActorSystem) extends Actor with ParticipantListReceiver {
+ log.info(s"Initializing the player actor...")
+ playerActor = system.actorOf(Props(classOf[PlayerActor], system), PlayerActor.getClass.getName)
+ playerActor ! RetrieveAddress
- private val UNKNOWN_ERROR = "Unknown Error"
- private val logger: Logger = Logger[ClientControllerActor] // TODO: replace with our logging
+ log.info(s"Initializing the API client actor...")
+ apiClientActor = system.actorOf(Props[ApiClientActor], ApiClientActor.getClass.getName)
- var jwtToken: String = _
+ log.info(s"Initializing the authentication view actor...")
+ authenticationViewActor = system.actorOf(Props[AuthenticationViewActor], AuthenticationViewActor.getClass.getName)
+ authenticationViewActor ! Initialize
- /**
- * Questo attore è quello che si occupa di gestire la partita di gioco.
- * Sono questi attori, per ciascun client, a connettersi nel cluster e gestire lo svolgimento del gioco.
- */
- var playerActor: ActorRef = _
- var playerAddress: String = _
+ log.info(s"Displaying the view...")
+ authenticationViewActor ! Show
+
+ log.info(s"Initializing the room view actor...")
+ roomViewActor = system.actorOf(Props[RoomViewActor], RoomViewActor.getClass.getName)
+ roomViewActor ! Initialize
+ }
+
+ override def receive: Receive = playerAddressRetrievalBehaviour
/**
- * Questo attore si occupa di effettuare tutte le richieste ai servizi web e attendere le risposte.
+ * @return the behaviour of retrieving address of player actor
*/
- var apiClientActor: ActorRef = _
+ private def playerAddressRetrievalBehaviour: Receive = {
+ case RetrieveAddressResponse(address) =>
+ playerAddress = address
+ context.become(authenticationGUIBehaviour orElse authenticationApiReceiverBehaviour)
+ }
/**
- * Questo è l'attore che gestisce la view della lebboy delle stanze al quale invieremo i messaggi
+ * @return the behaviour that manages authentication commands from GUI
*/
- var roomViewActor: ActorRef = _
+ private def authenticationGUIBehaviour: Receive = {
+ case message@LogIn(username, _) =>
+ log.info(s"Signing in as $username")
+ apiClientActor ! message
+ case message@SignUp(username, _) =>
+ log.info(s"Signing up as $username")
+ apiClientActor ! message
+ }
/**
- * Actor for the management of authentication processes to which the relative messages will be sent.
+ * @return the behaviour that manages authentication API responses
*/
- var authenticationViewActor: ActorRef = _
+ private def authenticationApiReceiverBehaviour: Receive = {
+ case LogInSuccess(token) => onAuthenticationSuccess(token)
+ case SignUpSuccess(token) => onAuthenticationSuccess(token)
+ case LogInFailure(errorMessage) => onAuthenticationFailure(errorMessage)
+ case SignUpFailure(errorMessage) => onAuthenticationFailure(errorMessage)
+ }
/**
- * Questa metodo non va richiamato manualmente ma viene chiamato in automatico
- * quando viene creato l'attore [[ClientControllerActor]].
- * Il suo compito è quello di creare l'attore [[RoomViewActor]].
- * Una volta creato inizializza e mostra la GUI
+ * Action to do on successful authentication
*/
- override def preStart(): Unit = {
- super.preStart()
- // Initialize all actors
- logger.info(s"Initializing the player actor...")
- playerActor = system.actorOf(Props(classOf[PlayerActor], system), "player")
- playerActor ! RetrieveAddress
- logger.info(s"Initializing the API client actor...")
- apiClientActor = system.actorOf(Props[ApiClientActor], "roomAPIClient") //todo parametrizzare le stringhe
- logger.info(s"Initializing the authentication view actor...")
- authenticationViewActor = system.actorOf(Props[AuthenticationViewActor], "authenticationView")
- authenticationViewActor ! AuthenticationViewMessages.InitController
- logger.info(s"Initializing the room view actor...")
- roomViewActor = system.actorOf(Props[RoomViewActor], "roomView")
- roomViewActor ! RoomViewMessages.InitController
-
- logger.info(s"Displaying the view...")
- authenticationViewActor ! AuthenticationViewMessages.ShowGUI
+ private def onAuthenticationSuccess(token: String): Unit = {
+ authenticationViewActor ! Info(AUTHENTICATION_SUCCEEDED_TITLE, AUTHENTICATION_SUCCEEDED_MESSAGE)
+ jwtToken = token
+ log.info(s"Setting the behaviour 'room-manager'")
+ context.become(roomsGUIBehaviour orElse roomsApiReceiverBehaviour)
+ roomViewActor ! Show
+ authenticationViewActor ! Hide
}
/**
- * Questa metodo gestisce tutti i possibili behavior che può assumero l'attore [[ClientControllerActor]].
- * Un behavior è un subset di azioni che il controller può eseguire in un determianto momento .
+ * Action to do on failed authentication
+ *
+ * @param errorMessage optionally the error message
*/
- override def receive: Receive = apiClientReceiverBehaviour orElse authenticationManagerBehaviour orElse {
- case RetrieveAddressResponse(address) =>
- playerAddress = address
- }
+ private def onAuthenticationFailure(errorMessage: Option[String]): Unit =
+ authenticationViewActor ! Error(AUTHENTICATION_ERROR_TITLE, errorMessage.getOrElse(UNKNOWN_ERROR))
/**
- * Set the behavior of the [[ClientControllerActor]] in order to handle authentication processes
+ * @return the behaviour that manages rooms commands from GUI
*/
- def becomeAuthenticationManager(): Unit = {
- logger.info(s"Setting the behaviour 'authentication-manager'")
- context.become(apiClientReceiverBehaviour orElse authenticationManagerBehaviour)
+ private def roomsGUIBehaviour: Receive = {
+ case GUICreate(roomName, playersNumber) =>
+ log.info(s"Creating the room $roomName")
+ apiClientActor ! ServiceCreate(roomName, playersNumber, jwtToken)
+ case GUIEnterPrivate(roomID) =>
+ log.info(s"Entering the private room $roomID")
+ openOneTimeServerAndGetAddress()
+ .map(url => apiClientActor ! ServiceEnterPrivate(roomID, Address(playerAddress), url, jwtToken))
+ case GUIEnterPublic(playersNumber) =>
+ log.info(s"Entering the public room with $playersNumber players")
+ openOneTimeServerAndGetAddress()
+ .map(url => apiClientActor ! ServiceEnterPublic(playersNumber, Address(playerAddress), url, jwtToken))
+ case GUIExitPrivate(roomID) => // TODO: exiting behaviour
+ log.info(s"Exiting room $roomID")
+ case GUIExitPublic(playersNumber) => // TODO: exiting behaviour (close one-time server)
+ log.info(s"Exiting public room with $playersNumber")
}
/**
- * Imposta il behavior del [[ClientControllerActor]] in modo da gestire solo la lobby delle stanze
+ * @return the behaviour that manages room API responses
*/
- private def becomeRoomsManager(): Unit = {
- logger.info(s"Setting the behaviour 'room-manager'")
- context.become(apiClientReceiverBehaviour orElse roomManagerBehaviour)
- roomViewActor ! RoomViewMessages.ShowGUI
+ //noinspection ScalaStyle
+ private def roomsApiReceiverBehaviour: Receive = {
+ case CreateSuccess(token) => roomViewActor ! ShowToken(token)
+ case CreateFailure(errorMessage) => roomViewActor ! Error(CREATE_ERROR_TITLE, errorMessage.getOrElse(UNKNOWN_ERROR))
+ case EnterPrivateSuccess => onRoomEnteringSuccess()
+ case EnterPublicSuccess => onRoomEnteringSuccess()
+ case EnterPrivateFailure(errorMessage) => onRoomEnteringFailure(errorMessage)
+ case EnterPublicFailure(errorMessage) => onRoomEnteringFailure(errorMessage)
+ case ExitPrivateSuccess => onRoomExitingSuccess()
+ case ExitPublicSuccess => onRoomExitingSuccess()
+ case ExitPrivateFailure(errorMessage) => onRoomExitingFailure(errorMessage)
+ case ExitPublicFailure(errorMessage) => onRoomExitingFailure(errorMessage)
}
- private def becomeInGame(participants: List[Participant]): Unit = {
- logger.info(s"Setting the behaviour 'in-game'")
- context.become(inGameBehaviour)
- roomViewActor ! RoomViewMessages.HideGUI
- playerActor ! StartGame(participants)
+ /**
+ * Action to do on successful room entering
+ */
+ private def onRoomEnteringSuccess(): Unit = {
+ roomViewActor ! Info(ROOM_ENTERING_SUCCEEDED_TITLE, ROOM_ENTERING_SUCCEEDED_MESSAGE)
+ roomViewActor ! WaitingForOthers
}
-
- import it.cwmp.client.controller.ClientControllerMessages._
-
/**
- * Behavior to be applied to manage authentication processes.
- * Messages that can be processed in this behavior are shown in [[ClientControllerMessages]]
+ * Action to do on room entering failure
*
+ * @param errorMessage optionally the error message
*/
- def authenticationManagerBehaviour: Receive = {
- case AuthenticationPerformSignIn(username, password) =>
- logger.info(s"Signing in as $username")
- apiClientActor ! ApiClientIncomingMessages.AuthenticationPerformSignIn(username, password)
- case AuthenticationPerformSignUp(username, password) =>
- logger.info(s"Signing up as $username")
- apiClientActor ! ApiClientIncomingMessages.AuthenticationPerformSignUp(username, password)
+ private def onRoomEnteringFailure(errorMessage: Option[String]): Unit = {
+ stopListeningForParticipants()
+ roomViewActor ! Error(ENTERING_ERROR_TITLE, errorMessage.getOrElse(UNKNOWN_ERROR))
}
/**
- * Questo metodo rappresenta il behavior che si ha quando si sta gestendo la lobby delle stanze.
- * I messaggi che questo attore, in questo behavoir, è ingrado di ricevere sono raggruppati in [[ClientControllerMessages]]
- *
+ * Action to do on room exiting success
*/
+ private def onRoomExitingSuccess(): Unit = ??? // TODO:
- import it.cwmp.client.controller.ClientControllerMessages._
-
- private def roomManagerBehaviour: Receive = {
- case RoomCreatePrivate(name, nPlayer) =>
- logger.info(s"Creating the room $name")
- apiClientActor ! ApiClientIncomingMessages.RoomCreatePrivate(name, nPlayer, jwtToken)
- case RoomEnterPrivate(idRoom) =>
- logger.info(s"Entering the private room $idRoom")
- enterRoom().map(url =>
- apiClientActor ! ApiClientIncomingMessages.RoomEnterPrivate(
- idRoom, Address(playerAddress), url, jwtToken)
- )
- case RoomEnterPublic(nPlayer) =>
- logger.info(s"Entering the public room with $nPlayer players")
- enterRoom().map(url =>
- apiClientActor ! ApiClientIncomingMessages.RoomEnterPublic(
- nPlayer, Address(playerAddress), url, jwtToken)
- )
- }
+ /**
+ * Action to do on room exiting failure
+ *
+ * @param errorMessage optionally an error message
+ */
+ private def onRoomExitingFailure(errorMessage: Option[String]): Unit = ??? // TODO:
private def inGameBehaviour: Receive = {
case _ => // TODO
}
- private def enterRoom(): Future[Address] = {
- logger.debug(s"Starting the local one-time reception server...")
- // Apre il server in ricezione per la lista dei partecipanti
- listenForParticipantListFuture(
- // Quando ha ricevuto la lista dei partecipanti dal server
- participants => {
- logger.info(s"Participants list received!")
- becomeInGame(participants)
- }
- ).andThen({ // Una volta creato
- case Success(address) =>
- logger.debug(s"Server completely started listening at the address: $address")
- case Failure(error) => // Invia un messaggio di errore alla GUI
- logger.error(s"Problem starting the server: ${error.getMessage}")
- roomViewActor ! AlertMessages.Error("Error", error.getMessage)
- })
+ /**
+ * @return the Future containing the address of one-time server that will receive the participants
+ */
+ private def openOneTimeServerAndGetAddress(): Future[Address] = {
+ val onListReceived: List[Participant] => Unit = participants => {
+ log.info(s"Participants list received!")
+ onSuccessFindingOpponents(participants)
+ }
+
+ log.debug(s"Starting the local one-time receiver server...")
+ listenForParticipantListFuture(onListReceived)
+ .andThen({
+ case Success(address) =>
+ log.debug(s"Server started and listening at the address: $address")
+ case Failure(error) =>
+ log.error(s"Problem starting the server: ${error.getMessage}")
+ roomViewActor ! Error(RECEIVING_PARTICIPANT_LIST_ERROR_TITLE, error.getMessage)
+ })
}
/**
- * Questo è il behavior che sta in ascolto del successo o meno di una chiamata fatta ad un servizio online tramite l'ApiClientActor.
- * I messaggi che questo attore, in questo behavoir, è in grado di ricevere sono raggruppati in [[ApiClientOutgoingMessages]]
+ * Action to execute when found opponents
*
+ * @param participants the participants to game
*/
+ private def onSuccessFindingOpponents(participants: List[Participant]): Unit = {
+ log.info(s"Setting the behaviour 'in-game'")
+ context.become(inGameBehaviour)
+ roomViewActor ! Hide
+ playerActor ! StartGame(participants)
+ }
- import ApiClientOutgoingMessages._
-
- // TODO: qui lasciamo il behaviuor misto?
- private def apiClientReceiverBehaviour: Receive = {
- case AuthenticationSignInSuccessful(token) =>
- authenticationViewActor ! AlertMessages.Info(s"Result", "Sign in successfully completed!", Some(() => {
- this.jwtToken = token
- becomeRoomsManager()
- authenticationViewActor ! AuthenticationViewMessages.HideGUI
- }))
- case AuthenticationSignInFailure(reason) =>
- authenticationViewActor ! AlertMessages.Error("Warning", reason.getOrElse(UNKNOWN_ERROR))
- case AuthenticationSignUpSuccessful(token) =>
- authenticationViewActor ! AlertMessages.Info(s"Result", "Sign up successfully completed!", Some(() => {
- this.jwtToken = token
- becomeRoomsManager()
- authenticationViewActor ! AuthenticationViewMessages.HideGUI
- }))
- case AuthenticationSignUpFailure(reason) =>
- authenticationViewActor ! AlertMessages.Error("Warning", reason.getOrElse(UNKNOWN_ERROR))
-
- case RoomCreatePrivateSuccessful(token) =>
- roomViewActor ! AlertMessages.Info("Token", token)
- case RoomCreatePrivateFailure(reason) =>
- roomViewActor ! AlertMessages.Error("Problem", reason.getOrElse(UNKNOWN_ERROR)) // TODO parametrizzazione stringhe
- case RoomEnterPrivateSuccessful =>
- roomViewActor ! AlertMessages.Info("Stanza privata", "Sei entrato") // TODO parametrizzazione stringhe
- case RoomEnterPrivateFailure(reason) =>
- roomViewActor ! AlertMessages.Error("Problem", reason.getOrElse(UNKNOWN_ERROR)) // TODO parametrizzazione stringhe
- case RoomEnterPublicSuccessful =>
- roomViewActor ! AlertMessages.Info("Stanza pubblica", "Sei entrato") // TODO parametrizzazione stringhe
- case RoomEnterPublicFailure(reason) =>
- roomViewActor ! AlertMessages.Error("Problem", reason.getOrElse(UNKNOWN_ERROR)) // TODO parametrizzazione stringhe
+ /**
+ * Action to execute when logout occurs
+ */
+ private def onLogOut(): Unit = {
+ log.info(s"Setting the behaviour 'authentication-manager'")
+ context.become(authenticationGUIBehaviour orElse authenticationApiReceiverBehaviour)
+ authenticationViewActor ! Show
}
+}
+
+/**
+ * Companion object
+ */
+object ClientControllerActor {
+ private val CREATE_ERROR_TITLE = "Errore nella creazione della stanza"
+ private val ENTERING_ERROR_TITLE = "Errore nell'entrata nella stanza"
+ private val RECEIVING_PARTICIPANT_LIST_ERROR_TITLE = "Errore durante la ricezione degli altri partecipanti"
+ private val AUTHENTICATION_ERROR_TITLE = "Errore durante l'autenticazione"
+
+ private val AUTHENTICATION_SUCCEEDED_TITLE = "Autenticazione riuscita!"
+ private val AUTHENTICATION_SUCCEEDED_MESSAGE = "Ti sei autenticato correttamente :)"
+
+ private val ROOM_ENTERING_SUCCEEDED_TITLE = "Entrata nella stanza riuscita!"
+ private val ROOM_ENTERING_SUCCEEDED_MESSAGE = "Sei entrato nella stanza, ora attendiamo altri partecipanti!"
}
\ No newline at end of file
diff --git a/client/src/main/scala/it/cwmp/client/controller/ParticipantListReceiver.scala b/client/src/main/scala/it/cwmp/client/controller/ParticipantListReceiver.scala
new file mode 100644
index 00000000..6f98221c
--- /dev/null
+++ b/client/src/main/scala/it/cwmp/client/controller/ParticipantListReceiver.scala
@@ -0,0 +1,51 @@
+package it.cwmp.client.controller
+
+import java.net.InetAddress
+
+import it.cwmp.client.controller.ParticipantListReceiver.ADDRESS_TOKEN_LENGTH
+import it.cwmp.model.{Address, Participant}
+import it.cwmp.services.roomreceiver.RoomReceiverServiceVerticle
+import it.cwmp.services.roomreceiver.ServerParameters._
+import it.cwmp.utils.Utils.stringToOption
+import it.cwmp.utils.{Logging, Utils, VertxInstance}
+
+import scala.concurrent.Future
+import scala.util.Success
+
+/**
+ * A trait implementing a one time server to receive a participant list to the game room
+ */
+trait ParticipantListReceiver extends VertxInstance with Logging {
+
+ private var deploymentID: Option[String] = None
+
+ /**
+ * Listens for a list of participants
+ *
+ * @param onListReceived the action to execute on list received
+ * @return the Future containing the address on which to contact this listener
+ */
+ def listenForParticipantListFuture(onListReceived: List[Participant] => Unit): Future[Address] = {
+ val token = Utils.randomString(ADDRESS_TOKEN_LENGTH)
+ val verticle = RoomReceiverServiceVerticle(token, participants => onListReceived(participants))
+ vertx.deployVerticleFuture(verticle)
+ .andThen { case Success(id) => deploymentID = id }
+ .map(_ => Address(s"http://${InetAddress.getLocalHost.getHostAddress}:${verticle.port}"
+ + API_RECEIVE_PARTICIPANTS_URL(token)))
+ }
+
+ /**
+ * A method that stops the server that would have received participants to entered room
+ */
+ def stopListeningForParticipants(): Unit = {
+ log.info("Stopping one-time participants receiver server")
+ deploymentID.foreach(vertx.undeploy)
+ }
+}
+
+/**
+ * Companion object
+ */
+object ParticipantListReceiver {
+ private val ADDRESS_TOKEN_LENGTH = 20
+}
\ No newline at end of file
diff --git a/client/src/main/scala/it/cwmp/client/model/PlayerActor.scala b/client/src/main/scala/it/cwmp/client/controller/PlayerActor.scala
similarity index 57%
rename from client/src/main/scala/it/cwmp/client/model/PlayerActor.scala
rename to client/src/main/scala/it/cwmp/client/controller/PlayerActor.scala
index 3fb8b34d..612f7e9a 100644
--- a/client/src/main/scala/it/cwmp/client/model/PlayerActor.scala
+++ b/client/src/main/scala/it/cwmp/client/controller/PlayerActor.scala
@@ -1,32 +1,18 @@
-package it.cwmp.client.model
+package it.cwmp.client.controller
import akka.actor.{Actor, ActorRef, ActorSystem, AddressFromURIString, Props}
import akka.cluster.Cluster
import akka.cluster.ClusterEvent._
import akka.cluster.ddata.DistributedData
import it.cwmp.client.GameMain
-import it.cwmp.client.model.game.impl.CellWorld
+import it.cwmp.client.controller.PlayerActor.{EndGame, RetrieveAddress, RetrieveAddressResponse, StartGame}
+import it.cwmp.client.model.DistributedState
+import it.cwmp.client.model.game.impl.{CellWorld, CellWorldDistributedState}
import it.cwmp.client.view.game.GameViewActor
import it.cwmp.client.view.game.GameViewActor._
import it.cwmp.model.Address
import it.cwmp.utils.Logging
-object PlayerActor {
-
- def apply(system: ActorSystem): PlayerActor = new PlayerActor(system)
-
- // Incoming messages
- case object RetrieveAddress
-
- case class StartGame(participantList: List[Address])
-
- case object EndGame
-
- // Outgoing messages
- case class RetrieveAddressResponse(address: String)
-
-}
-
/**
* Questo attore è quello che si occupa di gestire l'esecuzione del gioco distribuito.
* Innanzi tutto all'avvio della partita crea un cluster con gli altri partecipanti; poi
@@ -34,73 +20,108 @@ object PlayerActor {
*
* @author Eugenio Pierfederici
*/
-
-import it.cwmp.client.model.PlayerActor._
-
-class PlayerActor(system: ActorSystem) extends Actor with Logging {
+class PlayerActor(system: ActorSystem) extends Actor with Logging { // TODO: review (and translate doc)
// View actor
- var gameViewActor: ActorRef = _
+ private var gameViewActor: ActorRef = _
// Cluster info
- var roomSize: Int = _
+ private var roomSize: Int = _
// distributed replica system
- val replicator: ActorRef = DistributedData(context.system).replicator
- implicit val cluster: Cluster = Cluster(context.system)
+ private val replicator: ActorRef = DistributedData(context.system).replicator
+ private val cluster: Cluster = Cluster(context.system)
// Distributed world
- val distributedState: DistributedState[CellWorld] = DistributedState(this, println)
+ private val distributedState: DistributedState[CellWorld] =
+ CellWorldDistributedState(onWorldUpdatedAction)(replicator, cluster)
override def preStart(): Unit = {
log.info(s"Initializing the game-view actor...")
- gameViewActor = system.actorOf(Props[GameViewActor], "game-view")
+ gameViewActor = system.actorOf(Props(classOf[GameViewActor], self), "game-view")
log.info(s"Subscribing to cluster changes...")
cluster.subscribe(self, initialStateMode = InitialStateAsEvents,
classOf[MemberEvent], classOf[UnreachableMember])
+ distributedState.subscribe(self)
}
- override def postStop(): Unit = cluster.unsubscribe(self)
+ override def postStop(): Unit = {
+ cluster.unsubscribe(self)
+ distributedState.unsubscribe(self)
+ }
override def receive: Receive = clusterBehaviour orElse lobbyBehaviour
+ /**
+ * @return the behaviour of an actor in a cluster
+ */
private def clusterBehaviour: Receive = {
case MemberUp(member) =>
log.info("Member is Up: {}", member.address)
log.debug("Cluster size: " + cluster.state.members.size)
- if (cluster.state.members.size == roomSize) enterGame
+ if (cluster.state.members.size == roomSize) enterGameAction()
case UnreachableMember(member) =>
log.info("Member detected as unreachable: {}", member)
case MemberRemoved(member, previousStatus) =>
- log.info(
- "Member is Removed: {} after {}",
- member.address, previousStatus)
+ log.info("Member is Removed: {} after {}", member.address, previousStatus)
case _: MemberEvent => // ignore
}
- private def backToLobby: Unit = context.become(clusterBehaviour orElse lobbyBehaviour)
-
+ // TODO: add doc
private def lobbyBehaviour: Receive = {
case RetrieveAddress =>
sender() ! RetrieveAddressResponse(getAddress)
case StartGame(participants) =>
- connectTo(participants)
+ join(participants)
roomSize = participants.size
}
- private def enterGame: Unit = {
+ /**
+ * @return the behaviour of the actor when it's in game
+ */
+ private def inGameBehaviour: Receive =
+ distributedState.distributedStateBehaviour orElse {
+ case EndGame => backToLobbyAction()
+ }
+
+ private def enterGameAction(): Unit = {
context.become(clusterBehaviour orElse inGameBehaviour)
gameViewActor ! ShowGUI
gameViewActor ! NewWorld(GameMain.debugWorld)
}
- private def inGameBehaviour: Receive = distributedState.stateBehaviour orElse {
- case EndGame => backToLobby
- }
+ private def backToLobbyAction(): Unit = context.become(clusterBehaviour orElse lobbyBehaviour)
+
+ /**
+ * Action that will be executed every time the world will be updated
+ *
+ * @param world the updated world
+ */
+ private def onWorldUpdatedAction(world: CellWorld): Unit = gameViewActor ! NewWorld(world)
+
- private def connectTo(participants: List[Address]): Unit = {
+ private def join(participants: List[Address]): Unit = {
cluster.join(AddressFromURIString(participants.head.address))
}
private def getAddress: String = cluster.selfAddress + self.path.toString.substring(self.path.address.toString.length)
}
+
+/**
+ * Companion object, containing actor messages
+ */
+object PlayerActor {
+
+ def apply(system: ActorSystem): PlayerActor = new PlayerActor(system)
+
+ // Incoming messages
+ case object RetrieveAddress
+
+ case class StartGame(participantList: List[Address])
+
+ case object EndGame
+
+ // Outgoing messages
+ case class RetrieveAddressResponse(address: String)
+
+}
diff --git a/client/src/main/scala/it/cwmp/client/controller/game/GameConstants.scala b/client/src/main/scala/it/cwmp/client/controller/game/GameConstants.scala
index 57f0a325..9dfebd7e 100644
--- a/client/src/main/scala/it/cwmp/client/controller/game/GameConstants.scala
+++ b/client/src/main/scala/it/cwmp/client/controller/game/GameConstants.scala
@@ -12,7 +12,7 @@ object GameConstants {
*
* The energy that a cell has when it's born
*/
- val cellWhenBornEnergy = 20d
+ val CELL_ENERGY_WHEN_BORN = 20d
/**
* ==Cell==
diff --git a/client/src/main/scala/it/cwmp/client/controller/game/GameEngine.scala b/client/src/main/scala/it/cwmp/client/controller/game/GameEngine.scala
index c7157517..5febda40 100644
--- a/client/src/main/scala/it/cwmp/client/controller/game/GameEngine.scala
+++ b/client/src/main/scala/it/cwmp/client/controller/game/GameEngine.scala
@@ -86,7 +86,7 @@ object GameEngine extends EvolutionStrategy[CellWorld, Duration] {
allTentacles: Seq[Tentacle],
maturedEnergy: Double): Cell = {
val firstAttackerOfCell = allTentacles.min(Tentacle.orderByLaunchInstant).from.owner
- Cell(firstAttackerOfCell, cell.position, GameConstants.cellWhenBornEnergy + maturedEnergy)
+ Cell(firstAttackerOfCell, cell.position, GameConstants.CELL_ENERGY_WHEN_BORN + maturedEnergy)
}
/**
diff --git a/client/src/main/scala/it/cwmp/client/controller/messages/AuthenticationCommon.scala b/client/src/main/scala/it/cwmp/client/controller/messages/AuthenticationCommon.scala
new file mode 100644
index 00000000..f15cb5a6
--- /dev/null
+++ b/client/src/main/scala/it/cwmp/client/controller/messages/AuthenticationCommon.scala
@@ -0,0 +1,74 @@
+package it.cwmp.client.controller.messages
+
+/**
+ * Collection of Authentication request messages
+ */
+object AuthenticationRequests {
+
+ sealed trait AuthenticationRequest
+
+ /**
+ * Request to log-in
+ *
+ * @param username the player username
+ * @param password the player password
+ */
+ sealed case class LogIn(username: String, password: String) extends AuthenticationRequest
+
+ /**
+ * Request to sign-up
+ *
+ * @param username the player username
+ * @param password the player password
+ */
+ sealed case class SignUp(username: String, password: String) extends AuthenticationRequest
+
+ /**
+ * Request to log-out
+ *
+ * @param username the player username that's signing-out
+ */
+ sealed case class LogOut(username: String) extends AuthenticationRequest
+
+}
+
+/**
+ * Collection of Authentication response messages
+ */
+object AuthenticationResponses {
+
+ sealed trait AuthenticationResponse
+
+ sealed trait LogInResponse extends AuthenticationResponse
+
+ sealed trait SignUpResponse extends AuthenticationResponse
+
+ /**
+ * Log-in succeeded
+ *
+ * @param token the user identifying token
+ */
+ sealed case class LogInSuccess(token: String) extends LogInResponse
+
+ /**
+ * Log-in failure
+ *
+ * @param errorMessage optionally an error message
+ */
+ sealed case class LogInFailure(errorMessage: Option[String]) extends LogInResponse
+
+ /**
+ * Sign-up succeeded
+ *
+ * @param token the user identifying token
+ */
+ sealed case class SignUpSuccess(token: String) extends SignUpResponse
+
+ /**
+ * Sign-up failure
+ *
+ * @param errorMessage optionally an error message
+ */
+ sealed case class SignUpFailure(errorMessage: Option[String]) extends SignUpResponse
+
+}
\ No newline at end of file
diff --git a/client/src/main/scala/it/cwmp/client/controller/messages/Initialize.scala b/client/src/main/scala/it/cwmp/client/controller/messages/Initialize.scala
new file mode 100644
index 00000000..b4688ed7
--- /dev/null
+++ b/client/src/main/scala/it/cwmp/client/controller/messages/Initialize.scala
@@ -0,0 +1,6 @@
+package it.cwmp.client.controller.messages
+
+/**
+ * Tells the receiver who is the sender, so replies will be directed to it
+ */
+case object Initialize
\ No newline at end of file
diff --git a/client/src/main/scala/it/cwmp/client/controller/messages/RoomsCommon.scala b/client/src/main/scala/it/cwmp/client/controller/messages/RoomsCommon.scala
new file mode 100644
index 00000000..c660749d
--- /dev/null
+++ b/client/src/main/scala/it/cwmp/client/controller/messages/RoomsCommon.scala
@@ -0,0 +1,192 @@
+package it.cwmp.client.controller.messages
+
+import it.cwmp.model.Address
+
+/**
+ * The collection of Rooms request messages
+ */
+object RoomsRequests {
+
+ sealed trait RoomRequest
+
+
+ sealed trait GUIRequest
+
+ sealed trait ServiceRequest
+
+
+ sealed trait RoomPrivateRequest
+
+ sealed trait RoomPublicRequest
+
+
+ sealed trait RoomCreationRequest extends RoomRequest
+
+ sealed trait RoomEnteringRequest extends RoomRequest
+
+ sealed trait RoomExitingRequest extends RoomRequest
+
+ /**
+ * Create a new private room; request from GUI
+ *
+ * @param name the room name
+ * @param playersNumber the players number
+ */
+ sealed case class GUICreate(name: String, playersNumber: Int) extends RoomCreationRequest with GUIRequest
+
+ /**
+ * Enter a private room; request from GUI
+ *
+ * @param roomID the roomID to enter
+ */
+ sealed case class GUIEnterPrivate(roomID: String) extends RoomEnteringRequest with RoomPrivateRequest with GUIRequest
+
+ /**
+ * Enter a public room; request from GUI
+ *
+ * @param playersNumber the players number of the public room to enter
+ */
+ sealed case class GUIEnterPublic(playersNumber: Int) extends RoomEnteringRequest with RoomPublicRequest with GUIRequest
+
+ /**
+ * Exits a private room; request from GUI
+ *
+ * @param roomID the room id of room to exit
+ */
+ sealed case class GUIExitPrivate(roomID: String) extends RoomExitingRequest with RoomPrivateRequest with GUIRequest
+
+ /**
+ * Exits a public room; request from GUI
+ *
+ * @param playersNumber the players number of public room to exit
+ */
+ sealed case class GUIExitPublic(playersNumber: Int) extends RoomExitingRequest with RoomPublicRequest with GUIRequest
+
+ /**
+ * Create a new private room; request for online service
+ *
+ * @param name the name of the room
+ * @param playersNumber the players number
+ * @param token the user token
+ */
+ sealed case class ServiceCreate(name: String, playersNumber: Int, token: String) extends RoomCreationRequest with ServiceRequest
+
+ /**
+ * Enter a private room; request for online service
+ *
+ * @param roomID the room id
+ * @param playerAddress the player address that wants to join the room
+ * @param webAddress the address where tha player wants to receive other participants addresses
+ * @param token the user token
+ */
+ sealed case class ServiceEnterPrivate(roomID: String, playerAddress: Address, webAddress: Address, token: String)
+ extends RoomEnteringRequest with RoomPrivateRequest with ServiceRequest
+
+ /**
+ * Enter a public room; request for online service
+ *
+ * @param playersNumber the number of players the public room should have
+ * @param playerAddress the player address that wants to join the room
+ * @param webAddress the address where the player wants to receive tha other participants addresses
+ * @param token the user token
+ */
+ sealed case class ServiceEnterPublic(playersNumber: Int, playerAddress: Address, webAddress: Address, token: String)
+ extends RoomEnteringRequest with RoomPublicRequest with ServiceRequest
+
+ /**
+ * Exits a private room; request from GUI
+ *
+ * @param roomID the room id of room to exit
+ * @param token the user token
+ */
+ sealed case class ServiceExitPrivate(roomID: String, token: String) extends RoomExitingRequest with RoomPrivateRequest with ServiceRequest
+
+ /**
+ * Exits a public room; request from GUI
+ *
+ * @param playersNumber the players number of public room to exit
+ * @param token the user token
+ */
+ sealed case class ServiceExitPublic(playersNumber: Int, token: String) extends RoomExitingRequest with RoomPublicRequest with ServiceRequest
+
+}
+
+/**
+ * The collection of Rooms response messages
+ */
+object RoomsResponses {
+
+ sealed trait RoomPrivateResponse
+
+ sealed trait RoomPublicResponse
+
+
+ sealed trait RoomCreationResponse
+
+ sealed trait RoomEnteringResponse
+
+ sealed trait RoomExitingResponse
+
+ /**
+ * Creation successful
+ *
+ * @param roomID the room identifier to use entering the room
+ */
+ sealed case class CreateSuccess(roomID: String) extends RoomCreationResponse
+
+ /**
+ * Creation failed
+ *
+ * @param errorMessage optionally an error message
+ */
+ sealed case class CreateFailure(errorMessage: Option[String]) extends RoomCreationResponse
+
+ /**
+ * Enter private room succeeded
+ */
+ case object EnterPrivateSuccess extends RoomEnteringResponse with RoomPrivateResponse
+
+ /**
+ * Enter private room failed
+ *
+ * @param errorMessage optionally an error message
+ */
+ sealed case class EnterPrivateFailure(errorMessage: Option[String]) extends RoomEnteringResponse with RoomPrivateResponse
+
+ /**
+ * Enter public room succeeded
+ */
+ case object EnterPublicSuccess extends RoomEnteringResponse with RoomPublicResponse
+
+ /**
+ * Enter public room failed
+ *
+ * @param errorMessage optionally an error message
+ */
+ sealed case class EnterPublicFailure(errorMessage: Option[String]) extends RoomEnteringResponse with RoomPublicResponse
+
+ /**
+ * Exit private room succeeded
+ */
+ case object ExitPrivateSuccess extends RoomExitingResponse with RoomPrivateResponse
+
+ /**
+ * Exit private room failed
+ *
+ * @param errorMessage optionally an error message
+ */
+ sealed case class ExitPrivateFailure(errorMessage: Option[String]) extends RoomExitingResponse with RoomPrivateResponse
+
+ /**
+ * Exit public room succeeded
+ */
+ case object ExitPublicSuccess extends RoomExitingResponse with RoomPublicResponse
+
+ /**
+ * Exit public room failed
+ *
+ * @param errorMessage optionally an error message
+ */
+ sealed case class ExitPublicFailure(errorMessage: Option[String]) extends RoomExitingResponse with RoomPublicResponse
+
+}
\ No newline at end of file
diff --git a/client/src/main/scala/it/cwmp/client/model/ApiClientActor.scala b/client/src/main/scala/it/cwmp/client/model/ApiClientActor.scala
deleted file mode 100644
index f7b6ea54..00000000
--- a/client/src/main/scala/it/cwmp/client/model/ApiClientActor.scala
+++ /dev/null
@@ -1,207 +0,0 @@
-package it.cwmp.client.model
-
-import akka.actor.Actor
-import it.cwmp.model.Address
-import it.cwmp.services.wrapper.{AuthenticationApiWrapper, RoomsApiWrapper}
-
-import scala.concurrent.ExecutionContext.Implicits.global
-import scala.util.{Failure, Success}
-
-/**
- * Questo oggetto contiene tutti i messaggi che questo attore può ricevere.
- */
-object ApiClientIncomingMessages {
-
- /**
- * Message indicating the need to log into the system.
- * When the system receives it, it sends the request to the online service.
- *
- * @param username identification chosen by the player to access the system
- * @param password password chosen during sign up
- */
- case class AuthenticationPerformSignIn(username: String, password: String)
-
- /**
- * Message indicating the need to create a new account.
- * When the system receives it, it sends the request to the online service.
- *
- * @param username identification chosen by the player to register in the system
- * @param password password chosen to authenticate in the system
- */
- case class AuthenticationPerformSignUp(username: String, password: String)
-
- /**
- * Questo messaggio gestisce la volontà di creare una nuova stanza privata.
- * Quando lo ricevo, inoltro la richiesta al servizio online.
- *
- * @param name è il nome della stanza da creare
- * @param nPlayer è il numero dei giocatori che potranno entrare nella stanza
- * @param token è il token d'autenticazione per poter fare le richieste
- */
- case class RoomCreatePrivate(name: String, nPlayer: Int, token: String)
-
- /**
- * Questo messaggio gestisce la volontà di entrare in una stanza privata.
- * Quando lo ricevo, inoltro la richiesta al servizio online.
- *
- * @param idRoom è l'id della stanza nella quale voglio entrare
- * @param playerAddress è il partecipante che si vuole far entrare nella stanza
- * @param webAddress è l'indirizzo del web server in ascolto per la lista dei partecipanti
- * @param token è il token d'autenticazione per poter fare le richieste
- */
- case class RoomEnterPrivate(idRoom: String, playerAddress: Address, webAddress: Address, token: String)
-
- /**
- * Questo messaggio gestisce la volontà di entrare in una stanza pubblica
- * Quando lo ricevo, inoltro la richiesta al servizio online.
- *
- * @param nPlayer è il numero dei partecipanti a quella stanza
- */
- case class RoomEnterPublic(nPlayer: Int, playerAddress: Address, webAddress: Address, token: String)
-
-}
-
-/**
- * Questo oggetto contiene tutti i messaggi che questo attore può inviare.
- */
-object ApiClientOutgoingMessages {
-
- /**
- * Message that represents the successfully access to the system.
- *
- * @param token user identification token within the system
- */
- case class AuthenticationSignInSuccessful(token: String)
-
- /**
- * Message representing the failure of access to the system.
- *
- * @param reason reason that generated the failure
- */
- case class AuthenticationSignInFailure(reason: Option[String])
-
- /**
- * Message that represents the successfully registration in the system.
- *
- * @param token user identification token within the system
- */
- case class AuthenticationSignUpSuccessful(token: String)
-
- /**
- * Message representing the failure of registration in the system.
- *
- * @param reason reason that generated the failure
- */
- case class AuthenticationSignUpFailure(reason: Option[String])
-
- /**
- * Questo messaggio rappresenta il successo della crazione di una stanza privata.
- *
- * @param token è il token identificativo che restitusice il model dopo che la stanza viene creata correttamente
- */
- case class RoomCreatePrivateSuccessful(token: String)
-
- /**
- * Questo messaggio rappresenta il fallimento nella crazione di una stanza privata.
- *
- * @param reason è il motivo che ha generato il fallimento
- */
- case class RoomCreatePrivateFailure(reason: Option[String])
-
- /**
- * Questo messaggio rappresenta che si è entrati in una stanza privata
- */
- case object RoomEnterPrivateSuccessful
-
- /**
- * Questo messaggio rappresenta che si è entrati in una stanza pubblica
- */
- case object RoomEnterPublicSuccessful
-
- /**
- * Questo messaggio rappresenta che non si è entrati in una stanza pubblica
- */
- /**
- * Questo messaggio rappresenta il fallimento nella entrata in una stanza pubblica.
- *
- * @param reason è il motivo che ha generato il fallimento
- */
- case class RoomEnterPublicFailure(reason: Option[String])
-
- /**
- * Questo messaggio rappresenta il fallimento quando si prova ad entrare in una stanza privata
- *
- * @param reason è il motivo del fallimento
- */
- case class RoomEnterPrivateFailure(reason: Option[String])
-
-}
-
-import it.cwmp.client.model.ApiClientIncomingMessages._
-import it.cwmp.client.model.ApiClientOutgoingMessages._
-
-object ApiClientActor {
- def apply(): ApiClientActor = new ApiClientActor()
-}
-
-class ApiClientActor() extends Actor {
-
- /**
- * Possono essere ricevuti messaggi di tipo [[ApiClientIncomingMessages]] ed inviati quelli di tipo [[ApiClientOutgoingMessages]]
- *
- * @return [[Receive]] che gestisce tutti i messaggi corrispondenti alle richieste che è possibile inviare ai servizi online
- */
- // Tutti i behaviour sono attivi in contemporanea, la separazione è solo logica per una migliore leggibilità
- override def receive: Receive = authenticationBehaviour orElse roomManagerBehaviour // orElse ...
-
- /*
- * Behaviour che gestisce tutte le chiamate al servizio di gestione delle stanze.
- */
- private val roomApiWrapper = RoomsApiWrapper()
-
- import roomApiWrapper._
-
- private def roomManagerBehaviour: Receive = {
- case RoomCreatePrivate(name, nPlayer, token) =>
- val senderTmp = sender
- createRoom(name, nPlayer)(token).onComplete({
- case Success(t) => senderTmp ! RoomCreatePrivateSuccessful(t)
- case Failure(error) => senderTmp ! RoomCreatePrivateFailure(Option(error.getMessage))
- })
- case RoomEnterPrivate(idRoom, address, webAddress, token) =>
- val senderTmp = sender
- enterRoom(idRoom, address, webAddress)(token).onComplete({
- case Success(_) => senderTmp ! RoomEnterPrivateSuccessful
- case Failure(error) => senderTmp ! RoomEnterPrivateFailure(Option(error.getMessage))
- })
- case RoomEnterPublic(nPlayer, address, webAddress, token) =>
- val senderTmp = sender
- enterPublicRoom(nPlayer, address, webAddress)(token).onComplete({
- case Success(_) => senderTmp ! RoomEnterPublicSuccessful
- case Failure(error) => senderTmp ! RoomEnterPublicFailure(Option(error.getMessage))
- })
- }
-
- /*
- * Behavior that handles the calls to the authentication management service.
- */
- private val authenticationApiWrapper = AuthenticationApiWrapper()
-
- import authenticationApiWrapper._
-
- private def authenticationBehaviour: Receive = {
- case AuthenticationPerformSignIn(username, password) =>
- val senderTmp = sender
- login(username, password).onComplete({
- case Success(token) => senderTmp ! AuthenticationSignInSuccessful(token)
- case Failure(reason) => senderTmp ! AuthenticationSignInFailure(Option(reason.getMessage))
- })
- case AuthenticationPerformSignUp(username, password) =>
- val senderTmp = sender
- signUp(username, password).onComplete({
- case Success(token) => senderTmp ! AuthenticationSignUpSuccessful(token)
- case Failure(reason) => senderTmp ! AuthenticationSignUpFailure(Option(reason.getMessage))
- })
-
- }
-}
diff --git a/client/src/main/scala/it/cwmp/client/model/DistributedState.scala b/client/src/main/scala/it/cwmp/client/model/DistributedState.scala
index 7adb115d..6a8af17d 100644
--- a/client/src/main/scala/it/cwmp/client/model/DistributedState.scala
+++ b/client/src/main/scala/it/cwmp/client/model/DistributedState.scala
@@ -1,68 +1,80 @@
package it.cwmp.client.model
import akka.actor.Actor.Receive
-import akka.actor.{Actor, ActorRef}
+import akka.actor.ActorRef
import akka.cluster.Cluster
import akka.cluster.ddata.Replicator._
import akka.cluster.ddata._
-import it.cwmp.client.model.game.World
+import it.cwmp.client.model.DistributedState.DISTRIBUTED_KEY_NAME
import it.cwmp.utils.Logging
-object DistributedStateMessages {
-
- /**
- * The message received when the user do something in the GUI
- *
- * @param world The world to update in the GUI
- * @tparam W Type of the world
- */
- case class UpdateWorld[W <: World[_, _, _]](world: W)
-
-}
-
/**
- * Distributed representation of the world and of his behaviours.
+ * Distributed representation of data and attached behaviours.
*
- * @param onWorldUpdate the update strategy when the world is changed
+ * @param onDistributedStateUpdate the update strategy when the world is changed
* @author Eugenio Pierfederici
+ * @author contributor Enrico Siboni
*/
-case class DistributedState[WD <: World[_, _, _]]
-(actor: Actor, onWorldUpdate: WD => Unit)(implicit replicator: ActorRef, cluster: Cluster) extends Logging {
+abstract class DistributedState[T](onDistributedStateUpdate: T => Unit)
+ (implicit replicatorActor: ActorRef, cluster: Cluster) extends Logging {
- import it.cwmp.client.model.DistributedStateMessages._
+ protected val DistributedKey: LWWRegisterKey[T] = LWWRegisterKey[T](DISTRIBUTED_KEY_NAME)
- private val WorldKey = LWWRegisterKey[WD]("world")
+ /**
+ * Subscribes the provided actor to receive changes in this distributed state
+ *
+ * @param subscriber the actor to subscribe
+ */
+ def subscribe(subscriber: ActorRef): Unit =
+ replicatorActor ! Subscribe(DistributedKey, subscriber)
- def initState(): Unit = {
- replicator ! Subscribe(WorldKey, actor.self)
- }
+ /**
+ * Un-subscribes the provided actor from updates of this distributed state
+ *
+ * @param subscriber the exiting subscriber
+ */
+ def unsubscribe(subscriber: ActorRef): Unit =
+ replicatorActor ! Unsubscribe(DistributedKey, subscriber)
/**
- * This behaviour provides an easy way to integrate the distributed state in the player actor.
+ * This behaviour provides an easy way to make the interested actor,
+ * able to receive updates and make changes in this distributed state
*/
- def stateBehaviour: Receive = userActionBehaviour orElse distributedActionBehaviour
+ def distributedStateBehaviour: Receive = passiveBehaviour orElse activeBehaviour
/**
- * All the behaviours needed to execute all the action requested from the user (the player on this client)
- *
- * @return
+ * @return the behaviour enabling to listen for modification in the distributed state
*/
- private def userActionBehaviour: Receive = {
- // Called from the view/controller
- case UpdateWorld(world: WD) =>
- log.debug("Requiring UpdateWorld DISTRIBUTED")
- replicator ! Update(WorldKey, LWWRegister[WD](world), WriteLocal)(_.withValue(world))
+ protected def passiveBehaviour: Receive = {
+ // Called when notified of the distributed data change
+ case c@Changed(DistributedKey) =>
+ log.debug("Being notified that distributed state has changed")
+ onDistributedStateUpdate(c.get(DistributedKey).getValue)
}
/**
- * All the behaviours needed to manage the distribution of the state
+ * @return the behaviour enabling to modify distributed state
+ */
+ protected def activeBehaviour: Receive
+
+ /**
+ * @return the consistency policy to adopt when writing updates in distributed state
+ */
+ protected def consistencyPolicy: Replicator.WriteConsistency
+}
+
+/**
+ * Companion Object, containing actor messages
+ */
+object DistributedState {
+
+ private val DISTRIBUTED_KEY_NAME = "distributedKey"
+
+ /**
+ * The message to send to update distributed state
*
- * @return
+ * @param state the new state
*/
- private def distributedActionBehaviour: Receive = {
- // Called when notified of the distributed data change
- case c@Changed(WorldKey) =>
- log.debug("UpdateGUI from DISTRIBUTED because WorldKey was changed")
- onWorldUpdate(c.get(WorldKey).getValue)
- }
+ case class UpdateState[T](state: T)
+
}
diff --git a/client/src/main/scala/it/cwmp/client/model/ParticipantListReceiver.scala b/client/src/main/scala/it/cwmp/client/model/ParticipantListReceiver.scala
deleted file mode 100644
index da67f013..00000000
--- a/client/src/main/scala/it/cwmp/client/model/ParticipantListReceiver.scala
+++ /dev/null
@@ -1,21 +0,0 @@
-package it.cwmp.client.model
-
-import java.net.InetAddress
-
-import it.cwmp.model.{Address, Participant}
-import it.cwmp.services.roomreceiver.RoomReceiverServiceVerticle
-import it.cwmp.services.roomreceiver.ServerParameters._
-import it.cwmp.utils.{Utils, VertxInstance}
-
-import scala.concurrent.Future
-
-trait ParticipantListReceiver extends VertxInstance {
-
- def listenForParticipantListFuture(onListReceived: List[Participant] => Unit): Future[Address] = {
- val token = Utils.randomString(20)
- val verticle = RoomReceiverServiceVerticle(token, participants => onListReceived(participants))
- vertx.deployVerticleFuture(verticle)
- .map(_ => Address(s"http://${InetAddress.getLocalHost.getHostAddress}:${verticle.port}"
- + API_RECEIVE_PARTICIPANTS_URL(token)))
- }
-}
diff --git a/client/src/main/scala/it/cwmp/client/model/game/impl/GeometricUtils.scala b/client/src/main/scala/it/cwmp/client/model/game/GeometricUtils.scala
similarity index 67%
rename from client/src/main/scala/it/cwmp/client/model/game/impl/GeometricUtils.scala
rename to client/src/main/scala/it/cwmp/client/model/game/GeometricUtils.scala
index 51f0d711..e5058735 100644
--- a/client/src/main/scala/it/cwmp/client/model/game/impl/GeometricUtils.scala
+++ b/client/src/main/scala/it/cwmp/client/model/game/GeometricUtils.scala
@@ -1,4 +1,6 @@
-package it.cwmp.client.model.game.impl
+package it.cwmp.client.model.game
+
+import it.cwmp.client.model.game.impl.Point
/**
* A little collection of useful geometric utils
@@ -61,7 +63,7 @@ object GeometricUtils {
def deltaXYFromFirstPoint(point1: Point,
point2: Point,
distance: Double): (Double, Double) = {
- require(distance > 0, "Distance should be greater that 0")
+ require(distance > 0, "Distance should be greater than 0")
val deltaY =
if (point1.y == point2.y) 0 // point on same horizontal line has no delta Y
@@ -76,6 +78,41 @@ object GeometricUtils {
(deltaX, deltaY)
}
+ /**
+ * A method returning the distance of a point from a straight line passing through two points
+ *
+ * @param myPoint the point from which to calculate the distance
+ * @param point1 the first point from which the straight line is passing through
+ * @param point2 the second point from which the straight line is passing through
+ * @return the distance of first point from this straight line
+ */
+ def pointDistanceFromStraightLine(myPoint: Point,
+ point1: Point,
+ point2: Point): Double = {
+ val angularCoefficient = GeometricUtils.angularCoefficient(point1, point2)
+
+ if (angularCoefficient.isPosInfinity) Math.abs(myPoint.x - point1.x) // if the straight line is vertical, the distance is the difference from myPoint X and a point on straight line X
+ else
+ Math.abs(myPoint.y - (angularCoefficient * myPoint.x + ordinateAtOrigin(point1, point2))) /
+ Math.sqrt(angularCoefficient.squared + 1)
+ }
+
+ /**
+ * A method that says if provided point is inside the circumference with provided center and radius
+ *
+ * @param point the point to verify inside circumference
+ * @param center the center of the circumference
+ * @param radius the radius of the circumference
+ * @return true if the point is inside or on the circumference, false otherwise
+ */
+ def isWithinCircumference(point: Point,
+ center: Point,
+ radius: Double): Boolean = {
+ require(radius >= 0, "Circumference radius must be positive")
+
+ (point.x - center.x).squared + (point.y - center.y).squared <= radius.squared
+ }
+
/**
* A class that makes possible to square numbers
*
diff --git a/client/src/main/scala/it/cwmp/client/model/game/impl/Cell.scala b/client/src/main/scala/it/cwmp/client/model/game/impl/Cell.scala
index 86b46f99..e08b2cfb 100644
--- a/client/src/main/scala/it/cwmp/client/model/game/impl/Cell.scala
+++ b/client/src/main/scala/it/cwmp/client/model/game/impl/Cell.scala
@@ -1,10 +1,10 @@
package it.cwmp.client.model.game.impl
import java.time.Duration
-import java.util.Objects._
+import java.util.Objects.requireNonNull
-import it.cwmp.client.controller.game.GameConstants._
-import it.cwmp.client.model.game._
+import it.cwmp.client.controller.game.GameConstants.{CELL_ENERGY_WHEN_BORN, MILLIS_TO_ENERGY_CONVERSION_RATE}
+import it.cwmp.client.model.game.{Character, EvolutionStrategy, GeometricUtils}
import it.cwmp.model.User
/**
@@ -17,7 +17,7 @@ import it.cwmp.model.User
*/
case class Cell(owner: User,
position: Point,
- energy: Double) extends Character[User, Point, Double] {
+ energy: Double = CELL_ENERGY_WHEN_BORN) extends Character[User, Point, Double] {
requireNonNull(owner, "User owner must not be null")
requireNonNull(position, "Position must not be null")
diff --git a/client/src/main/scala/it/cwmp/client/model/game/impl/CellWorld.scala b/client/src/main/scala/it/cwmp/client/model/game/impl/CellWorld.scala
index e76ed3ba..7d237c7c 100644
--- a/client/src/main/scala/it/cwmp/client/model/game/impl/CellWorld.scala
+++ b/client/src/main/scala/it/cwmp/client/model/game/impl/CellWorld.scala
@@ -1,9 +1,9 @@
package it.cwmp.client.model.game.impl
import java.time.{Duration, Instant}
-import java.util.Objects._
+import java.util.Objects.requireNonNull
-import it.cwmp.client.controller.game.GameConstants._
+import it.cwmp.client.controller.game.GameConstants.{ATTACK_DURATION_TO_ENERGY_REDUCTION_RATE, LENGTH_TO_ENERGY_REDUCTION_RATE}
import it.cwmp.client.model.game.{SizingStrategy, World}
/**
@@ -74,6 +74,4 @@ object CellWorld {
*/
val durationToEnergyConversionStrategy: SizingStrategy[Duration, Double] =
(attackDuration: Duration) => attackDuration.toMillis / ATTACK_DURATION_TO_ENERGY_REDUCTION_RATE
-
- // TODO: Add converter to DDATA
}
diff --git a/client/src/main/scala/it/cwmp/client/model/game/impl/CellWorldDistributedState.scala b/client/src/main/scala/it/cwmp/client/model/game/impl/CellWorldDistributedState.scala
new file mode 100644
index 00000000..cb143125
--- /dev/null
+++ b/client/src/main/scala/it/cwmp/client/model/game/impl/CellWorldDistributedState.scala
@@ -0,0 +1,29 @@
+package it.cwmp.client.model.game.impl
+
+import akka.actor.Actor.Receive
+import akka.actor.ActorRef
+import akka.cluster.Cluster
+import akka.cluster.ddata.Replicator.{Update, WriteMajority}
+import akka.cluster.ddata.{LWWRegister, Replicator}
+import it.cwmp.client.model.DistributedState
+import it.cwmp.client.model.DistributedState.UpdateState
+
+import scala.concurrent.duration._
+
+/**
+ * Distributed representation of the world and of his behaviours.
+ *
+ * @author Eugenio Pierfederici
+ * @author contributor Enrico Siboni
+ */
+case class CellWorldDistributedState(onWorldUpdate: CellWorld => Unit)
+ (implicit replicatorActor: ActorRef, cluster: Cluster) extends DistributedState[CellWorld](onWorldUpdate) {
+
+ override def consistencyPolicy: Replicator.WriteConsistency = WriteMajority(1.seconds)
+
+ override protected def activeBehaviour: Receive = {
+ case UpdateState(state: CellWorld) =>
+ log.debug("Updating distributed state")
+ replicatorActor ! Update(DistributedKey, LWWRegister[CellWorld](state), consistencyPolicy)(_.withValue(state))
+ }
+}
diff --git a/client/src/main/scala/it/cwmp/client/model/game/impl/Tentacle.scala b/client/src/main/scala/it/cwmp/client/model/game/impl/Tentacle.scala
index 61267d00..df2dc392 100644
--- a/client/src/main/scala/it/cwmp/client/model/game/impl/Tentacle.scala
+++ b/client/src/main/scala/it/cwmp/client/model/game/impl/Tentacle.scala
@@ -1,9 +1,9 @@
package it.cwmp.client.model.game.impl
import java.time.{Duration, Instant}
-import java.util.Objects._
+import java.util.Objects.requireNonNull
-import it.cwmp.client.controller.game.GameConstants._
+import it.cwmp.client.controller.game.GameConstants.MILLIS_TO_MOVEMENT_CONVERSION_RATE
import it.cwmp.client.model.game.{Attack, SizingStrategy}
/**
diff --git a/client/src/main/scala/it/cwmp/client/utils/LayoutRes.scala b/client/src/main/scala/it/cwmp/client/utils/LayoutRes.scala
index 5e1701ba..d30e7f93 100644
--- a/client/src/main/scala/it/cwmp/client/utils/LayoutRes.scala
+++ b/client/src/main/scala/it/cwmp/client/utils/LayoutRes.scala
@@ -2,7 +2,7 @@ package it.cwmp.client.utils
/**
* Utility class that keeps together all the file names of the FXML layout files.
- **/
+ */
object LayoutRes {
val authenticationLayout: String = "/layouts/authenticationManagerLayout.fxml"
diff --git a/client/src/main/scala/it/cwmp/client/utils/StringRes.scala b/client/src/main/scala/it/cwmp/client/utils/StringRes.scala
index 44ee17fb..051d0ac5 100644
--- a/client/src/main/scala/it/cwmp/client/utils/StringRes.scala
+++ b/client/src/main/scala/it/cwmp/client/utils/StringRes.scala
@@ -2,16 +2,10 @@ package it.cwmp.client.utils
/**
* Utility class that keeps together all the strings that compose the interface.
- **/
+ */
object StringRes {
- val none: String = ""
-
val appName: String = "Cell Wars MultiPlayer"
- // Authentication
-
- //RoomManagerView
val roomManagerTitle = "Gestione stanze"
- val createPrivateRoomTitle = "Crea una stanza privata"
}
diff --git a/client/src/main/scala/it/cwmp/client/view/AlertActor.scala b/client/src/main/scala/it/cwmp/client/view/AlertActor.scala
deleted file mode 100644
index 0045e8f8..00000000
--- a/client/src/main/scala/it/cwmp/client/view/AlertActor.scala
+++ /dev/null
@@ -1,43 +0,0 @@
-package it.cwmp.client.view
-
-import akka.actor.Actor.Receive
-
-object AlertMessages {
-
- /**
- * Questo messagio serve per mostrare un messaggio all'utente di tipo informativo.
- *
- * @param title contiene il titolo del messaggio
- * @param message contiene il corpo del messaggio da visualizzare
- */
- case class Info(title: String, message: String, onClose: Option[() => Unit] = None)
-
- /**
- * Questo messagio serve per mostrare un messaggio di errore all'utente.
- *
- * @param title contiene il titolo del messaggio
- * @param message contiene il corpo del messaggio da visualizzare
- */
- case class Error(title: String, message: String, onClose: Option[() => Unit] = None)
-}
-
-/**
- *
- * Trait da estendere ogni volta che si vuole avere in automatico la gestione di dei messaggi informativi o di errore.
- * Per usarlo va aggiunto il behaviour: alertBehaviour al comportamento della receive.
- *
- * @author Eugenio Pierfederici
- */
-trait AlertActor {
-
- def fxController: FXAlerts
-
- /**
- * Il behaviour che si occupa di restare in ascolto per i messaggi specificati in [[AlertMessages]].
- */
- import AlertMessages._
- protected def alertBehaviour: Receive = {
- case Info(title, message, onClose) => fxController showInfo(title, message, onClose)
- case Error(title, message, onClose) => fxController showError(title, message, onClose)
- }
-}
diff --git a/client/src/main/scala/it/cwmp/client/view/FXAlerts.scala b/client/src/main/scala/it/cwmp/client/view/FXAlerts.scala
deleted file mode 100644
index 26524c6d..00000000
--- a/client/src/main/scala/it/cwmp/client/view/FXAlerts.scala
+++ /dev/null
@@ -1,29 +0,0 @@
-package it.cwmp.client.view
-
-import java.util.Optional
-
-import javafx.application.Platform
-import javafx.scene.control.Alert.AlertType
-import javafx.scene.control.{Alert, ButtonType}
-
-trait FXAlerts {
- this: FXController =>
-
- def showInfo(title: String, message: String, onClose: Option[() => Unit] = None): Unit =
- genericAlert(AlertType.INFORMATION, title, s"$title", message, onClose)
-
- def showError(title: String, message: String, onClose: Option[() => Unit] = None): Unit =
- genericAlert(AlertType.ERROR, title, s"$title", message, onClose)
-
- private def genericAlert(alertType: AlertType, title: String, headerTitle: String, message: String, onClose: Option[() => Unit]): Unit = {
- Platform runLater(() => {
- val alert = new Alert(alertType, message, ButtonType.OK)
- alert setTitle title
- alert setHeaderText headerTitle
- val result: Optional[ButtonType] = alert.showAndWait
- if (result.get.eq(ButtonType.OK) && onClose.isDefined) {
- onClose.get()
- }
- })
- }
-}
diff --git a/client/src/main/scala/it/cwmp/client/view/FXAlertsController.scala b/client/src/main/scala/it/cwmp/client/view/FXAlertsController.scala
new file mode 100644
index 00000000..335386d8
--- /dev/null
+++ b/client/src/main/scala/it/cwmp/client/view/FXAlertsController.scala
@@ -0,0 +1,155 @@
+package it.cwmp.client.view
+
+import it.cwmp.client.view.FXAlertsController.WAIT_MESSAGE
+import javafx.scene.Node
+import javafx.scene.control.Alert.AlertType
+import javafx.scene.control.{Alert, ButtonType, ProgressBar}
+import javafx.scene.layout.BorderPane
+
+/**
+ * A trait that makes possible to show Alerts
+ *
+ * @author Eugenio Pierfederici
+ * @author contributor Davide Borficchia
+ * @author contributor Enrico Siboni
+ */
+trait FXAlertsController extends FXRunOnUIThread {
+
+ /**
+ * A method to show an info message
+ *
+ * @param headerText the text to show as header
+ * @param message the specific message
+ * @param onCloseAction the action that should be executed on alert close
+ */
+ def showInfo(headerText: String, message: String,
+ onCloseAction: () => Unit = () => ()): Unit =
+ showAlertWithButton(AlertType.INFORMATION, headerText, message, ButtonType.OK, onCloseAction)
+
+ /**
+ * An info alert with custom content
+ *
+ * @param headerText the header text to show
+ * @param message the message to show
+ * @param alertContent the custom content to show
+ * @param onCloseAction the action that should be executed on alert close
+ */
+ def showInfoWithContent(headerText: String, message: String,
+ alertContent: Node, onCloseAction: () => Unit = () => ()): Unit =
+ showAlertWithButton(AlertType.INFORMATION, headerText, message, ButtonType.OK, onCloseAction, Some(alertContent))
+
+ /**
+ * A method to show an error message
+ *
+ * @param headerText the text to show as header
+ * @param message the specific message
+ * @param onCloseAction the action that should be executed on alert close
+ */
+ def showError(headerText: String, message: String,
+ onCloseAction: () => Unit = () => ()): Unit =
+ showAlertWithButton(AlertType.ERROR, headerText, message, ButtonType.OK, onCloseAction)
+
+ /**
+ * Shows a loading dialog that can not be closed
+ *
+ * @param message the message to show
+ * @param headerText the header text to show
+ */
+ def showLoading(message: String, headerText: String = WAIT_MESSAGE): Unit = runOnUIThread(() => {
+ LoadingManagement
+ .createLoadingAlert(headerText, message, canBeClosed = false)
+ .show()
+ })
+
+
+ /**
+ * Shows a loading dialog that can be closed
+ *
+ * @param message the message to show
+ * @param headerText the header text to show
+ * @param onPrematureClosureAction which action has to be executed if the loading is closed prematurely
+ */
+ def showCloseableLoading(message: String, headerText: String = WAIT_MESSAGE,
+ onPrematureClosureAction: () => Unit = () => ()): Unit = runOnUIThread(() => {
+ LoadingManagement
+ .createLoadingAlert(headerText, message, canBeClosed = true, onPrematureClosureAction)
+ .show()
+ })
+
+ /**
+ * Hides the loading dialog
+ */
+ def hideLoading(): Unit = LoadingManagement.closeLoadingAlert()
+
+ /**
+ * Shows an alert with specified parameters
+ *
+ * @param alertType the alert type
+ * @param headerText the title to set
+ * @param message the message to show
+ * @param buttonType the buttonType
+ * @param dialogContent the content on the dialog
+ */
+ private def showAlertWithButton(alertType: AlertType, headerText: String,
+ message: String, buttonType: ButtonType,
+ onCloseAction: () => Unit,
+ dialogContent: Option[Node] = None): Unit =
+ runOnUIThread(() => {
+ val alert = new Alert(alertType, message, buttonType)
+ alert setTitle alertType.toString
+ alert setHeaderText (if (dialogContent.isDefined) s"$headerText\n\n$message" else headerText)
+ dialogContent foreach (alert.getDialogPane.setContent(_))
+ alert.showAndWait
+ onCloseAction() // because of showAndWait runs after dialog closing
+ })
+
+
+ /**
+ * An object that wraps Loading alert management
+ */
+ private object LoadingManagement {
+ var loadingAlert: Alert = _
+
+ /**
+ * Creates and shows a non blocking loading alert
+ *
+ * @param title the title of the loading alert
+ * @param message the message to show in the loading alert
+ */
+ def createLoadingAlert(title: String, message: String,
+ canBeClosed: Boolean,
+ onPrematureCloseAction: () => Unit = () => ()): Alert = {
+ loadingAlert = new Alert(AlertType.NONE)
+ loadingAlert setTitle title
+ loadingAlert setHeaderText message
+ val pane = new BorderPane()
+ pane.setCenter(new ProgressBar())
+ loadingAlert.getDialogPane.setContent(pane)
+
+ if (canBeClosed) {
+ loadingAlert.getButtonTypes.add(ButtonType.CANCEL)
+ loadingAlert.setOnCloseRequest(_ => onPrematureCloseAction())
+ }
+
+ loadingAlert
+ }
+
+ /**
+ * Closes the loading alert
+ */
+ def closeLoadingAlert(): Unit =
+ runOnUIThread(() => {
+ //noinspection ScalaStyle
+ loadingAlert.setOnCloseRequest(null)
+ loadingAlert.setResult(ButtonType.OK) // setting a result of right type, seems the only way to have dialog to close
+ })
+ }
+
+}
+
+/**
+ * Companion object
+ */
+object FXAlertsController {
+ val WAIT_MESSAGE = "Attendere..."
+}
\ No newline at end of file
diff --git a/client/src/main/scala/it/cwmp/client/view/FXChecks.scala b/client/src/main/scala/it/cwmp/client/view/FXChecks.scala
deleted file mode 100644
index 78415c8c..00000000
--- a/client/src/main/scala/it/cwmp/client/view/FXChecks.scala
+++ /dev/null
@@ -1,38 +0,0 @@
-package it.cwmp.client.view
-
-import javafx.scene.control.{CheckBox, Spinner, TextField}
-
-trait FXChecks extends FXController with FXAlerts {
-
- private val alertTitle = "Wrong input!" // TODO parametrize
-
- def getTextFieldValue(field: TextField): Option[String] =
- if (field != null && field.getText() != "") Some(field.getText)
- else None
-
- def getTextFieldValue(field: TextField, message: String): Option[String] =
- getTextFieldValue(field) match {
- case s @ Some(_) => s
- case None => showError(alertTitle, message); None
- }
-
- def getSpinnerFieldValue[A](spinner: Spinner[A]): Option[A] =
- if (spinner != null) Some(spinner.getValue)
- else None
-
- def getSpinnerFieldValue[A](spinner: Spinner[A], message: String): Option[A] =
- getSpinnerFieldValue(spinner) match {
- case s @ Some(_) => s
- case None => showError(alertTitle, message); None
- }
-
- def getCheckedBoxValue(checkBox: CheckBox): Option[Boolean] =
- if (checkBox != null) Some(checkBox.isSelected)
- else None
-
- def getCheckedBoxValue(checkBox: CheckBox, message: String): Option[Boolean] =
- getCheckedBoxValue(checkBox) match {
- case s @ Some(_) => s
- case None => showError(alertTitle, message); None
- }
-}
\ No newline at end of file
diff --git a/client/src/main/scala/it/cwmp/client/view/FXController.scala b/client/src/main/scala/it/cwmp/client/view/FXController.scala
deleted file mode 100644
index c80a9522..00000000
--- a/client/src/main/scala/it/cwmp/client/view/FXController.scala
+++ /dev/null
@@ -1,7 +0,0 @@
-package it.cwmp.client.view
-
-
-trait FXController {
-
- def resetFields(): Unit
-}
diff --git a/client/src/main/scala/it/cwmp/client/view/FXInputChecks.scala b/client/src/main/scala/it/cwmp/client/view/FXInputChecks.scala
new file mode 100644
index 00000000..03aa98db
--- /dev/null
+++ b/client/src/main/scala/it/cwmp/client/view/FXInputChecks.scala
@@ -0,0 +1,83 @@
+package it.cwmp.client.view
+
+import it.cwmp.utils.Utils.emptyString
+import javafx.scene.control.{CheckBox, Spinner, TextField}
+
+/**
+ * A trait describing common checks over view input
+ */
+trait FXInputChecks extends FXAlertsController {
+
+ private val WRONG_INPUT_ERROR = "Wrong input!" // TODO parametrize
+
+ /**
+ * Gets a text field value
+ *
+ * @param field the field to get
+ * @return optionally the text into field
+ */
+ def getTextFieldValue(field: TextField): Option[String] =
+ if (field != null && !emptyString(field.getText)) Some(field.getText)
+ else None
+
+ /**
+ * Gets a text field or show an error
+ *
+ * @param field the field to get
+ * @param message the message to show if field empty
+ * @return optionally the field text
+ */
+ def getTextFieldValue(field: TextField, message: String): Option[String] =
+ getTextFieldValue(field) match {
+ case s@Some(_) => s
+ case None => showError(WRONG_INPUT_ERROR, message); None
+ }
+
+ /**
+ * Gets the spinner value
+ *
+ * @param spinner the spinner on which to work
+ * @tparam A the type of objects into spinner
+ * @return optionally the spinner value
+ */
+ def getSpinnerFieldValue[A](spinner: Spinner[A]): Option[A] =
+ if (spinner != null) Some(spinner.getValue)
+ else None
+
+ /**
+ * Gets spinner value or shows an error
+ *
+ * @param spinner the spinner on which to work
+ * @param message the message to show if value not present
+ * @tparam A the type of spinner values
+ * @return optionally the spinner value
+ */
+ def getSpinnerFieldValue[A](spinner: Spinner[A], message: String): Option[A] =
+ getSpinnerFieldValue(spinner) match {
+ case s@Some(_) => s
+ case None => showError(WRONG_INPUT_ERROR, message); None
+ }
+
+ /**
+ * Gets the check box value
+ *
+ * @param checkBox the check box on which to work
+ * @return optionally the checkbox value
+ */
+ def getCheckBoxValue(checkBox: CheckBox): Option[Boolean] =
+ if (checkBox != null) Some(checkBox.isSelected)
+ else None
+
+ /**
+ * Gets the checkbox value or show an error message
+ *
+ * @param checkBox the checkbox on which to work
+ * @param message the message to show on error
+ * @return optionally the checkbox value
+ */
+ def getCheckedBoxValue(checkBox: CheckBox, message: String): Option[Boolean] =
+ getCheckBoxValue(checkBox) match {
+ case s@Some(_) => s
+ case None => showError(WRONG_INPUT_ERROR, message); None
+ }
+}
\ No newline at end of file
diff --git a/client/src/main/scala/it/cwmp/client/view/FXInputViewController.scala b/client/src/main/scala/it/cwmp/client/view/FXInputViewController.scala
new file mode 100644
index 00000000..8f5ebdc6
--- /dev/null
+++ b/client/src/main/scala/it/cwmp/client/view/FXInputViewController.scala
@@ -0,0 +1,24 @@
+package it.cwmp.client.view
+
+/**
+ * A trait that gives generic methods that all JavaFX input controllers should have
+ *
+ * @author contributor Enrico Siboni
+ */
+trait FXInputViewController {
+
+ /**
+ * Resets input fields
+ */
+ def resetFields(): Unit
+
+ /**
+ * Disables View components
+ */
+ def disableViewComponents(): Unit
+
+ /**
+ * Enables disabled view components
+ */
+ def enableViewComponents(): Unit
+}
diff --git a/client/src/main/scala/it/cwmp/client/view/FXRunOnUIThread.scala b/client/src/main/scala/it/cwmp/client/view/FXRunOnUIThread.scala
new file mode 100644
index 00000000..1c889ff2
--- /dev/null
+++ b/client/src/main/scala/it/cwmp/client/view/FXRunOnUIThread.scala
@@ -0,0 +1,20 @@
+package it.cwmp.client.view
+
+import javafx.application.Platform
+
+/**
+ * A trait that gives a way to run tasks on JavaFX Application Thread without overhead
+ *
+ * @author Enrico Siboni
+ */
+trait FXRunOnUIThread {
+
+ /**
+ * A method to run a task on UI thread without overheads
+ *
+ * @param task the task to run on GUI thread
+ */
+ def runOnUIThread(task: Runnable): Unit =
+ if (Platform.isFxApplicationThread) task.run() else Platform.runLater(task)
+
+}
diff --git a/client/src/main/scala/it/cwmp/client/view/FXServiceViewActor.scala b/client/src/main/scala/it/cwmp/client/view/FXServiceViewActor.scala
new file mode 100644
index 00000000..f508bc99
--- /dev/null
+++ b/client/src/main/scala/it/cwmp/client/view/FXServiceViewActor.scala
@@ -0,0 +1,47 @@
+package it.cwmp.client.view
+
+import akka.actor.{Actor, ActorRef}
+import it.cwmp.client.controller.messages.Initialize
+import it.cwmp.client.controller.{ActorAlertManagement, ActorViewVisibilityManagement}
+import javafx.application.Platform
+import javafx.embed.swing.JFXPanel
+
+/**
+ * A base class representing a Service View actor with JavaFX underlying
+ *
+ * @author Enrico Siboni
+ */
+abstract class FXServiceViewActor extends Actor with ActorAlertManagement with ActorViewVisibilityManagement {
+
+ protected def fxController: FXViewController with FXAlertsController with FXInputViewController
+
+ protected var controllerActor: ActorRef = _
+
+ override def preStart(): Unit = {
+ super.preStart()
+ new JFXPanel // initializes JavaFX
+ Platform setImplicitExit false
+ }
+
+ override def receive: Receive = alertBehaviour orElse visibilityBehaviour orElse {
+ case Initialize => controllerActor = sender()
+ }
+
+ override protected def onErrorAlertReceived(title: String, message: String): Unit = {
+ onServiceResponseReceived()
+ super.onErrorAlertReceived(title, message)
+ }
+
+ override protected def onInfoAlertReceived(title: String, message: String): Unit = {
+ onServiceResponseReceived()
+ }
+
+ /**
+ * When receiving a response from contacted service should enable buttons and hide loading
+ */
+ protected def onServiceResponseReceived(): Unit = {
+ fxController enableViewComponents()
+ fxController hideLoading()
+ }
+
+}
diff --git a/client/src/main/scala/it/cwmp/client/view/FXView.scala b/client/src/main/scala/it/cwmp/client/view/FXViewController.scala
similarity index 61%
rename from client/src/main/scala/it/cwmp/client/view/FXView.scala
rename to client/src/main/scala/it/cwmp/client/view/FXViewController.scala
index 272acd22..560ef4b9 100644
--- a/client/src/main/scala/it/cwmp/client/view/FXView.scala
+++ b/client/src/main/scala/it/cwmp/client/view/FXViewController.scala
@@ -11,48 +11,60 @@ import javafx.stage.Stage
* Implements the main measures for a basic management of the view, delegating to the class
* that extends it the choice of which components to use in the creation of the interface.
*/
-trait FXView {
+trait FXViewController {
+ protected val stage: Stage = new Stage
+
+ /**
+ * Method called to show the view
+ */
+ def showGUI(): Unit = {
+ initGUI()
+ stage show()
+ }
+
+ /**
+ * Method called to hide the view
+ */
+ def hideGUI(): Unit = stage close()
+
+ /**
+ * @return the path to layout resource
+ */
protected def layout: String
+
+ /**
+ * @return the view title string
+ */
protected def title: String
- protected def stage: Stage
- protected def controller: FXController
+
+ /**
+ * @return the FXViewController for this view
+ */
+ protected def controller: FXViewController
+
+ /**
+ * Tells which action to perform on window close
+ *
+ * @return the default close action (exits JVM)
+ */
+ protected def onCloseAction(): Unit = {
+ Platform.exit()
+ System.exit(0)
+ }
/**
* Initialization of the view
*/
private def initGUI(): Unit = {
- //creo un'istanza del file di layout
+ // creates an instance of layout
val loader = new FXMLLoader(getClass.getResource(layout))
loader.setController(controller)
val pane: Pane = loader.load()
- //setto il titolo della finestra
stage setTitle title
stage setResizable false
-
- //stabilisco cosa fare alla chiusura della finestra
- stage.setOnCloseRequest( _ => {
- Platform.exit()
- System.exit(0)
- })
- //carico il layout nella scena e imposto la scena creata nello stage
+ stage.setOnCloseRequest(_ => onCloseAction())
stage setScene new Scene(pane)
}
-
- /**
- * Method called to show the view
- */
- def showGUI(): Unit = {
- initGUI()
- stage show()
- }
-
- /**
- * Method called to hide the view
- */
- def hideGUI(): Unit = {
- // TODO: vedere se è un comportamento adatto a tutti o va astratto
- stage close()
- }
}
diff --git a/client/src/main/scala/it/cwmp/client/view/authentication/AuthenticationFXController.scala b/client/src/main/scala/it/cwmp/client/view/authentication/AuthenticationFXController.scala
index 42cc6523..1ab68c4d 100644
--- a/client/src/main/scala/it/cwmp/client/view/authentication/AuthenticationFXController.scala
+++ b/client/src/main/scala/it/cwmp/client/view/authentication/AuthenticationFXController.scala
@@ -1,132 +1,106 @@
package it.cwmp.client.view.authentication
import it.cwmp.client.utils.{LayoutRes, StringRes}
-import it.cwmp.client.view.{FXAlerts, FXChecks, FXController, FXView}
-import javafx.application.Platform
+import it.cwmp.client.view._
+import it.cwmp.client.view.authentication.AuthenticationFXController._
import javafx.fxml.FXML
import javafx.scene.control._
-import javafx.stage.Stage
-
-/**
- * Trait that models the strategy to be applied to resolve authentication requests.
- */
-trait AuthenticationFXStrategy {
- /**
- * Function invoked for a system access request.
- *
- * @param username identification chosen by the player to access the system
- * @param password password chosen during sign up
- */
- def onSignIn(username: String, password: String): Unit
-
- /**
- * Function invoked for checking the correctness of the passwords.
- *
- * @param password password chosen
- * @param confirmPassword confirmation password
- * @return true, if the passwords respect the correctness policies
- * false, otherwise
- */
- def onCheckPassword(password: String, confirmPassword: String): Boolean
-
- /**
- * Function invoked for a system registration request.
- *
- * @param username identification chosen by the player to register in the system
- * @param password password chosen to authenticate in the system
- */
- def onSignUp(username: String, password: String): Unit
-}
-
-/**
- * [[AuthenticationFXController]] companion object
- *
- * @author Elia Di Pasquale
- */
-object AuthenticationFXController {
- def apply(strategy: AuthenticationFXStrategy): AuthenticationFXController = {
- require(strategy != null)
- new AuthenticationFXController(strategy)
- }
-}
/**
* Class that models the controller that manages the various authentication processes.
*
* @param strategy strategy to be applied to resolve authentication requests.
+ * @author contributor Enrico Siboni
*/
-class AuthenticationFXController(strategy: AuthenticationFXStrategy) extends FXController with FXView with FXChecks with FXAlerts {
+class AuthenticationFXController(strategy: AuthenticationStrategy) extends FXViewController with FXInputViewController with FXInputChecks {
protected val layout: String = LayoutRes.authenticationLayout
protected val title: String = StringRes.appName
- protected val stage: Stage = new Stage
- protected val controller: FXController = this
-
- @FXML
- private var tpMain: TabPane = _
- @FXML
- private var tfSignInUsername: TextField = _
- @FXML
- private var pfSignInPassword: PasswordField = _
- @FXML
- private var tfSignUpUsername: TextField = _
- @FXML
- private var pfSignUpPassword: PasswordField = _
- @FXML
- private var pfSignUpConfirmPassword: PasswordField = _
-
-
- @FXML
- private def onClickSignIn(): Unit = {
- Platform.runLater(() => {
- for(
- username <- getTextFieldValue(tfSignInUsername, "È necessario inserire lo username");
- password <- getTextFieldValue(pfSignInPassword, "È necessario inserire la password")
- ) yield strategy.onSignIn(username, password)
- })
+ protected val controller: FXViewController = this
+
+ @FXML private var tpMain: TabPane = _
+ @FXML private var tfSignInUsername: TextField = _
+ @FXML private var pfSignInPassword: PasswordField = _
+ @FXML private var tfSignUpUsername: TextField = _
+ @FXML private var pfSignUpPassword: PasswordField = _
+ @FXML private var pfSignUpConfirmPassword: PasswordField = _
+ @FXML private var btnSignIn: Button = _
+ @FXML private var btnSignInReset: Button = _
+ @FXML private var btnSignUp: Button = _
+ @FXML private var btnSignUpReset: Button = _
+
+ override def showGUI(): Unit = {
+ super.showGUI()
+ // adds a listener to reset fields on tab change
+ tpMain.getSelectionModel.selectedItemProperty.addListener((_, _, _) => resetFields())
}
- @FXML
- private def onClickSignUp(): Unit = {
- Platform.runLater(() => {
- for(
- username <- getTextFieldValue(tfSignUpUsername, "È necessario inserire lo username");
- password <- getTextFieldValue(pfSignUpPassword, "È necessario inserire la password");
- confirmPassword <- getTextFieldValue(pfSignUpConfirmPassword, "È necessario inserire nuovamente la password")
- ) yield {
- // TODO: rivedere questa logica per farne una più specifica
- if (strategy.onCheckPassword(password, confirmPassword)) {
- strategy.onSignUp(username, password)
- } else {
- showError("Warning", "Non-compliant passwords!")
- }
- }
- })
+ override def resetFields(): Unit = {
+ tfSignInUsername setText ""
+ pfSignInPassword setText ""
+ tfSignUpUsername setText ""
+ pfSignUpPassword setText ""
+ pfSignUpConfirmPassword setText ""
}
- @FXML
- private def onClickSignInReset(): Unit = {
- Platform.runLater(() => {
- resetFields()
- })
+ override def disableViewComponents(): Unit = {
+ btnSignInReset.setDisable(true)
+ btnSignIn.setDisable(true)
+ btnSignUpReset.setDisable(true)
+ btnSignUp.setDisable(true)
}
- @FXML
- private def onClickSignUpReset(): Unit = {
- Platform.runLater(() => {
- resetFields()
- })
+ override def enableViewComponents(): Unit = {
+ btnSignInReset.setDisable(false)
+ btnSignIn.setDisable(false)
+ btnSignUpReset.setDisable(false)
+ btnSignUp.setDisable(false)
}
- override def resetFields(): Unit = {
- if (tpMain.getSelectionModel.getSelectedIndex == 0) {
- tfSignInUsername setText ""
- pfSignInPassword setText ""
- } else {
- tfSignUpUsername setText ""
- pfSignUpPassword setText ""
- pfSignUpConfirmPassword setText ""
- }
+ @FXML private def onClickSignIn(): Unit =
+ runOnUIThread(() => {
+ for (
+ username <- getTextFieldValue(tfSignInUsername, USERNAME_EMPTY_ERROR);
+ password <- getTextFieldValue(pfSignInPassword, PASSWORD_EMPTY_ERROR)
+ ) yield
+ strategy.performLogIn(username, password)
+ })
+
+
+ @FXML private def onClickSignUp(): Unit =
+ runOnUIThread(() => {
+ for (
+ username <- getTextFieldValue(tfSignUpUsername, USERNAME_EMPTY_ERROR);
+ password <- getTextFieldValue(pfSignUpPassword, PASSWORD_EMPTY_ERROR);
+ confirmPassword <- getTextFieldValue(pfSignUpConfirmPassword, REPEAT_PASSWORD_EMPTY_ERROR)
+ ) yield
+ if (strategy.performPasswordCheck(password, confirmPassword)) {
+ strategy.performSignUp(username, password)
+ } else {
+ showError(ATTENTION_MESSAGE, GIVEN_PASSWORD_DOES_NOT_MATCH)
+ }
+ })
+
+ @FXML private def onClickSignInReset(): Unit = runOnUIThread { () => resetFields() }
+
+ @FXML private def onClickSignUpReset(): Unit = runOnUIThread { () => resetFields() }
+}
+
+/**
+ * Companion object
+ *
+ * @author Elia Di Pasquale
+ */
+object AuthenticationFXController {
+ def apply(strategy: AuthenticationStrategy): AuthenticationFXController = {
+ require(strategy != null, "The authentication strategy cannot be null")
+ new AuthenticationFXController(strategy)
}
+ private val USERNAME_EMPTY_ERROR = "È necessario inserire lo username"
+ private val PASSWORD_EMPTY_ERROR = "È necessario inserire la password"
+ private val REPEAT_PASSWORD_EMPTY_ERROR = "È necessario inserire nuovamente la password"
+ private val GIVEN_PASSWORD_DOES_NOT_MATCH = "Le password inserite non sono uguali!"
+
+ private val ATTENTION_MESSAGE = "Attenzione"
}
diff --git a/client/src/main/scala/it/cwmp/client/view/authentication/AuthenticationStrategy.scala b/client/src/main/scala/it/cwmp/client/view/authentication/AuthenticationStrategy.scala
new file mode 100644
index 00000000..d5a90c09
--- /dev/null
+++ b/client/src/main/scala/it/cwmp/client/view/authentication/AuthenticationStrategy.scala
@@ -0,0 +1,35 @@
+package it.cwmp.client.view.authentication
+
+/**
+ * Trait that models the strategy to be applied to resolve authentication requests.
+ *
+ * @author Elia Di Pasquale
+ */
+trait AuthenticationStrategy {
+
+ /**
+ * Function invoked for a system access request.
+ *
+ * @param username identification chosen by the player to access the system
+ * @param password password chosen during sign up
+ */
+ def performLogIn(username: String, password: String): Unit
+
+ /**
+ * Function invoked for checking the correctness of the passwords.
+ *
+ * @param password password chosen
+ * @param confirmPassword confirmation password
+ * @return true, if the passwords respect the correctness policies
+ * false, otherwise
+ */
+ def performPasswordCheck(password: String, confirmPassword: String): Boolean
+
+ /**
+ * Function invoked for a system registration request.
+ *
+ * @param username identification chosen by the player to register in the system
+ * @param password password chosen to authenticate in the system
+ */
+ def performSignUp(username: String, password: String): Unit
+}
diff --git a/client/src/main/scala/it/cwmp/client/view/authentication/AuthenticationViewActor.scala b/client/src/main/scala/it/cwmp/client/view/authentication/AuthenticationViewActor.scala
index 20817fe1..6640112e 100644
--- a/client/src/main/scala/it/cwmp/client/view/authentication/AuthenticationViewActor.scala
+++ b/client/src/main/scala/it/cwmp/client/view/authentication/AuthenticationViewActor.scala
@@ -1,87 +1,45 @@
package it.cwmp.client.view.authentication
-import akka.actor.{Actor, ActorRef}
-import it.cwmp.client.controller.ClientControllerMessages
-import it.cwmp.client.view.{AlertActor, FXAlerts}
-import javafx.application.Platform
-import javafx.embed.swing.JFXPanel
-
-/**
- * Object that contains all the messages that this actor can receive.
- */
-object AuthenticationViewMessages {
- /**
- * Message that represents the initialization of the controller that will then be used for the answers that will
- * be sent to the sender.
- * When received, the controller is initialized.
- */
- case object InitController
-
- /**
- * Message representing the visualization of the graphical interface.
- * When received, the GUI is shown to the user.
- */
- case object ShowGUI
-
- /**
- * Message represents the hiding of the graphical interface.
- * When received, the GUI is hidden.
- */
- case object HideGUI
-}
-
-object AuthenticationViewActor {
- def apply(): AuthenticationViewActor = new AuthenticationViewActor()
-}
-
+import it.cwmp.client.controller.messages.AuthenticationRequests.{LogIn, SignUp}
+import it.cwmp.client.view.FXServiceViewActor
+import it.cwmp.client.view.authentication.AuthenticationViewActor.{LOGGING_IN_MESSAGE, SIGNING_UP_MESSAGE}
/**
* Actor assigned to the management of the display of the authentication screen and of the events generated by it.
*
* @author Elia Di Pasquale
+ * @author contributor Enrico Siboni
*/
-class AuthenticationViewActor extends Actor with AlertActor {
+case class AuthenticationViewActor() extends FXServiceViewActor {
- /**
- * Controller that deals with the graphic management of authentication components.
- */
- var fxController: AuthenticationFXController = _
- /**
- * Control actor through which messages for event management are received and sent.
- */
- var controllerActor: ActorRef = _
+ protected var fxController: AuthenticationFXController = _
- /**
- * Method invoked at the start of the actor (N.B. Do not call directly).
- * Initializes and manages the controller to manage the graphic layout of the authentication section.
- */
override def preStart(): Unit = {
super.preStart()
-
- //inizializzo il toolkit
- new JFXPanel
- Platform setImplicitExit false
- Platform runLater(() => {
- fxController = AuthenticationFXController(new AuthenticationFXStrategy {
- override def onSignIn(username: String, password: String): Unit =
- controllerActor ! ClientControllerMessages.AuthenticationPerformSignIn(username, password)
-
- override def onCheckPassword(password: String, confirmPassword: String): Boolean =
+ runOnUIThread(() =>
+ fxController = AuthenticationFXController(new AuthenticationStrategy {
+ override def performLogIn(username: String, password: String): Unit = {
+ fxController disableViewComponents()
+ fxController showLoading LOGGING_IN_MESSAGE
+ controllerActor ! LogIn(username, password)
+ }
+
+ override def performPasswordCheck(password: String, confirmPassword: String): Boolean =
password == confirmPassword
- override def onSignUp(username: String, password: String): Unit =
- controllerActor ! ClientControllerMessages.AuthenticationPerformSignUp(username, password)
- })
- })
- }
- /**
- * Method that defines the behavior of the current actor, called from outside to convey messages to it
- * (N.B. Do not call directly).
- * The messages that it is able to process are grouped into [[AuthenticationViewMessages]]
- */
- override def receive: Receive = alertBehaviour orElse {
- case AuthenticationViewMessages.InitController => controllerActor = sender()
- case AuthenticationViewMessages.ShowGUI => Platform runLater(() => fxController showGUI())
- case AuthenticationViewMessages.HideGUI => Platform runLater(() => fxController hideGUI())
+ override def performSignUp(username: String, password: String): Unit = {
+ fxController disableViewComponents()
+ fxController showLoading SIGNING_UP_MESSAGE
+ controllerActor ! SignUp(username, password)
+ }
+ }))
}
}
+
+/**
+ * Companion object
+ */
+object AuthenticationViewActor {
+ private val LOGGING_IN_MESSAGE = "Log-in in corso..."
+ private val SIGNING_UP_MESSAGE = "Registrazione in corso..."
+}
\ No newline at end of file
diff --git a/client/src/main/scala/it/cwmp/client/view/game/CellWorldObjectDrawer.scala b/client/src/main/scala/it/cwmp/client/view/game/CellWorldObjectDrawer.scala
new file mode 100644
index 00000000..9637be81
--- /dev/null
+++ b/client/src/main/scala/it/cwmp/client/view/game/CellWorldObjectDrawer.scala
@@ -0,0 +1,120 @@
+package it.cwmp.client.view.game
+
+import java.time.{Duration, Instant}
+
+import it.cwmp.client.view.game.model._
+import javafx.scene.canvas.GraphicsContext
+import javafx.scene.layout._
+import javafx.scene.paint.Color
+import javafx.scene.shape.{Line, SVGPath}
+import javafx.scene.text.Text
+
+import scala.language.implicitConversions
+
+/**
+ * A trait that makes possible to draw View items
+ *
+ * @author Eugenio Pierfederici
+ * @author Davide Borficchia
+ */
+trait CellWorldObjectDrawer {
+
+ private var firstWorldInstantOption: Option[Instant] = None
+
+ /**
+ * A method to draw a CellView
+ *
+ * @param cell the cell to draw
+ * @return the drawn region
+ */
+ def drawCell(cell: CellView): Region = {
+ val svg = new SVGPath
+ svg.setContent(CellView.CELL_VIEW_DEFAULT_SHAPE)
+ val svgShape = new Region
+ svgShape.setShape(svg)
+ svgShape.setBorder(cell.border)
+ svgShape.setPrefSize(cell.radius * 2, cell.radius * 2)
+ svgShape.setStyle("-fx-background-color: #" + getHexDecimalColor(cell.color))
+ svgShape.setLayoutX(cell.center.x - cell.radius)
+ svgShape.setLayoutY(cell.center.y - cell.radius)
+ svgShape
+ }
+
+ /**
+ * A method to draw the cell energy
+ *
+ * @param cell the cell of which energy is to be drawn
+ * @return the text to add to scene
+ */
+ def drawCellEnergy(cell: CellView): Text = {
+ val energyText = new Text(cell.energy.toInt.toString)
+ energyText.setFont(CellView.ENERGY_DEFAULT_FONT)
+ energyText.setFill(cell.energyTextColor)
+ energyText.setX(cell.center.x - (energyText.getLayoutBounds.getWidth / 2))
+ energyText.setY(cell.center.y + (energyText.getLayoutBounds.getHeight / 2))
+ energyText
+ }
+
+ /**
+ * A method to draw a tentacle on GUI
+ *
+ * @param tentacle the tentacle View to draw
+ * @return the line to add in GUI
+ */
+ def drawTentacle(tentacle: TentacleView): Line = {
+ val line = new Line()
+ line.setStroke(tentacle.color)
+ line.setStrokeWidth(tentacle.thickness)
+
+ val attackerPosition = tentacle.startPoint
+ val tentacleReachedPoint = tentacle.reachedPoint
+ line.setStartX(attackerPosition.x)
+ line.setStartY(attackerPosition.y)
+ line.setEndX(tentacleReachedPoint.x)
+ line.setEndY(tentacleReachedPoint.y)
+ line
+ }
+
+ /**
+ * A method to draw elapsed time on GUI
+ *
+ * @param actualWorldInstant the actual World Instant
+ * @param graphicsContext the graphic context on which to draw
+ * @return the text to draw
+ */
+ def drawInstant(actualWorldInstant: Instant)
+ (implicit graphicsContext: GraphicsContext): Text = {
+ val elapsedTimeFromBeginning: Duration = firstWorldInstantOption match {
+ case Some(firstWorldInstant) => Duration.between(firstWorldInstant, actualWorldInstant)
+ case None =>
+ firstWorldInstantOption = Some(actualWorldInstant)
+ Duration.ofSeconds(0)
+ }
+ val minutes = elapsedTimeFromBeginning.getSeconds / 60
+ val seconds = elapsedTimeFromBeginning.getSeconds % 60
+ val instantText = new Text(GameViewConstants.GAME_TIME_TEXT_FORMAT.format(minutes, seconds))
+ instantText.setFont(GameViewConstants.GAME_TIME_TEXT_FONT)
+ instantText.setFill(GameViewConstants.GAME_TIME_TEXT_COLOR)
+ instantText.setX((graphicsContext.getCanvas.getWidth / 2) - (instantText.getLayoutBounds.getWidth / 2))
+ instantText.setY(instantText.getLayoutBounds.getHeight)
+ instantText
+ }
+
+ /**
+ * Converter from JavaFX Colors to JavaAWT colors
+ *
+ * @param fxColor the JavaFX color to convert
+ * @return the JAvaAWT correspondent
+ */
+ private implicit def fxColorToAwtColor(fxColor: javafx.scene.paint.Color): java.awt.Color = {
+ new java.awt.Color(fxColor.getRed.toFloat, fxColor.getGreen.toFloat, fxColor.getBlue.toFloat, fxColor.getOpacity.toFloat)
+ }
+
+ /**
+ * Returns the Hex string representation of the color
+ *
+ * @param color the color of which to calculate the Hex representation
+ * @return the Hex string representation
+ */
+ private def getHexDecimalColor(color: Color): String = Integer.toHexString(color.getRGB).substring(2)
+}
diff --git a/client/src/main/scala/it/cwmp/client/view/game/GameFX.scala b/client/src/main/scala/it/cwmp/client/view/game/GameFX.scala
index 34b175cb..ab7b95a4 100644
--- a/client/src/main/scala/it/cwmp/client/view/game/GameFX.scala
+++ b/client/src/main/scala/it/cwmp/client/view/game/GameFX.scala
@@ -1,35 +1,51 @@
package it.cwmp.client.view.game
-import it.cwmp.client.model.game.impl.CellWorld
+import akka.actor.ActorRef
+import it.cwmp.client.model.game.impl.{CellWorld, Point}
+import it.cwmp.client.view.game.model.CellView._
+import it.cwmp.client.view.game.model.TentacleView
import javafx.application.Platform
import javafx.embed.swing.JFXPanel
import javafx.scene.canvas.{Canvas, GraphicsContext}
+import javafx.scene.input.MouseEvent
import javafx.scene.{Group, Scene}
import javafx.stage.Stage
+import scala.language.implicitConversions
+
/**
- * Questa classe permette di visualizzare una GUI statica che rappresenta uno stato del gioco
+ * This class shows the Game GUI
*
+ * @param viewManagerActor the actor that manages view events
* @author Davide Borficchia
+ * @author contributor Enrico Siboni
*/
-case class GameFX() extends ObjectDrawer {
+case class GameFX(viewManagerActor: ActorRef) extends CellWorldObjectDrawer {
- var stage: Stage = _
- var root: Group = _
- var canvas: Canvas = _
+ private var stage: Stage = _
+ private var root: Group = _
+ private var canvas: Canvas = _
+ /**
+ * Initializes the GUI
+ *
+ * @param title the GUI title
+ * @param size the Window size
+ */
def start(title: String, size: Int): Unit = {
- new JFXPanel()
+ new JFXPanel() // initializes JavaFX
Platform.runLater(() => {
stage = new Stage
root = new Group
canvas = new Canvas(size, size)
+ UserEventHandler.initializeEventHandlers(root, viewManagerActor)
+
stage.setTitle(title)
root.getChildren.add(canvas)
stage.setScene(new Scene(root))
- //stabilisco cosa fare alla chiusura della finestra
+ // what to do on window closed
stage.setOnCloseRequest(_ => {
Platform.exit()
System.exit(0)
@@ -38,21 +54,100 @@ case class GameFX() extends ObjectDrawer {
})
}
+ /**
+ * Closes the GUI
+ */
def close(): Unit = {
Platform.runLater(() => {
stage.close()
})
}
+ /**
+ * Updates the GUI with the newly provided world
+ *
+ * @param world the new world to draw
+ */
def updateWorld(world: CellWorld): Unit = {
Platform.runLater(() => {
implicit val graphicsContext: GraphicsContext = canvas.getGraphicsContext2D
- graphicsContext.clearRect(0, 0, canvas.getWidth, canvas.getHeight)
- import it.cwmp.client.view.game.model.CellView._
+ root.getChildren.clear()
- world.attacks.foreach(tentacle => drawArch(tentacle, world.instant))
- // println(world.characters.map(_.size))
+ world.attacks.foreach(tentacle => root.getChildren.add(drawTentacle(TentacleView.tentacleToView(tentacle, world.instant))))
world.characters.foreach(cell => root.getChildren.add(drawCell(cell)))
+ world.characters.foreach(cell => root.getChildren.add(drawCellEnergy(cell)))
+ root.getChildren.add(drawInstant(world.instant))
})
}
+
+ /**
+ * An object that wraps logic behind user events un GUI
+ *
+ * @author Enrico Siboni
+ */
+ private object UserEventHandler {
+
+ private var mouseIsDragging = false
+ private var startDragPoint: Point = _
+
+ /**
+ * A method to initialize event handlers for GUI user actions
+ *
+ * @param viewGroup the viewGroup on which to listen for events
+ */
+ def initializeEventHandlers(viewGroup: Group, viewManagerActor: ActorRef): Unit = {
+
+ // user pressed mouse
+ viewGroup.addEventHandler(MouseEvent.MOUSE_PRESSED, (event: MouseEvent) => startDragPoint = event)
+
+ // start of user dragging
+ viewGroup.addEventHandler(MouseEvent.DRAG_DETECTED, (_: MouseEvent) => mouseIsDragging = true)
+
+ // stop of user dragging
+ viewGroup.addEventHandler(MouseEvent.MOUSE_RELEASED, (event: MouseEvent) =>
+ if (mouseIsDragging) {
+ val stopDragPoint: Point = event
+ sendAddAttackEvent(startDragPoint, stopDragPoint, viewManagerActor)
+ })
+
+ // user click event
+ viewGroup.addEventHandler(MouseEvent.MOUSE_CLICKED, (event: MouseEvent) =>
+ // if the user was dragging this event is launched after MOUSE_RELEASED
+ if (mouseIsDragging) {
+ mouseIsDragging = false // reset user dragging state
+ } else {
+ sendRemoveAttackEvent(event, viewManagerActor)
+ }
+ )
+ }
+
+ /**
+ * A method to send the AddAttack message to provided actor
+ *
+ * @param start the start point of the attack
+ * @param stop the stop point of the attack
+ * @param actor the actor responsible of this management
+ */
+ private def sendAddAttackEvent(start: Point, stop: Point, actor: ActorRef): Unit =
+ actor ! GameViewActor.AddAttack(start, stop)
+
+ /**
+ * A method to send the RemoveAttack messsage to provided actor
+ *
+ * @param onAttackPoint the point on the Attack View to remove
+ * @param actor the actor responsible of this management
+ */
+ private def sendRemoveAttackEvent(onAttackPoint: Point, actor: ActorRef): Unit =
+ actor ! GameViewActor.RemoveAttack(onAttackPoint)
+
+ /**
+ * An implicit conversion from mouse event to the point where event was generated
+ *
+ * @param event the event to convert
+ * @return the Point where event was generated
+ */
+ implicit def eventToPoint(event: MouseEvent): Point =
+ Point(event.getX.toInt, event.getY.toInt)
+ }
+
}
diff --git a/client/src/main/scala/it/cwmp/client/view/game/GameViewActor.scala b/client/src/main/scala/it/cwmp/client/view/game/GameViewActor.scala
index 17257422..768ef2bd 100644
--- a/client/src/main/scala/it/cwmp/client/view/game/GameViewActor.scala
+++ b/client/src/main/scala/it/cwmp/client/view/game/GameViewActor.scala
@@ -1,63 +1,167 @@
package it.cwmp.client.view.game
-import akka.actor.{Actor, ActorLogging, Cancellable}
+import akka.actor.{Actor, ActorRef, Cancellable}
import it.cwmp.client.controller.game.GameEngine
-import it.cwmp.client.model.game.impl.CellWorld
+import it.cwmp.client.model.DistributedState
+import it.cwmp.client.model.game.GeometricUtils
+import it.cwmp.client.model.game.impl._
+import it.cwmp.client.view.game.GameViewActor._
+import it.cwmp.client.view.game.model.{CellView, TentacleView}
+import it.cwmp.utils.Logging
import scala.concurrent.duration._
-object GameViewActor {
- def apply(): GameViewActor = new GameViewActor
-
- case object ShowGUI
-
- case object HideGUI
-
- case class NewWorld(world: CellWorld)
-
- case object UpdateLocalWorld
-
-}
-
-import it.cwmp.client.view.game.GameViewActor._
-
/**
+ * The actor that deals with Game View
+ *
* @author contributor Enrico Siboni
*/
-class GameViewActor extends Actor with ActorLogging {
+class GameViewActor(parentActor: ActorRef) extends Actor with Logging {
- private val gameFX: GameFX = GameFX()
- private val FRAME_RATE: FiniteDuration = 50.millis
+ private val gameFX: GameFX = GameFX(self)
+ private val TIME_BETWEEN_FRAMES: FiniteDuration = 500.millis
- private var isHidden = true
private var updatingSchedule: Cancellable = _
private var tempWorld: CellWorld = _
- override def receive: Receive = {
+ override def receive: Receive = showGUIBehaviour
+
+ /**
+ * The behaviour of opening the view
+ */
+ private def showGUIBehaviour: Receive = {
case ShowGUI =>
- if (isHidden) {
- isHidden = false
- gameFX.start("GIOCO", 512)
- }
+ gameFX.start(VIEW_TITLE, VIEW_SIZE)
+ context.become(hideGUIBehaviour orElse
+ newWorldBehaviour orElse guiWorldModificationsBehaviour)
+ }
+
+ /**
+ * The behaviour of closing the view
+ */
+ private def hideGUIBehaviour: Receive = {
case HideGUI =>
- if (!isHidden) {
- isHidden = true
- gameFX.close()
- }
+ gameFX.close()
+ context.become(showGUIBehaviour)
+ }
+
+ /**
+ * The behaviour of receiving world modifications from external source
+ */
+ private def newWorldBehaviour: Receive = {
case NewWorld(world) =>
if (updatingSchedule != null) updatingSchedule.cancel()
tempWorld = world
updatingSchedule = context.system.scheduler
- .schedule(0.millis,
- FRAME_RATE,
- self,
- UpdateLocalWorld)(context.dispatcher)
+ .schedule(0.millis, TIME_BETWEEN_FRAMES, self, UpdateLocalWorld)(context.dispatcher)
case UpdateLocalWorld =>
- log.info(s"World to paint: Characters=${tempWorld.characters} Attacks=${tempWorld.attacks} Instant=${tempWorld.instant}")
+ // log.info(s"World to paint: Characters=${tempWorld.characters} Attacks=${tempWorld.attacks} Instant=${tempWorld.instant}")
gameFX.updateWorld(tempWorld)
// is that to heavy computation here ???
- tempWorld = GameEngine(tempWorld, java.time.Duration.ofMillis(FRAME_RATE.toMillis))
+ tempWorld = GameEngine(tempWorld, java.time.Duration.ofMillis(TIME_BETWEEN_FRAMES.toMillis))
+ }
+
+ /**
+ * The behaviour of listening for user events on GUI
+ */
+ private def guiWorldModificationsBehaviour: Receive = {
+ case AddAttack(from, to) =>
+ log.info(s"AddAttack from:$from to:$to")
+ val worldCharacters = tempWorld.characters
+ val fromCell = findCellNearTo(from, worldCharacters)
+ val toCell = findCellNearTo(to, worldCharacters)
+ (fromCell, toCell) match {
+ case (Some(attacker), Some(attacked)) if attacker != attacked =>
+ log.debug(s"Adding attack from $attacker to $attacked ...")
+ parentActor ! DistributedState.UpdateState(tempWorld ++ Tentacle(attacker, attacked, tempWorld.instant))
+ case tmp@_ => log.debug(s"No cells detected or auto-attack $tmp")
+ }
+
+ case RemoveAttack(pointOnAttackView) =>
+ log.info(s"RemoveAttack pointOnView:$pointOnAttackView")
+ val attack = findTentacleNearTo(pointOnAttackView, tempWorld.attacks)
+ attack match {
+ case Some(tentacle) =>
+ log.debug(s"Removing this attack: $tentacle ...")
+ parentActor ! DistributedState.UpdateState(tempWorld -- tentacle)
+ case tmp@_ => log.debug(s"No attack detected $tmp")
+ }
}
}
+
+/**
+ * Companion object, containing actor messages
+ */
+object GameViewActor {
+ def apply(parentActor: ActorRef): GameViewActor = new GameViewActor(parentActor)
+
+ /**
+ * The title of game view
+ */
+ val VIEW_TITLE = "CellWars"
+
+ /**
+ * The size of the squared view
+ */
+ val VIEW_SIZE = 512 // TODO: sarebbe buono forse fare una dimensione diversa in base alla dimensione dello schermo
+
+ /**
+ * Shows the GUI
+ */
+ case object ShowGUI
+
+ /**
+ * Hides the GUI
+ */
+ case object HideGUI
+
+ /**
+ * Sets a new world to display
+ *
+ * @param world the newWorld from which to compute new evolution
+ */
+ case class NewWorld(world: CellWorld)
+
+ /**
+ * Updates local version of the world making it "move"
+ */
+ case object UpdateLocalWorld
+
+ /**
+ * A message stating that an attack has been launched from one point to another
+ *
+ * @param from the point from which attack is starting
+ * @param to the point to which the attack is going
+ */
+ case class AddAttack(from: Point, to: Point)
+
+ /**
+ * A message stating that an attack has been removed
+ *
+ * @param pointOnAttackView the point clicked by the player to remove the attack
+ */
+ case class RemoveAttack(pointOnAttackView: Point)
+
+ /**
+ * A method to find a cell near to a clicked point on view, according to actual cell sizing
+ *
+ * @param clickedPoint the clicked point on view
+ * @param cells the collection of cells on screen
+ * @return optionally the cell near the clicked point
+ */
+ private def findCellNearTo(clickedPoint: Point, cells: Seq[Cell]): Option[Cell] =
+ cells.find(cell => GeometricUtils.isWithinCircumference(clickedPoint, cell.position, CellView.sizingStrategy(cell)))
+
+ /**
+ * A method to find the tentacle near to a clicked point on view, according to actual tentacle sizing
+ *
+ * @param clickedPoint the cliked point on view
+ * @param tentacles the collection of attacks on screen
+ * @return optionally the tentacle near the clicked point
+ */
+ private def findTentacleNearTo(clickedPoint: Point, tentacles: Seq[Tentacle]): Option[Tentacle] =
+ tentacles.find(tentacle => GeometricUtils.
+ pointDistanceFromStraightLine(clickedPoint, tentacle.from.position, tentacle.to.position) <= TentacleView.thicknessStrategy(tentacle))
+}
diff --git a/client/src/main/scala/it/cwmp/client/view/game/GameViewConstants.scala b/client/src/main/scala/it/cwmp/client/view/game/GameViewConstants.scala
index 3cad8aae..fc6b2cd1 100644
--- a/client/src/main/scala/it/cwmp/client/view/game/GameViewConstants.scala
+++ b/client/src/main/scala/it/cwmp/client/view/game/GameViewConstants.scala
@@ -1,19 +1,52 @@
package it.cwmp.client.view.game
-import java.awt.Color
+import javafx.scene.paint.Color
+import javafx.scene.text.Font
/**
* An object where to put constants about the game visual
+ *
+ * @author Enrico Siboni
*/
object GameViewConstants {
/**
- * @return the cell Size
+ * The constant value indicating the rgb range max value
*/
- def cellSize: Int = 20
+ val RGB_RANGE = 255.0
/**
- * @return the default color
+ * The game default font size
*/
- def defaultColor: Color = Color.BLACK
+ val GAME_DEFAULT_FONT_SIZE = 20
+
+ /**
+ * The game default font color
+ */
+ val GAME_DEFAULT_FONT_COLOR: Color = Color.BLACK
+
+
+ // GAME TIME TEXT CONSTANTS
+
+ /**
+ * The game time default font
+ */
+ val GAME_TIME_TEXT_FONT: Font = Font.font("Verdana", GAME_DEFAULT_FONT_SIZE)
+
+ /**
+ * The constant value indicating the transparency of instant text in the GUI
+ */
+ val GAME_TIME_TEXT_TRANSPARENCY: Double = 0.5
+
+ /**
+ * The game time default font color
+ */
+ val GAME_TIME_TEXT_COLOR: Color =
+ Color.color(GAME_DEFAULT_FONT_COLOR.getRed, GameViewConstants.GAME_DEFAULT_FONT_COLOR.getGreen,
+ GameViewConstants.GAME_DEFAULT_FONT_COLOR.getBlue, GameViewConstants.GAME_TIME_TEXT_TRANSPARENCY)
+
+ /**
+ * The game time default format
+ */
+ val GAME_TIME_TEXT_FORMAT: String = "%02dm : %02ds"
}
diff --git a/client/src/main/scala/it/cwmp/client/view/game/ObjectDrawer.scala b/client/src/main/scala/it/cwmp/client/view/game/ObjectDrawer.scala
deleted file mode 100644
index f33e5c19..00000000
--- a/client/src/main/scala/it/cwmp/client/view/game/ObjectDrawer.scala
+++ /dev/null
@@ -1,66 +0,0 @@
-package it.cwmp.client.view.game
-
-import java.awt.Color
-import java.time.Instant
-
-import it.cwmp.client.model.game.impl.Tentacle
-import it.cwmp.client.view.game.model._
-import javafx.scene.canvas.GraphicsContext
-import javafx.scene.layout.Region
-import javafx.scene.shape.SVGPath
-
-import scala.language.implicitConversions
-
-/**
- *
- * @author Eugenio Pierfederici
- * @author Davide Borficchia
- */
-trait ObjectDrawer {
- /**
- * Meodo utilizzato per disegnare una cella nella GUI
- *
- * @param cell oggetto che rappresenta la cella che verrà disegnata
- * @param graphicsContext è l'oggetto che disenga la cella
- */
- def drawCell(cell: CellView)(implicit graphicsContext: GraphicsContext): Region = {
- val svg = new SVGPath
- //TODO cella hardcodata
- svg.setContent("M52.232,44.235c-1.039-0.285-2.039-0.297-2.969-0.112c-0.632,0.126-1.286-0.008-1.788-0.411 l-1.411-1.132c-0.766-0.614-0.992-1.665-0.585-2.559c0.377-0.829,0.69-1.693,0.932-2.587c0.402-1.487,2.008-2.394,3.444-1.838 c1.535,0.593,3.311,0.557,4.908-0.266c2.772-1.429,3.996-4.918,2.728-7.767c-1.364-3.066-4.967-4.413-8.002-3.009 c-0.266,0.123-0.563,0.312-0.868,0.535c-1.36,0.995-3.169,0.637-4.034-0.809c-0.546-0.913-1.215-1.741-1.882-2.571 c-0.883-1.098-1.037-2.618-0.387-3.878l1.479-2.871c0.55-1.068,1.57-1.871,2.765-1.988c0.603-0.059,1.226-0.22,1.865-0.512 c1.888-0.864,3.284-2.642,3.537-4.703c0.486-3.963-2.896-7.283-6.876-6.689c-2.527,0.377-4.589,2.411-4.996,4.933 c-0.197,1.221-0.025,2.386,0.423,3.404c0.459,1.045,0.499,2.226-0.024,3.241l-1.158,2.249c-0.805,1.563-2.612,2.394-4.29,1.87 c-0.981-0.306-2.718-0.523-3.92-0.644c-0.787-0.079-1.438-0.646-1.621-1.416L28.36,9.904c-0.141-0.594,0.036-1.207,0.435-1.669 c0.945-1.093,1.431-2.589,1.119-4.212c-0.371-1.933-1.91-3.514-3.838-3.913C22.849-0.557,20.009,1.89,20.009,5 c0,2.146,1.356,3.962,3.256,4.668c0.611,0.227,1.095,0.705,1.246,1.339l0.913,3.852c0.219,0.925-0.304,1.849-1.198,2.172 c-1.281,0.462-2.491,1.072-3.608,1.813c-0.802,0.531-1.895,0.519-2.642-0.086c-0.815-0.661-0.991-1.728-0.603-2.64 c0.628-1.474,0.829-3.173,0.429-4.95c-0.683-3.039-3.181-5.446-6.243-6.021c-5.63-1.057-10.471,3.79-9.402,9.422 c0.603,3.175,3.181,5.722,6.36,6.297c1.408,0.254,2.765,0.139,3.991-0.264c0.847-0.279,1.776,0.029,2.335,0.724l0.4,0.498 c0.574,0.714,0.636,1.706,0.167,2.493c-1.177,1.973-1.964,4.202-2.258,6.587c-0.122,0.992-0.904,1.771-1.9,1.86l-1.899,0.17 c-0.65,0.058-1.266-0.211-1.732-0.667c-0.721-0.705-1.688-1.181-2.83-1.258c-1.783-0.12-3.526,0.87-4.309,2.477 c-1.295,2.657,0.195,5.671,2.899,6.372c1.949,0.505,3.916-0.356,4.926-1.979c0.363-0.584,1.017-0.926,1.702-0.987l1.631-0.146 c0.987-0.088,1.888,0.529,2.192,1.472c0.669,2.076,1.728,3.977,3.089,5.618c0.487,0.587,0.459,1.443-0.051,2.009 c-0.425,0.472-1.085,0.609-1.69,0.416c-0.687-0.219-1.434-0.307-2.209-0.233c-2.498,0.237-4.582,2.233-4.913,4.721 c-0.497,3.738,2.765,6.871,6.537,6.15c1.964-0.376,3.596-1.867,4.172-3.783c0.28-0.93,0.303-1.829,0.139-2.661 c-0.092-0.468,0.051-0.95,0.37-1.305l0.785-0.871c0.472-0.524,1.243-0.673,1.862-0.336c0.489,0.266,0.993,0.508,1.51,0.726 c1.104,0.464,1.704,1.618,1.598,2.81c-0.051,0.575-0.019,1.174,0.11,1.787c0.528,2.504,2.683,4.44,5.228,4.703 c3.6,0.372,6.638-2.443,6.638-5.967c0-0.384-0.037-0.76-0.107-1.124c-0.23-1.199,0.257-2.415,1.325-3.006 c0.984-0.545,1.909-1.185,2.761-1.909c0.525-0.446,1.222-0.7,1.891-0.531c1.365,0.345,1.95,1.682,1.526,2.836 c-0.376,1.023-0.502,2.167-0.301,3.36c0.463,2.746,2.736,4.939,5.495,5.313c4.215,0.571,7.784-2.901,7.378-7.088 C56.721,47.208,54.792,44.938,52.232,44.235z")
- val svgShape = new Region
- svgShape.setShape(svg)
- // TODO: the cell size is not drawn according to size value!!!!
- svgShape.setMinSize(cell.size, cell.size)
- svgShape.setPrefSize(cell.size, cell.size)
- svgShape.setMaxSize(cell.size, cell.size)
- svgShape.setStyle("-fx-background-color: " + "#" + Integer.toHexString(cell.color.getRGB).substring(2))
- svgShape.setLayoutX(cell.center.x - cell.size / 2)
- svgShape.setLayoutY(cell.center.y - cell.size / 2)
- svgShape
- }
-
- /**
- * Metodo utilizato per disegnare l'arco che unisce due celle
- *
- * @param graphicsContext è l'oggetto che disenga l'arco
- */
- def drawArch(tentacle: Tentacle, actualInstant: Instant)(implicit graphicsContext: GraphicsContext): Unit = {
- graphicsContext.setStroke(TentacleView.coloringStrategy(tentacle))
- graphicsContext.setLineWidth(3.0)
-
- val attackerPosition = tentacle.from.position
- val tentacleReachedPoint = TentacleView.reachedPoint(tentacle, actualInstant)
- graphicsContext.strokeLine(attackerPosition.x, attackerPosition.y,
- tentacleReachedPoint.x, tentacleReachedPoint.y)
- }
-
- /**
- * Funzione per convertire i colori java.awt nel formato di colori utilizzato da javaFX
- *
- * @param awtColor Colore che si vuole convertire
- * @return
- */
- private implicit def awtColorToFxColor(awtColor: Color): javafx.scene.paint.Color = {
- new javafx.scene.paint.Color(awtColor.getRed / 255.0, awtColor.getGreen / 255.0, awtColor.getBlue / 255.0, awtColor.getAlpha / 255.0)
- }
-}
diff --git a/client/src/main/scala/it/cwmp/client/view/game/model/CellView.scala b/client/src/main/scala/it/cwmp/client/view/game/model/CellView.scala
index 60a1941f..7d3a78e0 100644
--- a/client/src/main/scala/it/cwmp/client/view/game/model/CellView.scala
+++ b/client/src/main/scala/it/cwmp/client/view/game/model/CellView.scala
@@ -1,34 +1,67 @@
package it.cwmp.client.view.game.model
-import java.awt.Color
-
-import com.github.tkqubo.colorHash.ColorHash
+import com.github.tkqubo.colorHash.{ColorHash, Rgb}
+import it.cwmp.client.controller.game.GameConstants
import it.cwmp.client.model.game.impl.{Cell, Point}
-import it.cwmp.client.model.game.SizingStrategy
-import it.cwmp.client.view.game.ColoringStrategy
-import it.cwmp.client.view.game.GameViewConstants._
+import it.cwmp.client.model.game.{GeometricUtils, SizingStrategy}
+import it.cwmp.client.view.game.GameViewConstants.{GAME_DEFAULT_FONT_COLOR, GAME_TIME_TEXT_COLOR, RGB_RANGE}
+import it.cwmp.client.view.game.{ColoringStrategy, GameViewConstants}
+import javafx.scene.layout._
+import javafx.scene.paint.Color
+import javafx.scene.text.Font
import scala.language.implicitConversions
/**
- * Classe che rappresenta una cella
+ * A class representing the View counterpart of a Cell
*
+ * @param center the center point where the CellView will be placed
+ * @param radius the radius of the cell
+ * @param color the color of the cellView
+ * @param energy the cell energy
+ * @param energyTextColor the color of the energyText
+ * @param border the cell border
* @author Davide Borficchia
* @author Eugenio Pierfederici
- * @param center punto nel quale verrà disegnata la cella
- * @param size dimensione della cella
*/
-case class CellView(center: Point, color: Color, size: Int)
+case class CellView(center: Point, radius: Double, color: Color,
+ energy: Double, energyTextColor: Color, border: Border)
/**
* Companion object
+ *
+ * @author Enrico Siboni
*/
object CellView {
/**
+ * The default font for energy text on cellViews
+ */
+ val ENERGY_DEFAULT_FONT: Font = Font.font("Verdana", GameViewConstants.GAME_DEFAULT_FONT_SIZE)
+
+ /**
+ * The cell default shape in SVG string format
+ */
+ val CELL_VIEW_DEFAULT_SHAPE: String = "M52.232,44.235c-1.039-0.285-2.039-0.297-2.969-0.112c-0.632,0.126-1.286-0.008-1.788-0.411 l-1.411-1.132c-0.766-0.614-0.992-1.665-0.585-2.559c0.377-0.829,0.69-1.693,0.932-2.587c0.402-1.487,2.008-2.394,3.444-1.838 c1.535,0.593,3.311,0.557,4.908-0.266c2.772-1.429,3.996-4.918,2.728-7.767c-1.364-3.066-4.967-4.413-8.002-3.009 c-0.266,0.123-0.563,0.312-0.868,0.535c-1.36,0.995-3.169,0.637-4.034-0.809c-0.546-0.913-1.215-1.741-1.882-2.571 c-0.883-1.098-1.037-2.618-0.387-3.878l1.479-2.871c0.55-1.068,1.57-1.871,2.765-1.988c0.603-0.059,1.226-0.22,1.865-0.512 c1.888-0.864,3.284-2.642,3.537-4.703c0.486-3.963-2.896-7.283-6.876-6.689c-2.527,0.377-4.589,2.411-4.996,4.933 c-0.197,1.221-0.025,2.386,0.423,3.404c0.459,1.045,0.499,2.226-0.024,3.241l-1.158,2.249c-0.805,1.563-2.612,2.394-4.29,1.87 c-0.981-0.306-2.718-0.523-3.92-0.644c-0.787-0.079-1.438-0.646-1.621-1.416L28.36,9.904c-0.141-0.594,0.036-1.207,0.435-1.669 c0.945-1.093,1.431-2.589,1.119-4.212c-0.371-1.933-1.91-3.514-3.838-3.913C22.849-0.557,20.009,1.89,20.009,5 c0,2.146,1.356,3.962,3.256,4.668c0.611,0.227,1.095,0.705,1.246,1.339l0.913,3.852c0.219,0.925-0.304,1.849-1.198,2.172 c-1.281,0.462-2.491,1.072-3.608,1.813c-0.802,0.531-1.895,0.519-2.642-0.086c-0.815-0.661-0.991-1.728-0.603-2.64 c0.628-1.474,0.829-3.173,0.429-4.95c-0.683-3.039-3.181-5.446-6.243-6.021c-5.63-1.057-10.471,3.79-9.402,9.422 c0.603,3.175,3.181,5.722,6.36,6.297c1.408,0.254,2.765,0.139,3.991-0.264c0.847-0.279,1.776,0.029,2.335,0.724l0.4,0.498 c0.574,0.714,0.636,1.706,0.167,2.493c-1.177,1.973-1.964,4.202-2.258,6.587c-0.122,0.992-0.904,1.771-1.9,1.86l-1.899,0.17 c-0.65,0.058-1.266-0.211-1.732-0.667c-0.721-0.705-1.688-1.181-2.83-1.258c-1.783-0.12-3.526,0.87-4.309,2.477 c-1.295,2.657,0.195,5.671,2.899,6.372c1.949,0.505,3.916-0.356,4.926-1.979c0.363-0.584,1.017-0.926,1.702-0.987l1.631-0.146 c0.987-0.088,1.888,0.529,2.192,1.472c0.669,2.076,1.728,3.977,3.089,5.618c0.487,0.587,0.459,1.443-0.051,2.009 c-0.425,0.472-1.085,0.609-1.69,0.416c-0.687-0.219-1.434-0.307-2.209-0.233c-2.498,0.237-4.582,2.233-4.913,4.721 c-0.497,3.738,2.765,6.871,6.537,6.15c1.964-0.376,3.596-1.867,4.172-3.783c0.28-0.93,0.303-1.829,0.139-2.661 c-0.092-0.468,0.051-0.95,0.37-1.305l0.785-0.871c0.472-0.524,1.243-0.673,1.862-0.336c0.489,0.266,0.993,0.508,1.51,0.726 c1.104,0.464,1.704,1.618,1.598,2.81c-0.051,0.575-0.019,1.174,0.11,1.787c0.528,2.504,2.683,4.44,5.228,4.703 c3.6,0.372,6.638-2.443,6.638-5.967c0-0.384-0.037-0.76-0.107-1.124c-0.23-1.199,0.257-2.415,1.325-3.006 c0.984-0.545,1.909-1.185,2.761-1.909c0.525-0.446,1.222-0.7,1.891-0.531c1.365,0.345,1.95,1.682,1.526,2.836 c-0.376,1.023-0.502,2.167-0.301,3.36c0.463,2.746,2.736,4.939,5.495,5.313c4.215,0.571,7.784-2.901,7.378-7.088 C56.721,47.208,54.792,44.938,52.232,44.235z"
+
+ private val CELL_VIEW_COLOR_OPACITY = 1
+ private val CELL_DYING_FONT_COLOR = Color.DARKRED
+
+ private val CELL_BORDER_COLOR = Color.BLACK
+ private val CELL_BORDER =
+ new Border(new BorderStroke(CELL_BORDER_COLOR, BorderStrokeStyle.SOLID, CornerRadii.EMPTY, BorderWidths.DEFAULT))
+
+ private val CELL_MINIMUM_RADIUS_FOR_ENERGY = (25d, 20d)
+ private val CELL_MAX_RADIUS_FOR_ENERGY = (45d, 100d)
+
+ /**
+ * Implicit Conversion from Cell to CellView
+ *
* @return the ViewCell corresponding to the given Cell
*/
- implicit def cellToViewCell(cell: Cell): CellView = CellView(cell.position, coloringStrategy(cell), sizingStrategy(cell))
+ implicit def cellToView(cell: Cell): CellView =
+ CellView(cell.position, sizingStrategy(cell), coloringStrategy(cell),
+ cell.energy, energyTextColoringStrategy(cell), CELL_BORDER)
/**
* Default cell coloring strategy
@@ -36,14 +69,53 @@ object CellView {
* Color based on the username hash value
*/
val coloringStrategy: ColoringStrategy[Cell, Color] = (cell: Cell) => {
- val color = new ColorHash().rgb(cell.owner.username)
- new Color(color.red, color.green, color.blue)
+ implicit def colorFromRGB(userColor: Rgb): Color =
+ new Color(userColor.red / RGB_RANGE, userColor.green / RGB_RANGE, userColor.blue / RGB_RANGE, CELL_VIEW_COLOR_OPACITY)
+
+ val colorHash = new ColorHash()
+ val username = cell.owner.username
+ val userColor: Color = colorHash.rgb(username)
+ // if userName hash gives a color used in GUI take another color
+ if (Seq(GAME_DEFAULT_FONT_COLOR, CELL_DYING_FONT_COLOR, GAME_TIME_TEXT_COLOR).contains(userColor)) {
+ colorHash.rgb(username.substring(username.length / 2))
+ } else userColor
+ }
+
+ /**
+ * Default energy text coloring strategy
+ */
+ val energyTextColoringStrategy: ColoringStrategy[Cell, Color] = {
+ case cell: Cell if cell.energy < GameConstants.CELL_ENERGY_WHEN_BORN => CELL_DYING_FONT_COLOR
+ case _ => GAME_DEFAULT_FONT_COLOR
}
/**
- * The default sizing strategy
+ * The default sizing strategy; returns the radius that the cellView should have
+ */
+ val sizingStrategy: SizingStrategy[Cell, Double] = {
+ case cell: Cell if cell.energy <= CELL_MINIMUM_RADIUS_FOR_ENERGY._2 => CELL_MINIMUM_RADIUS_FOR_ENERGY._1
+ case cell: Cell if cell.energy >= CELL_MAX_RADIUS_FOR_ENERGY._2 => CELL_MAX_RADIUS_FOR_ENERGY._1
+ case cell: Cell => radiusBetweenMinimumAndMaximum(cell, CELL_MINIMUM_RADIUS_FOR_ENERGY, CELL_MAX_RADIUS_FOR_ENERGY)
+ }
+
+ /**
+ * A method to calculate the radius of the cell between maximum and minimum provided
*
- * Maps size to energy
+ * @param cell the cell to draw
+ * @param minimumRadiusAndEnergy the lower bound values
+ * @param maximumRadiusAndEnergy the upper bound values
+ * @return the sized cell radius
*/
- val sizingStrategy: SizingStrategy[Cell, Int] = _.energy.toInt
+ private def radiusBetweenMinimumAndMaximum(cell: Cell,
+ minimumRadiusAndEnergy: (Double, Double),
+ maximumRadiusAndEnergy: (Double, Double)): Double = {
+ val energyDeltaFromMinimum = cell.energy - minimumRadiusAndEnergy._2
+ val minimumPoint = Point(minimumRadiusAndEnergy._1.toInt, minimumRadiusAndEnergy._2.toInt)
+ val maximumPoint = Point(maximumRadiusAndEnergy._1.toInt, maximumRadiusAndEnergy._2.toInt)
+
+ // I use geometric utils to calculate a pair (a point) between minimum and maximum pairs, related to energy delta
+ // I'm interested in first component of the delta because it represents the radius
+ val radiusDelta = GeometricUtils.deltaXYFromFirstPoint(minimumPoint, maximumPoint, energyDeltaFromMinimum)._1
+ minimumRadiusAndEnergy._1 + radiusDelta
+ }
}
diff --git a/client/src/main/scala/it/cwmp/client/view/game/model/TentacleView.scala b/client/src/main/scala/it/cwmp/client/view/game/model/TentacleView.scala
index 52dc35fb..f657bbaf 100644
--- a/client/src/main/scala/it/cwmp/client/view/game/model/TentacleView.scala
+++ b/client/src/main/scala/it/cwmp/client/view/game/model/TentacleView.scala
@@ -1,19 +1,46 @@
package it.cwmp.client.view.game.model
-import java.awt.Color
import java.time.{Duration, Instant}
-import it.cwmp.client.model.game.impl.{GeometricUtils, Point, Tentacle}
+import it.cwmp.client.model.game.impl.{Point, Tentacle}
+import it.cwmp.client.model.game.{GeometricUtils, SizingStrategy}
import it.cwmp.client.view.game.ColoringStrategy
+import javafx.scene.paint.Color
+import scala.language.implicitConversions
/**
- * Tentacle View utilities
+ * A class representing the View counterpart of Tentacle
+ *
+ * @param startPoint the starting point for this tentacle View
+ * @param reachedPoint the arrive point for this tentacle View
+ * @param color the color of this tetacle
+ * @author Enrico Siboni
+ */
+case class TentacleView(startPoint: Point, reachedPoint: Point, color: Color, thickness: Double)
+
+/**
+ * Companion Object
*
* @author Enrico Siboni
*/
object TentacleView {
+ /**
+ * Provides default value for tentacle thickness
+ */
+ val TENTACLE_DEFAULT_THICKNESS = 3d
+
+ /**
+ * Conversion from Tentacle to TentacleView
+ *
+ * @param tentacle the tentacle to convert
+ * @param actualInstant the instant of the world
+ * @return the TentacleView corresponding to the give Tentacle
+ */
+ def tentacleToView(tentacle: Tentacle, actualInstant: Instant): TentacleView =
+ TentacleView(tentacle.from.position, reachedPoint(tentacle, actualInstant), coloringStrategy(tentacle), thicknessStrategy(tentacle))
+
/**
* Default coloring strategy for tentacles
*
@@ -22,6 +49,14 @@ object TentacleView {
val coloringStrategy: ColoringStrategy[Tentacle, Color] =
(tentacle: Tentacle) => CellView.coloringStrategy(tentacle.from)
+ /**
+ * Default tentacle thickness strategy
+ *
+ * Returns always same thickness
+ */
+ val thicknessStrategy: SizingStrategy[Tentacle, Double] =
+ (_: Tentacle) => TENTACLE_DEFAULT_THICKNESS
+
/**
* A method returning the Point reached actually by the tentacle
*
@@ -30,12 +65,17 @@ object TentacleView {
* @return the point that the tentacle has reached going towards enemy cell
*/
def reachedPoint(tentacle: Tentacle, actualInstant: Instant): Point = {
- if (tentacle.hasReachedDestinationFor(actualInstant) == Duration.ZERO) {
- val tentacleActualLength = tentacle.length(actualInstant)
+ val tentacleActualLength = tentacle.length(actualInstant)
+
+ if (tentacleActualLength == 0) {
+ tentacle.from.position
+ } else if (tentacle.hasReachedDestinationFor(actualInstant) == Duration.ZERO) {
val attackerPosition = tentacle.from.position
val deltaXYFromAttackerPosition = GeometricUtils.deltaXYFromFirstPoint(attackerPosition, tentacle.to.position, tentacleActualLength)
Point(attackerPosition.x + deltaXYFromAttackerPosition._1.toInt,
attackerPosition.y + deltaXYFromAttackerPosition._2.toInt)
- } else tentacle.to.position
+ } else {
+ tentacle.to.position
+ }
}
}
diff --git a/client/src/main/scala/it/cwmp/client/view/room/RoomFXController.scala b/client/src/main/scala/it/cwmp/client/view/room/RoomFXController.scala
index 7c8851de..74036e92 100644
--- a/client/src/main/scala/it/cwmp/client/view/room/RoomFXController.scala
+++ b/client/src/main/scala/it/cwmp/client/view/room/RoomFXController.scala
@@ -1,80 +1,140 @@
package it.cwmp.client.view.room
import it.cwmp.client.utils.{LayoutRes, StringRes}
-import it.cwmp.client.view.{FXAlerts, FXChecks, FXController, FXView}
-import javafx.application.Platform
+import it.cwmp.client.view._
+import it.cwmp.client.view.room.RoomFXController._
import javafx.fxml.FXML
+import javafx.scene.Node
import javafx.scene.control._
-import javafx.stage.Stage
+import javafx.scene.input.{Clipboard, ClipboardContent}
+import javafx.scene.layout.GridPane
-trait RoomFXStrategy {
- def onCreate(name: String, nPlayer: Int): Unit
- def onEnterPrivate(idRoom: String): Unit
- def onEnterPublic(nPlayer: Int): Unit
-}
-
-object RoomFXController {
- def apply(strategy: RoomFXStrategy): RoomFXController = {
- require(strategy != null)
- new RoomFXController(strategy)
- }
-}
-
-class RoomFXController(strategy: RoomFXStrategy) extends FXController with FXView with FXChecks with FXAlerts {
+/**
+ * Class that manages the Rooms View
+ *
+ * @param strategy the strategy to use on user actions
+ * @author contributor Enrico Siboni
+ */
+class RoomFXController(strategy: RoomStrategy) extends FXViewController with FXInputViewController with FXInputChecks {
protected val layout: String = LayoutRes.roomManagerLayout
protected val title: String = StringRes.roomManagerTitle
- protected val stage: Stage = new Stage
- protected val controller: FXController = this
-
- @FXML
- private var pr_cr_roomName: TextField = _
- @FXML
- private var pr_cr_numPlayer: Spinner[Integer] = _
- @FXML
- private var pr_et_roomID: TextField = _
- @FXML
- private var pub_et_numPlayer: Spinner[Integer] = _
-
- //creare una stanza privata
- @FXML
- private def onClickCreate(): Unit = {
- Platform.runLater(() => {
- for(
- name <- getTextFieldValue(pr_cr_roomName, "Il nome non può essere vuoto"); // TODO parametrize input
- nPlayer <- getSpinnerFieldValue(pr_cr_numPlayer, "Deve essere selezionato il numero di giocatori")
- ) yield strategy.onCreate(name, nPlayer)
- })
- }
+ protected val controller: FXViewController = this
- @FXML
- private def onClickReset(): Unit = {
- Platform.runLater(() => {
- resetFields()
- })
+ @FXML private var tabPane: TabPane = _
+ @FXML private var tfPrivateCreateRoomName: TextField = _
+ @FXML private var spPrivateCreateNumPlayer: Spinner[Int] = _
+ @FXML private var tfPrivateEnterRoomID: TextField = _
+ @FXML private var spPublicEnterNumPlayer: Spinner[Int] = _
+ @FXML private var btnPrivateCreate: Button = _
+ @FXML private var btnPrivateReset: Button = _
+ @FXML private var btnPrivateEnter: Button = _
+ @FXML private var btnPublicEnter: Button = _
+
+ override def showGUI(): Unit = {
+ super.showGUI()
+ // adds a listener to reset fields on tab change
+ tabPane.getSelectionModel.selectedItemProperty.addListener((_, _, _) => resetFields())
}
override def resetFields(): Unit = {
- pr_cr_roomName setText ""
- pr_cr_numPlayer getValueFactory() setValue 2
+ tfPrivateCreateRoomName setText ""
+ spPrivateCreateNumPlayer getValueFactory() setValue 2
+ tfPrivateEnterRoomID setText ""
+ spPublicEnterNumPlayer getValueFactory() setValue 2
}
- @FXML
- private def onClickEnter(): Unit = {
- Platform.runLater(() => {
- for(
- id_room <- getTextFieldValue(pr_et_roomID, "L'ID della stanza non può essere vuoto") // TODO parametrize input
- ) yield strategy.onEnterPrivate(id_room)
- })
+ override def disableViewComponents(): Unit = {
+ btnPrivateCreate.setDisable(true)
+ btnPrivateReset.setDisable(true)
+ btnPrivateEnter.setDisable(true)
+ btnPublicEnter.setDisable(true)
+ }
+
+ override def enableViewComponents(): Unit = {
+ btnPrivateCreate.setDisable(false)
+ btnPrivateReset.setDisable(false)
+ btnPrivateEnter.setDisable(false)
+ btnPublicEnter.setDisable(false)
}
- //Componenti tab stanze pubbliche
- @FXML
- private def onClickRoomPublic(): Unit = {
- Platform.runLater(() => {
- for(
- nPlayer <- getSpinnerFieldValue(pub_et_numPlayer, "Deve essere selezionato il numero di giocatori") // TODO parametrize input
- ) yield strategy.onEnterPublic(nPlayer)
+ @FXML private def onClickCreatePrivate(): Unit =
+ runOnUIThread(() => {
+ for (
+ roomName <- getTextFieldValue(tfPrivateCreateRoomName, ROOM_NAME_EMPTY_ERROR);
+ playersNumber <- getSpinnerFieldValue(spPrivateCreateNumPlayer, ROOM_PLAYERS_NUMBER_ERROR)
+ ) yield
+ strategy.onCreate(roomName, playersNumber)
+ })
+
+ @FXML private def onClickResetPrivate(): Unit = runOnUIThread { () => resetFields() }
+
+ @FXML private def onClickEnterPrivate(): Unit =
+ runOnUIThread(() => {
+ for (roomID <- getTextFieldValue(tfPrivateEnterRoomID, EMPTY_ROOM_ID_ERROR))
+ yield strategy.onEnterPrivate(roomID)
+ })
+
+
+ @FXML private def onClickEnterPublic(): Unit =
+ runOnUIThread(() => {
+ for (playersNumber <- getSpinnerFieldValue(spPublicEnterNumPlayer, NOT_SELECTED_PLAYERS_NUMBER))
+ yield strategy.onEnterPublic(playersNumber)
})
+
+
+ /**
+ * A method to show the token dialog to user
+ *
+ * @param roomToken the token that user should be able to copy
+ */
+ def showTokenDialog(roomToken: String): Unit =
+ runOnUIThread(() =>
+ showInfoWithContent(PRIVATE_ROOM_TOKEN_TITLE, PRIVATE_ROOM_TOKEN_MESSAGE,
+ createRoomTokenDialogContent(roomToken)))
+
+ /**
+ * Creates the dialog content whit selectable token
+ *
+ * @param roomToken the token to show
+ * @return the content of the dialog to show
+ */
+ private def createRoomTokenDialogContent(roomToken: String): Node = {
+ val gridPane = new GridPane()
+
+ val tokenTextField = new TextField(roomToken)
+ tokenTextField.setEditable(false)
+
+ val copyButton = new Button(COPY_BUTTON_TEXT)
+ copyButton.setOnAction(_ => {
+ val content = new ClipboardContent
+ content.putString(roomToken)
+ Clipboard.getSystemClipboard.setContent(content)
+ })
+
+ gridPane.add(tokenTextField, 0, 1)
+ gridPane.add(copyButton, 1, 1)
+ gridPane
}
}
+
+/**
+ * Companion object
+ */
+object RoomFXController {
+ def apply(strategy: RoomStrategy): RoomFXController = {
+ require(strategy != null, "The room strategy cannot be null")
+ new RoomFXController(strategy)
+ }
+
+ private val ROOM_NAME_EMPTY_ERROR = "Il nome della stanza non può essere vuoto"
+ private val ROOM_PLAYERS_NUMBER_ERROR = "Deve essere selezionato il numero di giocatori"
+ private val EMPTY_ROOM_ID_ERROR = "L'ID della stanza non può essere vuoto"
+ private val NOT_SELECTED_PLAYERS_NUMBER = "Deve essere selezionato il numero di giocatori"
+
+ private val PRIVATE_ROOM_TOKEN_TITLE = "Token per la stanza privata"
+
+ private val PRIVATE_ROOM_TOKEN_MESSAGE = "Questo è il token da usare per entrare nella stanza che hai creato"
+
+ private val COPY_BUTTON_TEXT = "Copy to clipboard"
+}
diff --git a/client/src/main/scala/it/cwmp/client/view/room/RoomStrategy.scala b/client/src/main/scala/it/cwmp/client/view/room/RoomStrategy.scala
new file mode 100644
index 00000000..f539ad6b
--- /dev/null
+++ b/client/src/main/scala/it/cwmp/client/view/room/RoomStrategy.scala
@@ -0,0 +1,31 @@
+package it.cwmp.client.view.room
+
+/**
+ * A strategy to know what to do when requested to create or enter a room
+ *
+ * @author Enrico Siboni
+ */
+trait RoomStrategy {
+
+ /**
+ * Invoked when user wants to create a room
+ *
+ * @param roomName the room name
+ * @param playersNumber the palyers number
+ */
+ def onCreate(roomName: String, playersNumber: Int): Unit
+
+ /**
+ * Invoked whe user wants to enter a private room
+ *
+ * @param roomID the room to enter
+ */
+ def onEnterPrivate(roomID: String): Unit
+
+ /**
+ * Invoked when the user wants to enter a public room
+ *
+ * @param playersNumber the public room players number
+ */
+ def onEnterPublic(playersNumber: Int): Unit
+}
diff --git a/client/src/main/scala/it/cwmp/client/view/room/RoomViewActor.scala b/client/src/main/scala/it/cwmp/client/view/room/RoomViewActor.scala
index cbf543db..3c6867ff 100644
--- a/client/src/main/scala/it/cwmp/client/view/room/RoomViewActor.scala
+++ b/client/src/main/scala/it/cwmp/client/view/room/RoomViewActor.scala
@@ -1,81 +1,85 @@
package it.cwmp.client.view.room
-import akka.actor.{Actor, ActorRef}
-import it.cwmp.client.controller.ClientControllerMessages
-import it.cwmp.client.view.AlertActor
-import javafx.application.Platform
-import javafx.embed.swing.JFXPanel
+import it.cwmp.client.controller.messages.RoomsRequests._
+import it.cwmp.client.view.FXServiceViewActor
+import it.cwmp.client.view.room.RoomViewActor._
/**
- * Questo oggetto contiene tutti i messaggi che questo attore può ricevere.
+ * This class represents the actor that manages the rooms
+ *
+ * @author Davide Borficchia
+ * @author contributor Enrico Siboni
*/
-object RoomViewMessages {
- /**
- * Questo messaggio rappresenta l'inizializzazione del controller che verrà poi utilizzato per le rispote che verrano inviate al mittente.
- * Quando ricevuto, inizializzo il controller.
- */
- case object InitController
- /**
- * Questo messaggio rappresenta la visualizzazione dell'interfaccia grafica.
- * Quando ricevuto, viene mostrata all'utente l'interfaccia grafica.
- */
- case object ShowGUI
- /**
- * Questo messaggio rappresenta la chiusura dell'interfaccia grafica.
- * Quando ricevuto, viene nascosta all'utente l'interfaccia grafica di selezione delle stanze.
- */
- case object HideGUI
-}
+case class RoomViewActor() extends FXServiceViewActor {
-object RoomViewActor {
- def apply(): RoomViewActor = new RoomViewActor()
+ protected var fxController: RoomFXController = _
+
+ private var roomEnteringMessage: RoomEnteringRequest with GUIRequest = _
+
+ override def preStart(): Unit = {
+ super.preStart()
+ runOnUIThread(() =>
+ fxController = RoomFXController(new RoomStrategy {
+ override def onCreate(roomName: String, playersNumber: Int): Unit = {
+ fxController disableViewComponents()
+ fxController showLoading CREATING_PRIVATE_ROOM_MESSAGE
+ controllerActor ! GUICreate(roomName, playersNumber)
+ }
+
+ override def onEnterPrivate(roomID: String): Unit = {
+ fxController disableViewComponents()
+ fxController showLoading(ENTERING_ROOM_MESSAGE, ENTERING_ROOM_TITLE)
+ roomEnteringMessage = GUIEnterPrivate(roomID)
+ controllerActor ! roomEnteringMessage
+ }
+
+ override def onEnterPublic(playersNumber: Int): Unit = {
+ fxController disableViewComponents()
+ fxController showLoading(ENTERING_ROOM_MESSAGE, ENTERING_ROOM_TITLE)
+ roomEnteringMessage = GUIEnterPublic(playersNumber)
+ controllerActor ! roomEnteringMessage
+ }
+ }))
+ }
+
+ override def receive: Receive = super.receive orElse {
+ case ShowToken(roomToken) => runOnUIThread(() => {
+ onServiceResponseReceived()
+ fxController showTokenDialog roomToken
+ })
+ case WaitingForOthers =>
+ fxController showCloseableLoading(WAITING_FOR_PARTICIPANTS_MESSAGE, WAITING_FOR_PARTICIPANTS_TITLE, () => {
+ controllerActor ! (roomEnteringMessage match {
+ case GUIEnterPrivate(roomID) => GUIExitPrivate(roomID)
+ case GUIEnterPublic(playersNumber) => GUIExitPublic(playersNumber)
+ })
+ })
+ }
}
/**
- * Questa classe rappresenta l'attore incaricato di visualizzare
- * l'interfaccia grafica della lobby di selezione delle stanze.
- *
- * @author Davide Borficchia
+ * Companion object, with actor messages
*/
-class RoomViewActor extends Actor with AlertActor {
- /**
- * roomFXController il controller che gestisce la view della lobby delle stanze
- */
- var fxController: RoomFXController = _
- /**
- * Questo è l'attore che ci invia i messaggi e quello al quale dobbiamo rispondere
- */
- var controllerActor: ActorRef = _
+object RoomViewActor {
+
+ private val CREATING_PRIVATE_ROOM_MESSAGE = "Stiamo creando la stanza privata"
+
+ private val ENTERING_ROOM_TITLE = "Entrata in stanza"
+ private val ENTERING_ROOM_MESSAGE = "Stai per entrare nella stanza scelta"
+
+ private val WAITING_FOR_PARTICIPANTS_TITLE = "In attesa di giocatori"
+ private val WAITING_FOR_PARTICIPANTS_MESSAGE = "Stiamo attendendo che altri giocatori si uniscano alla stessa stanza per raggiungere il numero stabilito"
/**
- * Questo metodo viene invocato alla creazione dell'attore. Non va mai chiamato direttamente!
- * Si occupa di creare il controller/view di javaFX per gestire il layout grafico delle stanze.
+ * Shows the room token on screen
+ *
+ * @param roomToken the private room token to spread among friends
*/
- override def preStart(): Unit = {
- super.preStart()
- //inizializzo il toolkit
- new JFXPanel
- Platform setImplicitExit false
- Platform runLater(() => {
- fxController = RoomFXController(new RoomFXStrategy {
- override def onCreate(name: String, nPlayer: Int): Unit =
- controllerActor ! ClientControllerMessages.RoomCreatePrivate(name, nPlayer)
- override def onEnterPrivate(idRoom: String): Unit =
- controllerActor ! ClientControllerMessages.RoomEnterPrivate(idRoom)
- override def onEnterPublic(nPlayer: Int): Unit =
- controllerActor ! ClientControllerMessages.RoomEnterPublic(nPlayer)
- })
- })
- }
+ case class ShowToken(roomToken: String)
/**
- * Questo metodo viene chiamato in automatico dall'esterno ogni volta che viene ricevuto un messaggio.
- * Non va mai chiamato direttamente!
- * I messaggi che questo attore è ingrado di ricevere sono raggruppati in [[RoomViewMessages]]
+ * Shows a loading while waiting for others participants
*/
- override def receive: Receive = alertBehaviour orElse {
- case RoomViewMessages.InitController => controllerActor = sender()
- case RoomViewMessages.ShowGUI => Platform runLater(() => fxController.showGUI())
- case RoomViewMessages.HideGUI => Platform runLater(() => fxController.hideGUI())
- }
-}
+ case object WaitingForOthers
+
+}
\ No newline at end of file
diff --git a/client/src/test/scala/it/cwmp/client/model/game/impl/GeometricUtilsTest.scala b/client/src/test/scala/it/cwmp/client/model/game/GeometricUtilsTest.scala
similarity index 65%
rename from client/src/test/scala/it/cwmp/client/model/game/impl/GeometricUtilsTest.scala
rename to client/src/test/scala/it/cwmp/client/model/game/GeometricUtilsTest.scala
index e1666539..2a1c2d7e 100644
--- a/client/src/test/scala/it/cwmp/client/model/game/impl/GeometricUtilsTest.scala
+++ b/client/src/test/scala/it/cwmp/client/model/game/GeometricUtilsTest.scala
@@ -1,11 +1,14 @@
-package it.cwmp.client.model.game.impl
+package it.cwmp.client.model.game
-import it.cwmp.client.model.game.impl.GeometricUtils.RichDouble
+import it.cwmp.client.model.game.GeometricUtils.RichDouble
+import it.cwmp.client.model.game.impl.Point
import org.scalatest.prop.PropertyChecks
import org.scalatest.{Matchers, PropSpec}
/**
* A test class for GeometricUtils
+ *
+ * @author Enrico Siboni
*/
class GeometricUtilsTest extends PropSpec with PropertyChecks with Matchers {
@@ -77,7 +80,7 @@ class GeometricUtilsTest extends PropSpec with PropertyChecks with Matchers {
assert(GeometricUtils.deltaXYFromFirstPoint(firstPoint, Point(-3, -4), 2.5) == (-1.5, -2))
forAnyTwoPoints { (point1, point2) =>
- forAll { (distance: Int) =>
+ forAll { (distance: Double) =>
whenever(distance > 0) {
val deltaXY = GeometricUtils.deltaXYFromFirstPoint(point1, point2, distance)
@@ -103,18 +106,75 @@ class GeometricUtilsTest extends PropSpec with PropertyChecks with Matchers {
}
}
+ property("Method pointDistanceFromStraightLine() should calculate the distance of a point from a straight line " +
+ "passing through two other points") {
+
+ val myPoint = Point(3, 4)
+ assert(GeometricUtils.pointDistanceFromStraightLine(myPoint, Point(5, 2), Point(8, 2)) == 2)
+
+ forAnyPoint { myPoint =>
+ forAnyTwoPoints { (point1, point2) =>
+
+ if (point1.x == point2.x) {
+ assert(GeometricUtils.pointDistanceFromStraightLine(myPoint, point1, point2) == Math.abs(point2.x - myPoint.x))
+ } else {
+ val angularCoefficient = GeometricUtils.angularCoefficient(point1, point2)
+ val distanceFromStraightLine =
+ Math.abs(myPoint.y.toDouble - (angularCoefficient * myPoint.x.toDouble + GeometricUtils.ordinateAtOrigin(point1, point2))) /
+ Math.sqrt(angularCoefficient.squared + 1)
+
+ assert(GeometricUtils.pointDistanceFromStraightLine(myPoint, point1, point2) == distanceFromStraightLine)
+ }
+ }
+ }
+
+ }
+
+ property("Method isWithinCircumference() should tell if a point is inside a circumference") {
+
+ val myPoint = Point(1, 1)
+ val centerPoint = Point(0, 0)
+ val myRadius = 1
+
+ intercept[IllegalArgumentException](GeometricUtils.isWithinCircumference(myPoint, centerPoint, -1))
+
+ assert(!GeometricUtils.isWithinCircumference(myPoint, centerPoint, myRadius))
+ assert(GeometricUtils.isWithinCircumference(Point(1, 0), centerPoint, myRadius))
+ assert(GeometricUtils.isWithinCircumference(myPoint, centerPoint, 2))
+
+ forAnyTwoPoints { (myPoint, centerPoint) =>
+ forAll { (radius: Double) =>
+ whenever(radius > 0) {
+ if ((myPoint.x - centerPoint.x).squared + (myPoint.y - centerPoint.y).squared <= radius.squared)
+ assert(GeometricUtils.isWithinCircumference(myPoint, centerPoint, radius))
+ else
+ assert(!GeometricUtils.isWithinCircumference(myPoint, centerPoint, radius))
+ }
+ }
+ }
+ }
+
/**
- * A property check that should be valid for any two points that satisfy the provided condition
+ * A property check that should be valid for any two points
*
* @param test the test to run
*/
private def forAnyTwoPoints(test: (Point, Point) => Unit): Unit = {
- forAll { (x1: Int, y1: Int, x2: Int, y2: Int) =>
- val firstPoint = Point(x1, y1)
- val secondPoint = Point(x2, y2)
-
- test(firstPoint, secondPoint)
+ forAnyPoint { firstPoint =>
+ forAnyPoint { secondPoint =>
+ test(firstPoint, secondPoint)
+ }
}
}
+ /**
+ * A property check that should be valid for any point
+ *
+ * @param test the test to run
+ */
+ private def forAnyPoint(test: Point => Unit): Unit = {
+ forAll { (x1: Int, y1: Int) =>
+ test(Point(x1, y1))
+ }
+ }
}
diff --git a/client/src/test/scala/it/cwmp/client/model/game/impl/CellTest.scala b/client/src/test/scala/it/cwmp/client/model/game/impl/CellTest.scala
index 2a87ab3e..e3ed437a 100644
--- a/client/src/test/scala/it/cwmp/client/model/game/impl/CellTest.scala
+++ b/client/src/test/scala/it/cwmp/client/model/game/impl/CellTest.scala
@@ -7,6 +7,8 @@ import org.scalatest.FunSpec
/**
* A test for cell class
+ *
+ * @author Enrico Siboni
*/
class CellTest extends FunSpec {
diff --git a/client/src/test/scala/it/cwmp/client/model/game/impl/CellWorldTest.scala b/client/src/test/scala/it/cwmp/client/model/game/impl/CellWorldTest.scala
index 12dbd1dc..3ebbc598 100644
--- a/client/src/test/scala/it/cwmp/client/model/game/impl/CellWorldTest.scala
+++ b/client/src/test/scala/it/cwmp/client/model/game/impl/CellWorldTest.scala
@@ -7,6 +7,8 @@ import org.scalatest.FunSpec
/**
* A test class for CellWorld
+ *
+ * @author Enrico Siboni
*/
class CellWorldTest extends FunSpec {
@@ -47,7 +49,7 @@ class CellWorldTest extends FunSpec {
assert(newWorld.attacks contains tentacle)
}
- it("can remove tntacle from world") {
+ it("can remove tentacle from world") {
val newWorld = myCellWorld -- tentacles.head
assert(!(newWorld.attacks contains tentacles.head))
diff --git a/client/src/test/scala/it/cwmp/client/model/game/impl/TentacleTest.scala b/client/src/test/scala/it/cwmp/client/model/game/impl/TentacleTest.scala
index dbf0a47b..4fb3f9eb 100644
--- a/client/src/test/scala/it/cwmp/client/model/game/impl/TentacleTest.scala
+++ b/client/src/test/scala/it/cwmp/client/model/game/impl/TentacleTest.scala
@@ -8,6 +8,8 @@ import org.scalatest.{BeforeAndAfterEach, FunSpec}
/**
* A test class for Tentacle
+ *
+ * @author Enrico Siboni
*/
class TentacleTest extends FunSpec with BeforeAndAfterEach {
diff --git a/core-testing/src/main/scala/it/cwmp/testing/FutureMatchers.scala b/core-testing/src/main/scala/it/cwmp/testing/FutureMatchers.scala
index c2f151e3..a02cb255 100644
--- a/core-testing/src/main/scala/it/cwmp/testing/FutureMatchers.scala
+++ b/core-testing/src/main/scala/it/cwmp/testing/FutureMatchers.scala
@@ -16,12 +16,22 @@ import scala.util.{Failure, Success}
trait FutureMatchers {
this: Matchers =>
+ /**
+ * An exception thrown by FutureMatchers when failed
+ */
case object FutureTestingException extends Exception
+ /**
+ * A class that enables to rapidly test futures
+ *
+ * @param future the future to test
+ * @tparam T the type of future result
+ */
implicit class RichFutureTesting[T](future: Future[T]) extends Matchers {
/**
* Asserts that the future has any sort of failure.
+ *
* @param executionContext the implicit execution context
* @return the future containing the result of the verification
*/
@@ -33,11 +43,12 @@ trait FutureMatchers {
/**
* Asserts that the future has the failure requested
+ *
* @param executionContext the implicit execution context
* @tparam A the type of failure that should be obtained
* @return the future containing the result of the verification
*/
- def shouldFailWith[A <: Exception: ClassTag](implicit executionContext: ExecutionContext): Future[Assertion] = future
+ def shouldFailWith[A <: Exception : ClassTag](implicit executionContext: ExecutionContext): Future[Assertion] = future
.transform {
case Failure(e) if classTag[A].runtimeClass.isInstance(e) => Success(succeed)
case _ => Failure(FutureTestingException)
@@ -45,10 +56,12 @@ trait FutureMatchers {
/**
* Asserts that the future succeed.
+ *
* @param executionContext the implicit execution context
* @return the future containing the result of the verification
*/
def shouldSucceed(implicit executionContext: ExecutionContext): Future[Assertion] = future
.map(_ => succeed)
}
+
}
diff --git a/core-testing/src/main/scala/it/cwmp/testing/HttpMatchers.scala b/core-testing/src/main/scala/it/cwmp/testing/HttpMatchers.scala
index 65b94e79..87e49aeb 100644
--- a/core-testing/src/main/scala/it/cwmp/testing/HttpMatchers.scala
+++ b/core-testing/src/main/scala/it/cwmp/testing/HttpMatchers.scala
@@ -25,23 +25,28 @@ trait HttpMatchers {
/**
* Asserts that the future succeed (the response is returned) and that the server responded with the
* specified status code.
- * @param statusCode the status code that the server should return
+ *
+ * @param statusCode the status code that the server should return
* @param executionContext the implicit execution context
* @return the future containing the result of the verification
*/
- def shouldAnswerWith(statusCode: Int)(implicit executionContext: ExecutionContext): Future[Assertion] = toCheck
- .map(_.statusCode() shouldBe statusCode)
+ def shouldAnswerWith(statusCode: Int)
+ (implicit executionContext: ExecutionContext): Future[Assertion] =
+ toCheck.map(_.statusCode() shouldBe statusCode)
/**
* Asserts that the future succeed (the response is returned) and that the server responded with the
* specified status code and that the body respects the rule specified in the strategy.
- * @param statusCode the status code that the server should return
- * @param strategy the strategy that should be used to validate the body
+ *
+ * @param statusCode the status code that the server should return
+ * @param strategy the strategy that should be used to validate the body
* @param executionContext the implicit execution context
* @return the future containing the result of the verification
*/
- def shouldAnswerWith(statusCode: Int, strategy: Option[String] => Boolean)(implicit executionContext: ExecutionContext): Future[Assertion] = toCheck
+ def shouldAnswerWith(statusCode: Int, strategy: Option[String] => Boolean)
+ (implicit executionContext: ExecutionContext): Future[Assertion] = toCheck
.map(response => assert(response.statusCode() == statusCode && strategy(response.bodyAsString())))
}
+
}
diff --git a/core-testing/src/main/scala/it/cwmp/testing/VerticleBeforeAndAfterAll.scala b/core-testing/src/main/scala/it/cwmp/testing/VerticleBeforeAndAfterAll.scala
index e1744574..803c01b5 100644
--- a/core-testing/src/main/scala/it/cwmp/testing/VerticleBeforeAndAfterAll.scala
+++ b/core-testing/src/main/scala/it/cwmp/testing/VerticleBeforeAndAfterAll.scala
@@ -3,16 +3,20 @@ package it.cwmp.testing
import io.vertx.lang.scala.ScalaVerticle
import org.scalatest.BeforeAndAfterAll
+/**
+ * A trait that makes possible to deploy a bunch of verticles before all tests and un-deploy after all
+ */
trait VerticleBeforeAndAfterAll extends VerticleTest with BeforeAndAfterAll {
this: VertxTest =>
/**
* It contains the list of verticles to deploy before all the tests.
+ *
* @return a [[Traversable]] containing all the verticles to deploy
*/
protected def verticlesBeforeAll: Traversable[ScalaVerticle]
- override protected def beforeAll(): Unit = {
+ override protected def beforeAll(): Unit = {
super.beforeAll()
deployAll(verticlesBeforeAll)
}
diff --git a/core-testing/src/main/scala/it/cwmp/testing/VerticleBeforeAndAfterEach.scala b/core-testing/src/main/scala/it/cwmp/testing/VerticleBeforeAndAfterEach.scala
index c16eebde..5fa95c3d 100644
--- a/core-testing/src/main/scala/it/cwmp/testing/VerticleBeforeAndAfterEach.scala
+++ b/core-testing/src/main/scala/it/cwmp/testing/VerticleBeforeAndAfterEach.scala
@@ -3,16 +3,20 @@ package it.cwmp.testing
import io.vertx.lang.scala.ScalaVerticle
import org.scalatest.BeforeAndAfterEach
+/**
+ * A trait that makes possible to deploy a bunch of verticles before each test and un-deploy after each
+ */
trait VerticleBeforeAndAfterEach extends VerticleTest with BeforeAndAfterEach {
this: VertxTest =>
/**
* It contains the list of verticles to deploy before each test.
+ *
* @return a [[Traversable]] containing all the verticles to deploy
*/
protected def verticlesBeforeEach: Traversable[ScalaVerticle]
- override protected def beforeEach(): Unit = {
+ override protected def beforeEach(): Unit = {
super.beforeEach()
deployAll(verticlesBeforeEach)
}
diff --git a/core-testing/src/main/scala/it/cwmp/testing/VerticleTest.scala b/core-testing/src/main/scala/it/cwmp/testing/VerticleTest.scala
index 5d388e47..ce0321ab 100644
--- a/core-testing/src/main/scala/it/cwmp/testing/VerticleTest.scala
+++ b/core-testing/src/main/scala/it/cwmp/testing/VerticleTest.scala
@@ -8,7 +8,7 @@ import scala.util.{Failure, Success}
/**
* This class is automatically extended when using [[VerticleBeforeAndAfterEach]] or [[VerticleBeforeAndAfterAll]].
- * Here it is provided the structure for managing the deployment of the verticles and their undeploy at the end.
+ * Here it is provided the structure for managing the deployment of the verticles and their un-deploy at the end.
*
* @author Eugenio Pierfederici
*/
@@ -18,18 +18,19 @@ trait VerticleTest {
/**
* Once deployed, this set contains all the ids of the verticles just started.
*/
- private var deployementIds: Set[String] = _
+ private var deploymentIds: Set[String] = _
/**
* Deploys all the verticles passed as parameter. If any deploy fails or isn't deployed withing the
* requested time, it throws a [[RuntimeException]]
+ *
* @param verticles the list of verticle to deploy
- * @param atMost the maximum amount of time we can wait for the verticle to start. By default: 10000 milliseconds
+ * @param atMost the maximum amount of time we can wait for the verticle to start. By default: 10000 milliseconds
* @throws RuntimeException if any verticle can't be deployed
*/
protected def deployAll(verticles: Traversable[ScalaVerticle], atMost: Duration = 10000.millis): Unit = {
- deployementIds = Set()
- verticles.foreach(verticle => deployementIds = deployementIds + Await.result(vertx.deployVerticleFuture(verticle)
+ deploymentIds = Set()
+ verticles.foreach(verticle => deploymentIds = deploymentIds + Await.result(vertx.deployVerticleFuture(verticle)
.andThen {
case Success(d) => d
case Failure(t) => throw new RuntimeException(t)
@@ -39,11 +40,12 @@ trait VerticleTest {
/**
* Un-deploys all the verticles previously deployed. If any un-deploy fails or it isn't un-deployed withing the
* requested time, it throws a [[RuntimeException]]
+ *
* @param atMost the maximum amount of time we can wait for the verticle to stop. By default: 10000 milliseconds
* @throws RuntimeException if any verticle can't be un-deployed
*/
protected def undeployAll(atMost: Duration = 10000.millis): Unit =
- deployementIds.foreach(id => Await.result(vertx.undeployFuture(id)
+ deploymentIds.foreach(id => Await.result(vertx.undeployFuture(id)
.andThen {
case Success(d) => d
case Failure(t) => throw new RuntimeException(t)
diff --git a/core-testing/src/main/scala/it/cwmp/testing/VertxTest.scala b/core-testing/src/main/scala/it/cwmp/testing/VertxTest.scala
index 1dd4592a..5e87dbbb 100644
--- a/core-testing/src/main/scala/it/cwmp/testing/VertxTest.scala
+++ b/core-testing/src/main/scala/it/cwmp/testing/VertxTest.scala
@@ -8,5 +8,4 @@ import org.scalatest.AsyncFunSpec
*
* @author Enrico Siboni
*/
-abstract class VertxTest extends AsyncFunSpec with VertxInstance {
-}
+abstract class VertxTest extends AsyncFunSpec with VertxInstance
diff --git a/core/src/main/scala/it/cwmp/exceptions/HTTPException.scala b/core/src/main/scala/it/cwmp/exceptions/HTTPException.scala
index e2957ba5..5411bdbd 100644
--- a/core/src/main/scala/it/cwmp/exceptions/HTTPException.scala
+++ b/core/src/main/scala/it/cwmp/exceptions/HTTPException.scala
@@ -6,8 +6,8 @@ package it.cwmp.exceptions
* @param statusCode the HTTP code of error
* @param getMessage the error message
*/
-sealed case class HTTPException(statusCode: Int,
- override val getMessage: String = null) extends RuntimeException(getMessage) {
+//noinspection ScalaStyle
+sealed case class HTTPException(statusCode: Int, override val getMessage: String = null) extends RuntimeException(getMessage) {
/**
* @return optionally the message of this Exception
diff --git a/core/src/main/scala/it/cwmp/services/authentication/ServerParameters.scala b/core/src/main/scala/it/cwmp/services/authentication/ServerParameters.scala
index f67ffed1..17ad8836 100644
--- a/core/src/main/scala/it/cwmp/services/authentication/ServerParameters.scala
+++ b/core/src/main/scala/it/cwmp/services/authentication/ServerParameters.scala
@@ -1,5 +1,8 @@
package it.cwmp.services.authentication
+/**
+ * An object containing Authentication Service info
+ */
object ServerParameters {
val DEFAULT_PORT = 8666
diff --git a/core/src/main/scala/it/cwmp/services/roomreceiver/ServerParameters.scala b/core/src/main/scala/it/cwmp/services/roomreceiver/ServerParameters.scala
index beb6aa69..a567864f 100644
--- a/core/src/main/scala/it/cwmp/services/roomreceiver/ServerParameters.scala
+++ b/core/src/main/scala/it/cwmp/services/roomreceiver/ServerParameters.scala
@@ -1,5 +1,8 @@
package it.cwmp.services.roomreceiver
+/**
+ * An object containing RoomReceiver Service info
+ */
object ServerParameters {
def API_RECEIVE_PARTICIPANTS_URL(token: String) = s"/api/client/$token/room/participants"
diff --git a/core/src/main/scala/it/cwmp/services/rooms/RoomApiWrapperUtils.scala b/core/src/main/scala/it/cwmp/services/rooms/RoomApiWrapperUtils.scala
index 31fd527e..1a4946a4 100644
--- a/core/src/main/scala/it/cwmp/services/rooms/RoomApiWrapperUtils.scala
+++ b/core/src/main/scala/it/cwmp/services/rooms/RoomApiWrapperUtils.scala
@@ -3,21 +3,26 @@ package it.cwmp.services.rooms
import io.vertx.core.buffer.Buffer
import io.vertx.lang.scala.json.{Json, JsonArray, JsonObject}
import io.vertx.scala.ext.web.client.{HttpResponse, WebClient}
+import it.cwmp.model.Address.Converters._
import it.cwmp.model.{Address, Room}
import it.cwmp.services.rooms.ServerParameters._
import it.cwmp.utils.VertxClient
import scala.concurrent.Future
+/**
+ * A trait that contains utility methods for a RoomApiWrapper
+ */
trait RoomApiWrapperUtils {
this: VertxClient =>
/**
* Wrapper method to do REST HTTP call to RoomsService createPrivateRoom API
*/
- protected def createPrivateRoomRequest(roomName: String, neededPlayers: Int)(implicit webClient: WebClient, userToken: String): Future[HttpResponse[Buffer]] = {
- client.post(API_CREATE_PRIVATE_ROOM_URL).addAuthentication.sendJsonObjectFuture(roomForCreationJson(roomName, neededPlayers))
- }
+ protected def createPrivateRoomRequest(roomName: String, neededPlayers: Int)
+ (implicit webClient: WebClient, userToken: String): Future[HttpResponse[Buffer]] =
+ client.post(API_CREATE_PRIVATE_ROOM_URL).addAuthentication
+ .sendJsonObjectFuture(roomForCreationJson(roomName, neededPlayers))
/**
* Handle method to create the JSON to use in creation API
@@ -28,72 +33,60 @@ trait RoomApiWrapperUtils {
/**
* Wrapper method to do REST HTTP call to RoomsService enterPrivateRoom API
*/
- protected def enterPrivateRoomRequest(roomID: String,
- userAddress: Address,
- notificationAddress: Address)
- (implicit webClient: WebClient,
- userToken: String): Future[HttpResponse[Buffer]] = {
+ protected def enterPrivateRoomRequest(roomID: String, userAddress: Address, notificationAddress: Address)
+ (implicit webClient: WebClient, userToken: String): Future[HttpResponse[Buffer]] =
client.put(API_ENTER_PRIVATE_ROOM_URL).addAuthentication
- .setQueryParam(Room.FIELD_IDENTIFIER, roomID).sendJsonFuture(addressesForEnteringJson(userAddress, notificationAddress))
- }
+ .setQueryParam(Room.FIELD_IDENTIFIER, roomID)
+ .sendJsonFuture(addressesForEnteringJson(userAddress, notificationAddress))
/**
* Wrapper method to do REST HTTP call to RoomsService privateRoomInfo API
*/
- protected def privateRoomInfoRequest(roomID: String)(implicit webClient: WebClient, userToken: String): Future[HttpResponse[Buffer]] = {
+ protected def privateRoomInfoRequest(roomID: String)
+ (implicit webClient: WebClient, userToken: String): Future[HttpResponse[Buffer]] =
client.get(API_PRIVATE_ROOM_INFO_URL).addAuthentication
.setQueryParam(Room.FIELD_IDENTIFIER, roomID).sendFuture()
- }
/**
* Wrapper method to do REST HTTP call to RoomsService exitPrivateRoom API
*/
- protected def exitPrivateRoomRequest(roomID: String)(implicit webClient: WebClient, userToken: String): Future[HttpResponse[Buffer]] = {
+ protected def exitPrivateRoomRequest(roomID: String)
+ (implicit webClient: WebClient, userToken: String): Future[HttpResponse[Buffer]] =
client.delete(API_EXIT_PRIVATE_ROOM_URL).addAuthentication
.setQueryParam(Room.FIELD_IDENTIFIER, roomID).sendFuture()
- }
/**
* Wrapper method to do REST HTTP call to RoomsService listPublicRooms API
*/
- protected def listPublicRoomsRequest()(implicit webClient: WebClient, userToken: String): Future[HttpResponse[Buffer]] = {
- client.get(API_LIST_PUBLIC_ROOMS_URL).addAuthentication
- .sendFuture()
- }
+ protected def listPublicRoomsRequest()(implicit webClient: WebClient, userToken: String): Future[HttpResponse[Buffer]] =
+ client.get(API_LIST_PUBLIC_ROOMS_URL).addAuthentication.sendFuture()
/**
* Wrapper method to do REST HTTP call to RoomsService enterPublicRoom API
*/
- protected def enterPublicRoomRequest(playersNumber: Int,
- userAddress: Address,
- notificationAddress: Address)
- (implicit webClient: WebClient,
- userToken: String): Future[HttpResponse[Buffer]] = {
+ protected def enterPublicRoomRequest(playersNumber: Int, userAddress: Address, notificationAddress: Address)
+ (implicit webClient: WebClient, userToken: String): Future[HttpResponse[Buffer]] =
client.put(API_ENTER_PUBLIC_ROOM_URL).addAuthentication
- .setQueryParam(Room.FIELD_NEEDED_PLAYERS, playersNumber.toString).sendJsonFuture(addressesForEnteringJson(userAddress, notificationAddress))
- }
+ .setQueryParam(Room.FIELD_NEEDED_PLAYERS, playersNumber.toString)
+ .sendJsonFuture(addressesForEnteringJson(userAddress, notificationAddress))
/**
* Handle method to create the JSON to use in entering API
*/
- protected def addressesForEnteringJson(playerAddress: Address, notificationAddress: Address): JsonArray = {
- import Address.Converters._
- Json.arr(playerAddress.toJson, notificationAddress.toJson)
- }
+ protected def addressesForEnteringJson(playerAddress: Address, notificationAddress: Address): JsonArray =
+ Json arr(playerAddress.toJson, notificationAddress.toJson)
/**
* Wrapper method to do REST HTTP call to RoomsService publicRoomInfo API
*/
- protected def publicRoomInfoRequest(playersNumber: Int)(implicit webClient: WebClient, userToken: String): Future[HttpResponse[Buffer]] = {
+ protected def publicRoomInfoRequest(playersNumber: Int)(implicit webClient: WebClient, userToken: String): Future[HttpResponse[Buffer]] =
client.get(API_PUBLIC_ROOM_INFO_URL).addAuthentication
.setQueryParam(Room.FIELD_NEEDED_PLAYERS, playersNumber.toString).sendFuture()
- }
/**
* Wrapper method to do REST HTTP call to RoomsService exitPublicRoom API
*/
- protected def exitPublicRoomRequest(playersNumber: Int)(implicit webClient: WebClient, userToken: String): Future[HttpResponse[Buffer]] = {
+ protected def exitPublicRoomRequest(playersNumber: Int)(implicit webClient: WebClient, userToken: String): Future[HttpResponse[Buffer]] =
client.delete(API_EXIT_PUBLIC_ROOM_URL).addAuthentication
.setQueryParam(Room.FIELD_NEEDED_PLAYERS, playersNumber.toString).sendFuture()
- }
}
diff --git a/core/src/main/scala/it/cwmp/services/rooms/ServerParameters.scala b/core/src/main/scala/it/cwmp/services/rooms/ServerParameters.scala
index 066d4482..4eb4ae20 100644
--- a/core/src/main/scala/it/cwmp/services/rooms/ServerParameters.scala
+++ b/core/src/main/scala/it/cwmp/services/rooms/ServerParameters.scala
@@ -2,6 +2,9 @@ package it.cwmp.services.rooms
import it.cwmp.model.Room
+/**
+ * An object containing Rooms Service info
+ */
object ServerParameters {
val DEFAULT_PORT = 8667
diff --git a/core/src/main/scala/it/cwmp/services/wrapper/AuthenticationApiWrapper.scala b/core/src/main/scala/it/cwmp/services/wrapper/AuthenticationApiWrapper.scala
index f022c523..d67d246a 100644
--- a/core/src/main/scala/it/cwmp/services/wrapper/AuthenticationApiWrapper.scala
+++ b/core/src/main/scala/it/cwmp/services/wrapper/AuthenticationApiWrapper.scala
@@ -3,34 +3,43 @@ package it.cwmp.services.wrapper
import io.vertx.scala.ext.web.client.WebClientOptions
import it.cwmp.exceptions.HTTPException
import it.cwmp.model.User
+import it.cwmp.services.authentication.ServerParameters._
import it.cwmp.utils.{Validation, VertxClient, VertxInstance}
import scala.concurrent.Future
+/**
+ * A trait describing the API wrapper for authentication service
+ */
trait AuthenticationApiWrapper extends Validation[String, User] {
+ // TODO: doc
def signUp(username: String, password: String): Future[String]
def login(username: String, password: String): Future[String]
}
+/**
+ * Companion object
+ */
object AuthenticationApiWrapper {
val DEFAULT_HOST = "localhost"
- import it.cwmp.services.authentication.ServerParameters._
-
def apply(): AuthenticationApiWrapper =
AuthenticationApiWrapper(DEFAULT_HOST, DEFAULT_PORT)
+ def apply(host: String): AuthenticationApiWrapper =
+ AuthenticationApiWrapper(host, DEFAULT_PORT)
+
def apply(host: String, port: Int): AuthenticationApiWrapper =
new AuthenticationApiWrapperImpl(WebClientOptions()
.setDefaultHost(host)
.setDefaultPort(port))
- def apply(host: String): AuthenticationApiWrapper =
- AuthenticationApiWrapper(host, DEFAULT_PORT)
-
+ /**
+ * A default implementation class for Authentication API Wrapper
+ */
class AuthenticationApiWrapperImpl(override protected val clientOptions: WebClientOptions)
extends AuthenticationApiWrapper with VertxInstance with VertxClient {
@@ -48,9 +57,9 @@ object AuthenticationApiWrapper {
.expectStatus(200)
.map(_.bodyAsString().getOrElse(""))
- override def validate(token: String): Future[User] =
+ override def validate(authenticationHeader: String): Future[User] =
client.get(API_VALIDATE)
- .addAuthentication(token)
+ .addAuthenticationHeader(authenticationHeader)
.sendFuture()
.expectStatus(200)
.mapBody {
diff --git a/core/src/main/scala/it/cwmp/services/wrapper/RoomsApiWrapper.scala b/core/src/main/scala/it/cwmp/services/wrapper/RoomsApiWrapper.scala
index 55ac055e..869545ef 100644
--- a/core/src/main/scala/it/cwmp/services/wrapper/RoomsApiWrapper.scala
+++ b/core/src/main/scala/it/cwmp/services/wrapper/RoomsApiWrapper.scala
@@ -4,8 +4,10 @@ import io.vertx.core.buffer.Buffer
import io.vertx.lang.scala.json.Json
import io.vertx.scala.ext.web.client.{HttpResponse, WebClientOptions}
import it.cwmp.exceptions.HTTPException
+import it.cwmp.model.Room.Converters._
import it.cwmp.model.{Address, Room}
import it.cwmp.services.rooms.RoomApiWrapperUtils
+import it.cwmp.services.rooms.ServerParameters._
import it.cwmp.utils.{VertxClient, VertxInstance}
import scala.concurrent.Future
@@ -27,7 +29,8 @@ trait RoomsApiWrapper {
* @return the future containing the identifier of the created room,
* or fails if roomName is empty or playersNumber not correct
*/
- def createRoom(roomName: String, playersNumber: Int)(implicit userToken: String): Future[String]
+ def createRoom(roomName: String, playersNumber: Int)
+ (implicit userToken: String): Future[String]
/**
* Enters a room
@@ -40,7 +43,8 @@ trait RoomsApiWrapper {
* or fails if roomID not provided, not present
* or user already inside a room, or room full
*/
- def enterRoom(roomID: String, userAddress: Address, notificationAddress: Address)(implicit userToken: String): Future[Unit]
+ def enterRoom(roomID: String, userAddress: Address, notificationAddress: Address)
+ (implicit userToken: String): Future[Unit]
/**
* Retrieves room information
@@ -50,7 +54,8 @@ trait RoomsApiWrapper {
* @return the future that completes when the room information is available,
* or fails if room id not provided or not present
*/
- def roomInfo(roomID: String)(implicit userToken: String): Future[Room]
+ def roomInfo(roomID: String)
+ (implicit userToken: String): Future[Room]
/**
* Exits a room
@@ -61,7 +66,8 @@ trait RoomsApiWrapper {
* or fails if roomId is not provided or not present
* or user is not inside that room
*/
- def exitRoom(roomID: String)(implicit userToken: String): Future[Unit]
+ def exitRoom(roomID: String)
+ (implicit userToken: String): Future[Unit]
/**
* retrieves a list of available public rooms
@@ -82,7 +88,8 @@ trait RoomsApiWrapper {
* or fails if players number is not correct,
* or user already inside a room
*/
- def enterPublicRoom(playersNumber: Int, userAddress: Address, notificationAddress: Address)(implicit userToken: String): Future[Unit]
+ def enterPublicRoom(playersNumber: Int, userAddress: Address, notificationAddress: Address)
+ (implicit userToken: String): Future[Unit]
/**
* Retrieves information about a public room with specific number of players
@@ -92,7 +99,8 @@ trait RoomsApiWrapper {
* @return the future that completes when the information is available,
* or fails if players number not correct
*/
- def publicRoomInfo(playersNumber: Int)(implicit userToken: String): Future[Room]
+ def publicRoomInfo(playersNumber: Int)
+ (implicit userToken: String): Future[Room]
/**
* Exits a public room
@@ -103,7 +111,8 @@ trait RoomsApiWrapper {
* or fails if players number is not correct,
* or user not inside that room
*/
- def exitPublicRoom(playersNumber: Int)(implicit userToken: String): Future[Unit]
+ def exitPublicRoom(playersNumber: Int)
+ (implicit userToken: String): Future[Unit]
}
/**
@@ -115,8 +124,6 @@ object RoomsApiWrapper {
val DEFAULT_HOST = "localhost"
- import it.cwmp.services.rooms.ServerParameters._
-
def apply(): RoomsApiWrapper = RoomsApiWrapper(DEFAULT_HOST, DEFAULT_PORT)
def apply(host: String): RoomsApiWrapper = RoomsApiWrapper(host, DEFAULT_PORT)
@@ -134,64 +141,65 @@ object RoomsApiWrapper {
private class RoomsApiWrapperDefault(override protected val clientOptions: WebClientOptions)
extends RoomsApiWrapper with RoomApiWrapperUtils with VertxInstance with VertxClient {
- override def createRoom(roomName: String, playersNumber: Int)(implicit userToken: String): Future[String] =
+ override def createRoom(roomName: String, playersNumber: Int)
+ (implicit userToken: String): Future[String] =
createPrivateRoomRequest(roomName, playersNumber)
.flatMap(implicit response => handleResponse(Future.successful(response.bodyAsString().get), 201))
- override def enterRoom(roomID: String, userAddress: Address, notificationAddress: Address)(implicit userToken: String): Future[Unit] =
+ override def enterRoom(roomID: String, userAddress: Address, notificationAddress: Address)
+ (implicit userToken: String): Future[Unit] =
enterPrivateRoomRequest(roomID, userAddress, notificationAddress)
- .flatMap(implicit response => handleResponse(Future.successful(Unit), 200))
+ .flatMap(implicit response => handleResponse(Future.successful(()), 200))
- override def roomInfo(roomID: String)(implicit userToken: String): Future[Room] =
+ override def roomInfo(roomID: String)
+ (implicit userToken: String): Future[Room] =
privateRoomInfoRequest(roomID)
- .flatMap(implicit response => handleResponse({
- import Room.Converters._
- Future.successful(Json.fromObjectString(response.bodyAsString().get).toRoom)
- }, 200))
+ .flatMap(implicit response =>
+ handleResponse(Future.successful(Json.fromObjectString(response.bodyAsString().get).toRoom), 200))
- override def exitRoom(roomID: String)(implicit userToken: String): Future[Unit] =
+ override def exitRoom(roomID: String)
+ (implicit userToken: String): Future[Unit] =
exitPrivateRoomRequest(roomID)
- .flatMap(implicit response => handleResponse(Future.successful(Unit), 200))
+ .flatMap(implicit response => handleResponse(Future.successful(()), 200))
override def listPublicRooms()(implicit userToken: String): Future[Seq[Room]] =
listPublicRoomsRequest()
.flatMap(implicit response => handleResponse({
Future.successful {
- import Room.Converters._
Json.fromArrayString(response.bodyAsString().get)
.stream().toArray().toSeq.map(_.toString)
.map(jsonString => Json.fromObjectString(jsonString).toRoom)
}
}, 200))
+ override def enterPublicRoom(playersNumber: Int, userAddress: Address, notificationAddress: Address)
+ (implicit userToken: String): Future[Unit] =
+ enterPublicRoomRequest(playersNumber, userAddress, notificationAddress)
+ .flatMap(implicit response => handleResponse(Future.successful(()), 200))
+
+ override def publicRoomInfo(playersNumber: Int)
+ (implicit userToken: String): Future[Room] =
+ publicRoomInfoRequest(playersNumber)
+ .flatMap(implicit response =>
+ handleResponse(Future.successful(Json.fromObjectString(response.bodyAsString().get).toRoom), 200))
+
+ override def exitPublicRoom(playersNumber: Int)
+ (implicit userToken: String): Future[Unit] =
+ exitPublicRoomRequest(playersNumber)
+ .flatMap(implicit response => handleResponse(Future.successful(()), 200))
+
/**
* Utility method to handle the Service response
*/
- private def handleResponse[T](onSuccessFuture: => Future[T], successHttpCodes: Int*)(implicit response: HttpResponse[Buffer]) =
+ private def handleResponse[T](onSuccessFuture: => Future[T], successHttpCodes: Int*)
+ (implicit response: HttpResponse[Buffer]) =
successHttpCodes find (_ == response.statusCode) match {
case Some(_) => onSuccessFuture
case None => response.bodyAsString match {
- case Some(body) =>
- Future.failed(HTTPException(response.statusCode, body))
- case None =>
- Future.failed(HTTPException(response.statusCode))
+ case Some(body) => Future.failed(HTTPException(response.statusCode, body))
+ case None => Future.failed(HTTPException(response.statusCode))
}
}
-
- override def enterPublicRoom(playersNumber: Int, userAddress: Address, notificationAddress: Address)(implicit userToken: String): Future[Unit] =
- enterPublicRoomRequest(playersNumber, userAddress, notificationAddress)
- .flatMap(implicit response => handleResponse(Future.successful(Unit), 200))
-
- override def publicRoomInfo(playersNumber: Int)(implicit userToken: String): Future[Room] =
- publicRoomInfoRequest(playersNumber)
- .flatMap(implicit response => handleResponse({
- import Room.Converters._
- Future.successful(Json.fromObjectString(response.bodyAsString().get).toRoom)
- }, 200))
-
- override def exitPublicRoom(playersNumber: Int)(implicit userToken: String): Future[Unit] =
- exitPublicRoomRequest(playersNumber)
- .flatMap(implicit response => handleResponse(Future.successful(Unit), 200))
}
}
\ No newline at end of file
diff --git a/core/src/main/scala/it/cwmp/utils/AdvancedLogging.scala b/core/src/main/scala/it/cwmp/utils/AdvancedLogging.scala
index bbe62d4e..9c449ef4 100644
--- a/core/src/main/scala/it/cwmp/utils/AdvancedLogging.scala
+++ b/core/src/main/scala/it/cwmp/utils/AdvancedLogging.scala
@@ -26,7 +26,8 @@ trait AdvancedLogging extends Logging {
* @param executionContext the implicit execution context on which to execute the operation.
* @return The future itself.
*/
- def logSuccessInfo(message: String)(implicit executionContext: ExecutionContext): Future[T] =
+ def logSuccessInfo(message: String)
+ (implicit executionContext: ExecutionContext): Future[T] =
future.andThen {
case Success(_) => log.info(message)
}
@@ -39,7 +40,8 @@ trait AdvancedLogging extends Logging {
* @param executionContext the implicit execution context on which to execute the operation.
* @return The future itself.
*/
- def logSuccessInfo(message: String, condition: T => Boolean)(implicit executionContext: ExecutionContext): Future[T] =
+ def logSuccessInfo(message: String, condition: T => Boolean)
+ (implicit executionContext: ExecutionContext): Future[T] =
future.andThen {
case Success(s) if condition(s) => log.info(message)
}
@@ -51,7 +53,8 @@ trait AdvancedLogging extends Logging {
* @param executionContext the implicit execution context on which to execute the operation.
* @return The future itself.
*/
- def logFailureInfo(message: String)(implicit executionContext: ExecutionContext): Future[T] =
+ def logFailureInfo(message: String)
+ (implicit executionContext: ExecutionContext): Future[T] =
future.andThen {
case Failure(e) => log.info(message, e)
}
@@ -64,7 +67,8 @@ trait AdvancedLogging extends Logging {
* @param executionContext the implicit execution context on which to execute the operation.
* @return The future itself.
*/
- def logFailureInfo[A <: Exception : ClassTag](message: String)(implicit executionContext: ExecutionContext): Future[T] =
+ def logFailureInfo[A <: Exception : ClassTag](message: String)
+ (implicit executionContext: ExecutionContext): Future[T] =
future.andThen {
case Failure(e) if classTag[A].runtimeClass.isInstance(e) => log.info(message, e)
}
diff --git a/core/src/main/scala/it/cwmp/utils/JsonFormat.scala b/core/src/main/scala/it/cwmp/utils/JsonFormat.scala
deleted file mode 100644
index c24fd343..00000000
--- a/core/src/main/scala/it/cwmp/utils/JsonFormat.scala
+++ /dev/null
@@ -1,8 +0,0 @@
-package it.cwmp.utils
-
-import io.vertx.lang.scala.json.JsonObject
-
-trait JsonFormat {
-
- def toJson: JsonObject
-}
diff --git a/core/src/main/scala/it/cwmp/utils/Utils.scala b/core/src/main/scala/it/cwmp/utils/Utils.scala
index 40e7d5fe..34069046 100644
--- a/core/src/main/scala/it/cwmp/utils/Utils.scala
+++ b/core/src/main/scala/it/cwmp/utils/Utils.scala
@@ -2,8 +2,19 @@ package it.cwmp.utils
import java.text.ParseException
+import scala.language.implicitConversions
+
+/**
+ * Generic utilities that can be used anywhere
+ */
object Utils {
+ /**
+ * Generates a random string of specified length
+ *
+ * @param length the length of the random String
+ * @return the random string
+ */
def randomString(length: Int): String = scala.util.Random.alphanumeric.take(length).mkString
/**
@@ -12,11 +23,19 @@ object Utils {
* @param string the string to test
* @return true if string is empty, false otherwise
*/
- def emptyString(string: String): Boolean = string == null || string.isEmpty
+ def emptyString(string: String): Boolean = string == null || string.trim.isEmpty
/**
* @return the ParseException filled with error string
*/
def parseException(context: String, errorMessage: String): ParseException =
new ParseException(s"$context: $errorMessage", 0)
+
+ /**
+ * Implicit conversion of strings to String options
+ *
+ * @param string the string to convert
+ * @return the option of that string
+ */
+ implicit def stringToOption(string: String): Option[String] = Option(string)
}
diff --git a/core/src/main/scala/it/cwmp/utils/VertxClient.scala b/core/src/main/scala/it/cwmp/utils/VertxClient.scala
index dea4db6b..6ad2b68d 100644
--- a/core/src/main/scala/it/cwmp/utils/VertxClient.scala
+++ b/core/src/main/scala/it/cwmp/utils/VertxClient.scala
@@ -1,9 +1,11 @@
package it.cwmp.utils
+import io.netty.handler.codec.http.HttpHeaderNames
import io.vertx.scala.ext.web.client.{HttpRequest, HttpResponse, WebClient, WebClientOptions}
import it.cwmp.exceptions.HTTPException
import scala.concurrent.Future
+import scala.language.implicitConversions
import scala.util.{Failure, Success}
/**
@@ -52,10 +54,7 @@ trait VertxClient {
/**
* An implicit class to provide the [[HttpRequest]] with some more useful utilities.
*/
-
- import io.netty.handler.codec.http.HttpHeaderNames
-
- implicit class richHttpRequest[T](request: HttpRequest[T]) {
+ implicit class RichHttpRequest[T](request: HttpRequest[T]) {
/**
* Simplified way to add the basic Authorization header with the provided username and password
@@ -66,7 +65,7 @@ trait VertxClient {
*/
def addAuthentication(username: String, password: String): HttpRequest[T] =
HttpUtils.buildBasicAuthentication(username, password)
- .map(request.putHeader(HttpHeaderNames.AUTHORIZATION.toString, _))
+ .map(putRequestAuthorizationHeader(request, _))
.getOrElse(request)
/**
@@ -77,14 +76,33 @@ trait VertxClient {
*/
def addAuthentication(implicit token: String): HttpRequest[T] =
HttpUtils.buildJwtAuthentication(token)
- .map(request.putHeader(HttpHeaderNames.AUTHORIZATION.toString, _))
+ .map(putRequestAuthorizationHeader(request, _))
.getOrElse(request)
+
+ /**
+ * Simplified way to add an authorization header as is in request
+ *
+ * @param authenticationHeader the authorization header to add as is
+ * @return the same [[HttpRequest]] enriched, with the authorization header
+ */
+ def addAuthenticationHeader(implicit authenticationHeader: String): HttpRequest[T] =
+ putRequestAuthorizationHeader(request, authenticationHeader)
+
+ /**
+ * Utility mthod to put the authorization header in request
+ *
+ * @param request the request
+ * @param authorizationHeader the authorization header to put
+ * @return the request with provided header
+ */
+ private def putRequestAuthorizationHeader(request: HttpRequest[T], authorizationHeader: String): HttpRequest[T] =
+ request.putHeader(HttpHeaderNames.AUTHORIZATION.toString, authorizationHeader)
}
/**
* An implicit class to provide the Future[HttpResponse]
with some more useful utilities.
*/
- implicit class richHttpResponse[T](response: Future[HttpResponse[T]]) {
+ implicit class RichHttpResponse[T](response: Future[HttpResponse[T]]) {
/**
* Causes the future to fail if the status code if different from one of those passed
@@ -95,7 +113,7 @@ trait VertxClient {
def expectStatus(statusCode: Int*): Future[HttpResponse[T]] = {
response.transform {
case s@Success(res) if statusCode.contains(res.statusCode()) => s
- case Success(res) => Failure(HTTPException(res.statusCode(), "Invalid response code"))
+ case Success(res) => Failure(HTTPException(res.statusCode(), s"Error code: ${res.statusCode()}"))
case f@Failure(_) => f
}
}
diff --git a/core/src/main/scala/it/cwmp/utils/VertxInstance.scala b/core/src/main/scala/it/cwmp/utils/VertxInstance.scala
index 70f50ae6..74e31a54 100644
--- a/core/src/main/scala/it/cwmp/utils/VertxInstance.scala
+++ b/core/src/main/scala/it/cwmp/utils/VertxInstance.scala
@@ -11,6 +11,8 @@ import io.vertx.scala.core.Vertx
* @author Eugenio Pierfederici
*/
trait VertxInstance {
- val vertx: Vertx = Vertx.vertx
- implicit val vertxExecutionContext: VertxExecutionContext = VertxExecutionContext(vertx.getOrCreateContext())
+ private val vertxContext = Vertx.currentContext().getOrElse(Vertx.vertx.getOrCreateContext())
+
+ val vertx: Vertx = vertxContext.owner()
+ implicit val vertxExecutionContext: VertxExecutionContext = VertxExecutionContext(vertxContext)
}
diff --git a/core/src/main/scala/it/cwmp/utils/VertxJDBC.scala b/core/src/main/scala/it/cwmp/utils/VertxJDBC.scala
index 5761bc82..3ce54adf 100644
--- a/core/src/main/scala/it/cwmp/utils/VertxJDBC.scala
+++ b/core/src/main/scala/it/cwmp/utils/VertxJDBC.scala
@@ -1,10 +1,12 @@
package it.cwmp.utils
+import io.vertx.lang.scala.json.JsonArray
import io.vertx.scala.ext.jdbc.JDBCClient
import io.vertx.scala.ext.sql.SQLConnection
import scala.collection.mutable.ListBuffer
import scala.concurrent.{ExecutionContext, Future}
+import scala.language.implicitConversions
import scala.util.Success
/**
@@ -21,7 +23,7 @@ trait VertxJDBC {
private val clientFuture: Future[JDBCClient] =
vertx.fileSystem.readFileFuture(configurationPath)
- .recoverWith { case _ => vertx.fileSystem.readFileFuture("database/jdbc_config.json") }
+ .recoverWith { case _ => vertx.fileSystem.readFileFuture(DEFAULT_CONFIG_PATH) }
.map(_.toJsonObject)
.map(JDBCClient.createShared(vertx, _))
@@ -53,6 +55,12 @@ trait VertxJDBC {
*/
protected def closeAllConnections(): Unit = connectionList.foreach(closeConnection(_))
+ /**
+ * Closes last opened connection through this client.
+ */
+ protected def closeLastOpenedConnection(): Unit =
+ if (connectionList.nonEmpty) closeConnection(connectionList.last)
+
/**
* This class decorates the future with some utils for the connection management.
*
@@ -68,9 +76,32 @@ trait VertxJDBC {
* @return the future itself
*/
def closeConnections(implicit executionContext: ExecutionContext): Future[F] =
- future.andThen {
- case _ => closeAllConnections()
- }
+ future.andThen { case _ => closeAllConnections() }
+
+ /**
+ * When the future reches this point, it closes the last opened connection till now in the client.
+ *
+ * @param executionContext the execution context in which to execute the operation
+ * @return the future itself
+ */
+ def closeLastConnection(implicit executionContext: ExecutionContext): Future[F] =
+ future.andThen { case _ => closeLastOpenedConnection() }
}
}
+
+
+/**
+ * Companion object
+ */
+object VertxJDBC {
+
+ /**
+ * Implicit conversion to jsonArray
+ *
+ * @return the converted data
+ */
+ implicit def stringsToJsonArray(arguments: Iterable[String]): JsonArray = {
+ arguments.foldLeft(new JsonArray)(_.add(_))
+ }
+}
diff --git a/core/src/main/scala/it/cwmp/utils/VertxServer.scala b/core/src/main/scala/it/cwmp/utils/VertxServer.scala
index 36e0a137..23e754f4 100644
--- a/core/src/main/scala/it/cwmp/utils/VertxServer.scala
+++ b/core/src/main/scala/it/cwmp/utils/VertxServer.scala
@@ -1,16 +1,18 @@
package it.cwmp.utils
+import io.netty.handler.codec.http.HttpHeaderNames
import io.vertx.lang.scala.ScalaVerticle
import io.vertx.scala.core.http.{HttpServer, HttpServerRequest, HttpServerResponse}
import io.vertx.scala.ext.web.{Router, RoutingContext}
import it.cwmp.exceptions.HTTPException
import it.cwmp.model.User
+import it.cwmp.utils.Utils.stringToOption
+import it.cwmp.utils.VertxServer.NO_AUTH_HEADER_IN_REQUEST_ERROR
import scala.concurrent.Future
import scala.util.{Failure, Success}
/**
- *
* This is a Utility trait that simplifies the creation and management of a vertx server.
*
* @author Eugenio Pierfederici
@@ -35,10 +37,23 @@ trait VertxServer extends ScalaVerticle {
}
}
+ /**
+ * Initializes the server
+ *
+ * @return the future that completes when the server is initialized
+ */
protected def initServer: Future[_] = Future.successful(())
+ /**
+ * @return the server port
+ */
protected def serverPort: Int
+ /**
+ * Initializes the server router for requests
+ *
+ * @param router the router to initialize
+ */
protected def initRouter(router: Router): Unit
/**
@@ -49,10 +64,10 @@ trait VertxServer extends ScalaVerticle {
* @param message the message to send back
*/
protected def sendResponse(httpCode: Int,
- message: String = null)
+ message: Option[String] = None)
(implicit routingContext: RoutingContext): Unit = {
response.setStatusCode(httpCode)
- Option(message) match {
+ message match {
case Some(messageString) =>
log.info(s"Sending $httpCode response to client with message: $messageString")
response.end(messageString)
@@ -74,9 +89,9 @@ trait VertxServer extends ScalaVerticle {
* @param routingContext the routing context on which to extract
* @return the extracted room name
*/
- protected def getRequestParameter(paramName: String)(implicit routingContext: RoutingContext): Option[String] = {
+ protected def getRequestParameter(paramName: String)
+ (implicit routingContext: RoutingContext): Option[String] =
request.getParam(paramName)
- }
/**
* Utility method to obtain the request object
@@ -89,23 +104,22 @@ trait VertxServer extends ScalaVerticle {
/**
* An implicit class to provide the [[HttpServerRequest]] with some more useful utilities.
*/
-
- import io.netty.handler.codec.http.HttpHeaderNames
-
- implicit class richHttpRequest(request: HttpServerRequest) {
+ implicit class RichHttpRequest(request: HttpServerRequest) {
/**
* This is a utility method that checks if the request is authenticated.
* For the check it uses the validation strategy given.
- * @param strategy the validation strategy to use.
+ *
+ * @param strategy the validation strategy to use.
* @param routingContext the routing context in which to execute the check.
* @return A future containing the authenticated user, if present, otherwise it fails with a [[HTTPException]]
*/
- def checkAuthentication(implicit strategy: Validation[String, User], routingContext: RoutingContext): Future[User] = getAuthentication match {
+ def checkAuthentication(implicit strategy: Validation[String, User],
+ routingContext: RoutingContext): Future[User] = getAuthenticationHeader match {
case None =>
- log.warn("No authorization header in request")
- Future.failed(HTTPException(400))
- case Some(authentication) => strategy.validate(authentication)
+ log.warn(NO_AUTH_HEADER_IN_REQUEST_ERROR)
+ Future.failed(HTTPException(400, NO_AUTH_HEADER_IN_REQUEST_ERROR))
+ case Some(authenticationHeader) => strategy.validate(authenticationHeader)
}
/**
@@ -113,11 +127,13 @@ trait VertxServer extends ScalaVerticle {
* For the check it uses the validation strategy given.
* If it fails with any [[HTTPException]], then it responds to the request
* with the status code and message specified in the exception.
- * @param strategy the validation strategy to use.
+ *
+ * @param strategy the validation strategy to use.
* @param routingContext the routing context in which to execute the check.
* @return A future containing the authenticated user, if present
*/
- def checkAuthenticationOrReject(implicit strategy: Validation[String, User], routingContext: RoutingContext): Future[User] =
+ def checkAuthenticationOrReject(implicit strategy: Validation[String, User],
+ routingContext: RoutingContext): Future[User] =
checkAuthentication(strategy, routingContext).recoverWith {
case HTTPException(statusCode, errorMessage) =>
sendResponse(statusCode, errorMessage)
@@ -125,10 +141,18 @@ trait VertxServer extends ScalaVerticle {
}
/**
- * Reads the authorization token in the request.
+ * Reads the authorization header (with token) in the request.
+ *
* @return An optional containing the header, if present. Otherwise None
*/
- def getAuthentication: Option[String] = request.getHeader(HttpHeaderNames.AUTHORIZATION.toString)
+ def getAuthenticationHeader: Option[String] = request.getHeader(HttpHeaderNames.AUTHORIZATION.toString)
}
+}
+
+/**
+ * Comppanion object
+ */
+object VertxServer {
+ private val NO_AUTH_HEADER_IN_REQUEST_ERROR = "No authorization header in request!"
}
\ No newline at end of file
diff --git a/core/src/test/scala/it/cwmp/services/wrapper/AuthenticationApiWrapperTest.scala b/core/src/test/scala/it/cwmp/services/wrapper/AuthenticationApiWrapperTest.scala
index 19a9ca42..a365f867 100644
--- a/core/src/test/scala/it/cwmp/services/wrapper/AuthenticationApiWrapperTest.scala
+++ b/core/src/test/scala/it/cwmp/services/wrapper/AuthenticationApiWrapperTest.scala
@@ -2,17 +2,25 @@ package it.cwmp.services.wrapper
import it.cwmp.exceptions.HTTPException
import it.cwmp.services.testing.authentication.AuthenticationWebServiceTesting
-import it.cwmp.utils.Utils
+import it.cwmp.utils.HttpUtils
import scala.concurrent.Promise
import scala.util.Failure
+/**
+ * A test class for AuthenticationApiWrapper
+ */
class AuthenticationApiWrapperTest extends AuthenticationWebServiceTesting {
private val auth = AuthenticationApiWrapper()
- override protected def singupTests(): Unit = {
- it("when right should succed") {
+ /**
+ * @return a new authentication header with a token
+ */
+ protected def nextHeader: String = HttpUtils.buildJwtAuthentication(super.nextToken).get
+
+ override protected def singUpTests(): Unit = {
+ it("when right should succeed") {
val username = nextUsername
val password = nextPassword
@@ -35,12 +43,12 @@ class AuthenticationApiWrapperTest extends AuthenticationWebServiceTesting {
}
}
- override protected def signoutTests(): Unit = {
+ override protected def signOutTests(): Unit = {
// TODO implementation
}
override protected def loginTests(): Unit = {
- it("when right should succed") {
+ it("when right should succeed") {
val username = nextUsername
val password = nextPassword
@@ -65,7 +73,7 @@ class AuthenticationApiWrapperTest extends AuthenticationWebServiceTesting {
it("when password is wrong should fail") {
val username = nextUsername
val password = nextPassword
- val passwordWrong = Utils.randomString(10)
+ val passwordWrong = nextPassword
val promiseResult: Promise[Unit] = Promise()
auth.signUp(username, password)
@@ -79,12 +87,12 @@ class AuthenticationApiWrapperTest extends AuthenticationWebServiceTesting {
}
override protected def validationTests(): Unit = {
- it("when right should succed") {
+ it("when right should succeed") {
val username = nextUsername
val password = nextPassword
auth.signUp(username, password)
- .flatMap(token => auth.validate(token))
+ .flatMap(token => auth.validate(HttpUtils.buildJwtAuthentication(token).get))
.map(user => assert(user.username == username))
}
@@ -100,11 +108,11 @@ class AuthenticationApiWrapperTest extends AuthenticationWebServiceTesting {
promiseResult.future.map(_ => succeed)
}
- it("when unauthorized token should fail") {
- val myToken = nextToken
+ it("when unauthorized header should fail") {
+ val myAuthHeader = nextHeader
val promiseResult: Promise[Unit] = Promise()
- auth.validate(myToken)
+ auth.validate(myAuthHeader)
.onComplete({
case Failure(HTTPException(statusCode, _)) if statusCode == 401 => promiseResult.success(Unit)
case _ => promiseResult.failure(new Exception)
diff --git a/core/src/test/scala/it/cwmp/services/wrapper/RoomsApiWrapperTest.scala b/core/src/test/scala/it/cwmp/services/wrapper/RoomsApiWrapperTest.scala
index 7853522c..490e29af 100644
--- a/core/src/test/scala/it/cwmp/services/wrapper/RoomsApiWrapperTest.scala
+++ b/core/src/test/scala/it/cwmp/services/wrapper/RoomsApiWrapperTest.scala
@@ -6,7 +6,6 @@ import it.cwmp.testing.rooms.RoomsWebServiceTesting
import org.scalatest.Assertion
import scala.concurrent.Future
-import scala.util.Success
/**
* Testing class for RoomsApiWrapper
@@ -17,11 +16,12 @@ class RoomsApiWrapperTest extends RoomsWebServiceTesting with FutureMatchers {
private val apiWrapper = RoomsApiWrapper()
+ //noinspection ScalaStyle
import apiWrapper._
override protected def privateRoomCreationTests(roomName: String, playersNumber: Int): Unit = {
it("should succeed returning roomID if parameters are correct") {
- createRoom(roomName, playersNumber) flatMap (_ should not be empty)
+ for (roomID <- createRoom(roomName, playersNumber)) yield roomID should not be empty
}
describe("should fail") {
@@ -36,42 +36,41 @@ class RoomsApiWrapperTest extends RoomsWebServiceTesting with FutureMatchers {
override protected def privateRoomEnteringTests(roomName: String, playersNumber: Int): Unit = {
it("should succeed when the input is valid and room non full") {
- createRoom(roomName, playersNumber) flatMap (roomID => enterRoom(roomID, participantList.head, notificationAddress)
- .flatMap(_ => cleanUpRoom(roomID, succeed)))
+ for (roomID <- createRoom(roomName, playersNumber);
+ _ <- enterRoom(roomID, participantList.head, notificationAddress);
+ _ <- cleanUpRoom(roomID)) yield succeed
}
describe("should fail") {
onWrongRoomID(enterRoom(_, participantList.head, notificationAddress))
it("if user is already inside a room") {
- var createdRoom = ""
- createRoom(roomName, playersNumber).andThen { case Success(id) => createdRoom = id }
- .flatMap(roomID => enterRoom(roomID, participantList.head, notificationAddress)
- .flatMap(_ => enterRoom(roomID, participantList.head, notificationAddress)))
- .shouldFailWith[HTTPException]
- .flatMap(cleanUpRoom(createdRoom, _))
+ for (roomID <- createRoom(roomName, playersNumber);
+ _ <- enterRoom(roomID, participantList.head, notificationAddress);
+ assertion <- enterRoom(roomID, participantList.head, notificationAddress).shouldFailWith[HTTPException];
+ _ <- cleanUpRoom(roomID)) yield assertion
}
it("if the room is full") {
val playersNumber = 2
- var createdRoom = ""
- createRoom(roomName, playersNumber).andThen { case Success(id) => createdRoom = id }
- .flatMap(roomID => enterRoom(roomID, participantList.head, notificationAddress)
- .flatMap(_ => enterRoom(roomID, participantList(1), notificationAddress)(tokenList(1)))
- .flatMap(_ => enterRoom(roomID, participantList(2), notificationAddress)(tokenList(2))))
- .shouldFailWith[HTTPException]
+ for (roomID <- createRoom(roomName, playersNumber);
+ _ <- enterRoom(roomID, participantList.head, notificationAddress);
+ _ <- enterRoom(roomID, participantList(1), notificationAddress)(tokenList(1));
+ assertion <- enterRoom(roomID, participantList(2), notificationAddress)(tokenList(2)).shouldFailWith[HTTPException])
+ yield assertion
}
}
}
override protected def privateRoomInfoRetrievalTests(roomName: String, playersNumber: Int): Unit = {
it("should succeed if roomId is correct") {
- createRoom(roomName, playersNumber) flatMap roomInfo flatMap (_ => succeed)
+ for (roomID <- createRoom(roomName, playersNumber); _ <- roomInfo(roomID)) yield succeed
}
it("should show user inside room") {
- createRoom(roomName, playersNumber) flatMap (roomID => enterRoom(roomID, participantList.head, notificationAddress)
- .flatMap(_ => roomInfo(roomID))
- .flatMap(_.participants should contain(participantList.head))
- .flatMap(cleanUpRoom(roomID, _)))
+ for (roomID <- createRoom(roomName, playersNumber);
+ _ <- enterRoom(roomID, participantList.head, notificationAddress);
+ roomInfo <- roomInfo(roomID);
+ assertion <- roomInfo.participants should contain(participantList.head);
+ _ <- cleanUpRoom(roomID)) yield assertion
}
describe("should fail") {
@@ -79,47 +78,33 @@ class RoomsApiWrapperTest extends RoomsWebServiceTesting with FutureMatchers {
}
}
- override protected def onWrongRoomID(apiCall: String => Future[_]): Unit = {
- it("if roomID is empty") {
- apiCall("").shouldFailWith[HTTPException]
- }
- it("if provided roomID is not present") {
- apiCall("1233124").shouldFailWith[HTTPException]
- }
- }
-
- /**
- * Cleans up the provided room, exiting the user with passed token
- */
- private def cleanUpRoom(roomID: String, assertion: Assertion)(implicit userToken: String) =
- exitRoom(roomID)(userToken) map (_ => assertion)
-
override protected def privateRoomExitingTests(roomName: String, playersNumber: Int): Unit = {
it("should succeed if roomID is correct and user inside") {
- createRoom(roomName, playersNumber)
- .flatMap(roomID => enterRoom(roomID, participantList.head, notificationAddress)
- .flatMap(_ => exitRoom(roomID))) flatMap (_ => succeed)
+ for (roomID <- createRoom(roomName, playersNumber);
+ _ <- enterRoom(roomID, participantList.head, notificationAddress);
+ _ <- exitRoom(roomID)) yield succeed
}
it("user should not be inside after it") {
- createRoom(roomName, playersNumber)
- .flatMap(roomID => enterRoom(roomID, participantList.head, notificationAddress)
- .flatMap(_ => exitRoom(roomID))
- .flatMap(_ => roomInfo(roomID))) flatMap (_.participants shouldNot contain(participantList.head))
+ for (roomID <- createRoom(roomName, playersNumber);
+ _ <- enterRoom(roomID, participantList.head, notificationAddress);
+ _ <- exitRoom(roomID);
+ roomInfo <- roomInfo(roomID);
+ assertion <- roomInfo.participants shouldNot contain(participantList.head)) yield assertion
}
describe("should fail") {
onWrongRoomID(exitRoom)
it("if user is not inside the room") {
- createRoom(roomName, playersNumber).flatMap(exitRoom).shouldFailWith[HTTPException]
+ for (roomID <- createRoom(roomName, playersNumber);
+ assertion <- exitRoom(roomID).shouldFailWith[HTTPException]) yield assertion
}
it("if user is inside another room") {
- var createdRoom = ""
- createRoom(roomName, playersNumber)
- .flatMap(roomID => createRoom(roomName, playersNumber).andThen { case Success(id) => createdRoom = id }
- .flatMap(otherRoomID => enterRoom(otherRoomID, participantList.head, notificationAddress)) flatMap (_ => exitRoom(roomID)))
- .shouldFailWith[HTTPException]
- .flatMap(cleanUpRoom(createdRoom, _))
+ for (roomID <- createRoom(roomName, playersNumber);
+ otherRoomID <- createRoom(roomName, playersNumber);
+ _ <- enterRoom(otherRoomID, participantList.head, notificationAddress);
+ assertion <- exitRoom(roomID).shouldFailWith[HTTPException];
+ _ <- cleanUpRoom(otherRoomID)) yield assertion
}
}
}
@@ -127,21 +112,22 @@ class RoomsApiWrapperTest extends RoomsWebServiceTesting with FutureMatchers {
override protected def publicRoomEnteringTests(playersNumber: Int): Unit = {
describe("should succeed") {
it("if room with such players number exists") {
- enterPublicRoom(playersNumber, participantList.head, notificationAddress) flatMap (_ => cleanUpRoom(playersNumber, succeed))
+ for (_ <- enterPublicRoom(playersNumber, participantList.head, notificationAddress);
+ _ <- cleanUpRoom(playersNumber)) yield succeed
}
it("and the user should be inside it") {
- enterPublicRoom(playersNumber, participantList.head, notificationAddress)
- .flatMap(_ => publicRoomInfo(playersNumber))
- .flatMap(_.participants should contain(participantList.head))
- .flatMap(cleanUpRoom(playersNumber, _))
+ for (_ <- enterPublicRoom(playersNumber, participantList.head, notificationAddress);
+ roomInfo <- publicRoomInfo(playersNumber);
+ assertion <- roomInfo.participants should contain(participantList.head);
+ _ <- cleanUpRoom(playersNumber)) yield assertion
}
it("even when the room was filled in past") {
- enterPublicRoom(2, participantList.head, notificationAddress)
- .flatMap(_ => enterPublicRoom(2, participantList(1), notificationAddress)(tokenList(1)))
- .flatMap(_ => enterPublicRoom(2, participantList(2), notificationAddress)(tokenList(2)))
- .flatMap(_ => publicRoomInfo(2))
- .flatMap(_.participants should contain only participantList(2))
- .flatMap(cleanUpRoom(2, _)(tokenList(2)))
+ for (_ <- enterPublicRoom(2, participantList.head, notificationAddress);
+ _ <- enterPublicRoom(2, participantList(1), notificationAddress)(tokenList(1));
+ _ <- enterPublicRoom(2, participantList(2), notificationAddress)(tokenList(2));
+ roomInfo <- publicRoomInfo(2);
+ assertion <- roomInfo.participants should contain only participantList(2);
+ _ <- cleanUpRoom(2)(tokenList(2))) yield assertion
}
}
@@ -149,32 +135,16 @@ class RoomsApiWrapperTest extends RoomsWebServiceTesting with FutureMatchers {
onWrongPlayersNumber(enterPublicRoom(_, participantList.head, notificationAddress))
it("if same user is already inside a room") {
- enterPublicRoom(2, participantList.head, notificationAddress)
- .flatMap(_ => enterPublicRoom(3, participantList.head, notificationAddress))
- .shouldFailWith[HTTPException]
- .flatMap(cleanUpRoom(2, _))
+ for (_ <- enterPublicRoom(2, participantList.head, notificationAddress);
+ assertion <- enterPublicRoom(3, participantList.head, notificationAddress).shouldFailWith[HTTPException];
+ _ <- cleanUpRoom(2)) yield assertion
}
}
}
- override protected def onWrongPlayersNumber(apiCall: Int => Future[_]): Unit = {
- it("if players number provided less than 2") {
- apiCall(0).shouldFailWith[HTTPException]
- }
- it("if room with such players number doesn't exist") {
- apiCall(20).shouldFailWith[HTTPException]
- }
- }
-
- /**
- * Cleans up the provided public room, exiting player with passed token
- */
- private def cleanUpRoom(playersNumber: Int, assertion: Assertion)(implicit userToken: String) =
- exitPublicRoom(playersNumber)(userToken) map (_ => assertion)
-
override protected def publicRoomListingTests(playersNumber: Int): Unit = {
it("should be non empty") {
- listPublicRooms() flatMap (_ should not be empty)
+ for (rooms <- listPublicRooms(); assertion <- rooms should not be empty) yield assertion
}
}
@@ -241,12 +211,13 @@ class RoomsApiWrapperTest extends RoomsWebServiceTesting with FutureMatchers {
override protected def publicRoomInfoRetrievalTests(playersNumber: Int): Unit = {
it("should succeed if provided playersNumber is correct") {
- publicRoomInfo(playersNumber) flatMap (_ => succeed)
+ for (_ <- publicRoomInfo(playersNumber)) yield succeed
}
it("should show entered players") {
- (enterPublicRoom(playersNumber, participantList.head, notificationAddress) flatMap (_ => publicRoomInfo(playersNumber)))
- .flatMap(_.participants should contain(participantList.head))
- .flatMap(cleanUpRoom(playersNumber, _))
+ for (_ <- enterPublicRoom(playersNumber, participantList.head, notificationAddress);
+ roomInfo <- publicRoomInfo(playersNumber);
+ assertion <- roomInfo.participants should contain(participantList.head);
+ _ <- cleanUpRoom(playersNumber)) yield assertion
}
describe("should fail") {
@@ -256,13 +227,14 @@ class RoomsApiWrapperTest extends RoomsWebServiceTesting with FutureMatchers {
override protected def publicRoomExitingTests(playersNumber: Int): Unit = {
it("should succeed if players number is correct and user is inside") {
- (enterPublicRoom(playersNumber, participantList.head, notificationAddress) flatMap (_ => exitPublicRoom(playersNumber))) flatMap (_ => succeed)
+ for (_ <- enterPublicRoom(playersNumber, participantList.head, notificationAddress);
+ _ <- exitPublicRoom(playersNumber)) yield succeed
}
it("user should not be inside after it") {
- enterPublicRoom(playersNumber, participantList.head, notificationAddress)
- .flatMap(_ => exitPublicRoom(playersNumber))
- .flatMap(_ => publicRoomInfo(playersNumber))
- .flatMap(_.participants shouldNot contain(participantList.head))
+ for (_ <- enterPublicRoom(playersNumber, participantList.head, notificationAddress);
+ _ <- exitPublicRoom(playersNumber);
+ roomInfo <- publicRoomInfo(playersNumber);
+ assertion <- roomInfo.participants shouldNot contain(participantList.head)) yield assertion
}
describe("should fail") {
@@ -272,11 +244,40 @@ class RoomsApiWrapperTest extends RoomsWebServiceTesting with FutureMatchers {
exitPublicRoom(playersNumber).shouldFailWith[HTTPException]
}
it("if user is inside another room") {
- enterPublicRoom(2, participantList.head, notificationAddress)
- .flatMap(_ => exitPublicRoom(3))
- .shouldFailWith[HTTPException]
- .flatMap(cleanUpRoom(2, _))
+ for (_ <- enterPublicRoom(2, participantList.head, notificationAddress);
+ assertion <- exitPublicRoom(3).shouldFailWith[HTTPException];
+ _ <- cleanUpRoom(2)) yield assertion
}
}
}
+
+ override protected def onWrongRoomID(apiCall: String => Future[_]): Unit = {
+ it("if roomID is empty") {
+ apiCall("").shouldFailWith[HTTPException]
+ }
+ it("if provided roomID is not present") {
+ apiCall("1233124").shouldFailWith[HTTPException]
+ }
+ }
+
+ override protected def onWrongPlayersNumber(apiCall: Int => Future[_]): Unit = {
+ it("if players number provided less than 2") {
+ apiCall(0).shouldFailWith[HTTPException]
+ }
+ it("if room with such players number doesn't exist") {
+ apiCall(TOO_BIG_PLAYERS_NUMBER).shouldFailWith[HTTPException]
+ }
+ }
+
+ /**
+ * Cleans up the provided room, exiting the user with passed token
+ */
+ override protected def cleanUpRoom(roomID: String)(implicit userToken: String): Future[Unit] =
+ for (_ <- exitRoom(roomID)(userToken)) yield ()
+
+ /**
+ * Cleans up the provided public room, exiting player with passed token
+ */
+ override protected def cleanUpRoom(playersNumber: Int)(implicit userToken: String): Future[Unit] =
+ for (_ <- exitPublicRoom(playersNumber)(userToken)) yield ()
}
diff --git a/core/src/test/scala/it/cwmp/utils/HttpUtilsTest.scala b/core/src/test/scala/it/cwmp/utils/HttpUtilsTest.scala
index ce1b72c8..be13604c 100644
--- a/core/src/test/scala/it/cwmp/utils/HttpUtilsTest.scala
+++ b/core/src/test/scala/it/cwmp/utils/HttpUtilsTest.scala
@@ -2,6 +2,9 @@ package it.cwmp.utils
import org.scalatest.FunSpec
+/**
+ * A test class for HttpUtils
+ */
class HttpUtilsTest extends FunSpec {
describe("Basic Authentication") {
diff --git a/doc/agile/Sprint5 backlog.md b/doc/agile/Sprint5 backlog.md
index 8aca83c5..c729034f 100644
--- a/doc/agile/Sprint5 backlog.md
+++ b/doc/agile/Sprint5 backlog.md
@@ -3,12 +3,12 @@
| Product Backlog Item | Item N. | Sprint Task | Person | Initial size estimate | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
|----------------------|---------|----------------------------------------------------------------------------|-----------|-----------------------|---|---|---|---|---|---|---|
|I client possono eseguire delle azioni sullo stato
-| | 1 | Predisposizione GameViewActor alla modifica del mondo (ricezione aggiunta o rimozione tentacolo)|| 10 |
-| | 2 | Binding azione sulla GUI -> messaggio all'attore | | 20 |
+| | 1 | Predisposizione GameViewActor alla modifica del mondo (ricezione aggiunta o rimozione tentacolo)|Enrico| 10 | 5 | 0 |
+| | 2 | Binding azione sulla GUI -> messaggio all'attore | Enrico | 20 | 20| 20| 10| 0 |
|Produzione GUI di gioco
-| | 3 | Scrivere energia della cellula sullla GUI | | 10 |
-| | 4 | Implementare dimesionamento della cellula in base all'energia | | 20 |
-| | 5 | Scrivere tempo trascorso nella schermata di gioco | | 10 |
+| | 3 | Scrivere energia della cellula sullla GUI | Davide | 10 | 10| 5 | 0 |
+| | 4 | Implementare dimesionamento della cellula in base all'energia | Enrico | 20 | 20| 20| 0 |
+| | 5 | Scrivere tempo trascorso nella schermata di gioco | Davide | 10 | 10| 10| 0 |
|Refactor codice con aggiunta logging
-| | 6 | Rifattorizzare storage autenticazione in DAO | | 50 |
-| | 7 | Rimuovere JSONFormat | | 1 |
\ No newline at end of file
+| | 6 | Rifattorizzare storage autenticazione in DAO | Davide, Enrico| 50 | 50| 25| 0 |
+| | 7 | Rimuovere JSONFormat | Enrico | 1 | 1 | 1 | 0 |
\ No newline at end of file
diff --git a/gradle.properties b/gradle.properties
index 3c582a94..7fffa737 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -1,6 +1,6 @@
projectGroup = it.unibo.cwmp
artifactId = cwmp
-projectVersion = 0.0.4
+projectVersion = 0.0.5
longName = CellWars-MultiPlayer
projectDescription = Cell Wars MultiPlayer - multiplayer game
diff --git a/services/authentication/src/main/scala/it/cwmp/services/authentication/AuthenticationLocalDAO.scala b/services/authentication/src/main/scala/it/cwmp/services/authentication/AuthenticationLocalDAO.scala
new file mode 100644
index 00000000..dc240f85
--- /dev/null
+++ b/services/authentication/src/main/scala/it/cwmp/services/authentication/AuthenticationLocalDAO.scala
@@ -0,0 +1,175 @@
+package it.cwmp.services.authentication
+
+import it.cwmp.services.authentication.AuthenticationLocalDAO._
+import it.cwmp.utils.VertxJDBC.stringsToJsonArray
+import it.cwmp.utils.{Logging, VertxInstance, VertxJDBC}
+
+import scala.concurrent._
+
+/**
+ * Trait che descrive l' Authentication Data Access Object
+ *
+ * @author Davide Borficchia
+ */
+trait AuthenticationDAO {
+ /**
+ * Registra un nuovo utente all'interno dello storage
+ *
+ * @param username username del nuovo utente
+ * @param password password del nuovo utente
+ * @return ritorna un Future vuoto
+ */
+ def signUpFuture(username: String, password: String): Future[Unit]
+
+ /**
+ * Fa sloggare un utente che ha precedentemente fatto login
+ *
+ * @param username username dell'utente che si vuole fare sloggare
+ * @return ritorna un Future vuoto
+ */
+ def signOutFuture(username: String): Future[Unit]
+
+ /**
+ * Permette di far loggare un utente precedentemente registrato nel sistema
+ *
+ * @param username username dell'utente
+ * @param password password dell'utente
+ * @return ritorna un Future vuoto
+ */
+ def loginFuture(username: String, password: String): Future[Unit]
+
+ /**
+ * controlla se un utente è presente nel sistema
+ *
+ * @param username utente da controllare
+ * @return ritorna un Future vuoto
+ */
+ def existsFuture(username: String): Future[Unit]
+}
+
+/**
+ * Wrapper per accedere allo storage Vertex locale per l'autenticazione
+ *
+ * @author Davide Borficchia
+ */
+case class AuthenticationLocalDAO(override val configurationPath: String = "authentication/database.json")
+ extends AuthenticationDAO with VertxInstance with VertxJDBC with Logging {
+
+ private var notInitialized = true
+
+ def initialize(): Future[Unit] = {
+ log.info("Initializing RoomLocalDAO...")
+ (for (
+ connection <- openConnection();
+ _ <- connection.executeFuture(createStorageTableSql)
+ ) yield {
+ notInitialized = false
+ }).closeConnections
+ }
+
+ override def signUpFuture(usernameP: String, passwordP: String): Future[Unit] = {
+ log.debug(s"signup() username:$usernameP, password:$passwordP")
+ (for (
+ // Controllo l'input
+ username <- Option(usernameP) if username.nonEmpty;
+ password <- Option(passwordP) if password.nonEmpty
+ ) yield {
+ (for (
+ // Eseguo operazioni sul db in maniera sequenziale
+ _ <- checkInitialization(notInitialized);
+ connection <- openConnection();
+ _ <- connection.updateWithParamsFuture(
+ insertNewUserSql, Seq(username, password, "SALT"))
+ ) yield ()).closeConnections
+ }).getOrElse(Future.failed(new IllegalArgumentException()))
+ }
+
+ override def signOutFuture(usernameP: String): Future[Unit] = {
+ log.debug(s"signoutFuture() username:$usernameP")
+ (for (
+ // Controllo l'input
+ username <- Option(usernameP) if username.nonEmpty
+ ) yield {
+ (for (
+ // Eseguo operazioni sul db in maniera sequenziale
+ _ <- checkInitialization(notInitialized);
+ connection <- openConnection();
+ result <- connection.updateWithParamsFuture(signOutUserSql, Seq(username)) if result.getUpdated > 0
+ ) yield ()).closeConnections
+ }).getOrElse(Future.failed(new IllegalArgumentException()))
+ }
+
+ override def loginFuture(usernameP: String, passwordP: String): Future[Unit] = {
+ log.debug(s"loginFuture() username:$usernameP, password:$passwordP")
+ (for (
+ // Controllo l'input
+ username <- Option(usernameP) if username.nonEmpty;
+ password <- Option(passwordP) if password.nonEmpty
+ ) yield {
+ (for (
+ // Eseguo operazioni sul db in maniera sequenziale
+ _ <- checkInitialization(notInitialized);
+ connection <- openConnection();
+ result <- connection.queryWithParamsFuture(loginUserSql, Seq(username, password)) if result.getResults.nonEmpty
+ ) yield ()).closeConnections
+ }).getOrElse(Future.failed(new IllegalArgumentException()))
+ }
+
+ override def existsFuture(usernameP: String): Future[Unit] = {
+ log.debug(s"existsFuture() username:$usernameP")
+ (for (
+ // Controllo l'input
+ username <- Option(usernameP) if username.nonEmpty
+ ) yield {
+ (for (
+ // Eseguo operazioni sul db in maniera sequenziale
+ _ <- checkInitialization(notInitialized);
+ connection <- openConnection();
+ result <- connection.queryWithParamsFuture(existFutureUserSql, Seq(username)) if result.getResults.nonEmpty
+ ) yield ()).closeConnections
+ }).getOrElse(Future.failed(new IllegalArgumentException()))
+ }
+}
+
+/**
+ * Companion Object
+ */
+object AuthenticationLocalDAO {
+
+ private val FIELD_AUTH_USERNAME = "auth_username"
+ private val FIELD_AUTH_PASSWORD = "auth_password"
+ private val FIELD_AUTH_SALT = "auth_salt"
+
+ /**
+ * Utility method per controlloare se il DAO è stato inizializzato
+ */
+ private def checkInitialization(notInitialized: Boolean): Future[Unit] = {
+ if (notInitialized) Future.failed(new IllegalStateException("Not initialized, you should first call initialize()"))
+ else Future.successful(())
+ }
+
+ private val createStorageTableSql =
+ s"""
+ CREATE TABLE IF NOT EXISTS authorization (
+ $FIELD_AUTH_USERNAME VARCHAR(45) NOT NULL,
+ $FIELD_AUTH_PASSWORD VARCHAR(45) NOT NULL,
+ $FIELD_AUTH_SALT CHAR(32) NOT NULL,
+ PRIMARY KEY ($FIELD_AUTH_USERNAME))
+ """
+
+ private val insertNewUserSql = "INSERT INTO authorization VALUES (?, ?, ?)"
+ private val signOutUserSql = "DELETE FROM authorization WHERE auth_username = ?"
+ private val loginUserSql =
+ s"""
+ SELECT *
+ FROM authorization
+ WHERE $FIELD_AUTH_USERNAME = ?
+ AND $FIELD_AUTH_PASSWORD = ?
+ """
+ private val existFutureUserSql =
+ s"""
+ SELECT *
+ FROM authorization
+ WHERE $FIELD_AUTH_USERNAME = ?
+ """
+}
\ No newline at end of file
diff --git a/services/authentication/src/main/scala/it/cwmp/services/authentication/AuthenticationServiceMain.scala b/services/authentication/src/main/scala/it/cwmp/services/authentication/AuthenticationServiceMain.scala
index b2816b42..9a4254e2 100644
--- a/services/authentication/src/main/scala/it/cwmp/services/authentication/AuthenticationServiceMain.scala
+++ b/services/authentication/src/main/scala/it/cwmp/services/authentication/AuthenticationServiceMain.scala
@@ -1,14 +1,13 @@
package it.cwmp.services.authentication
-import io.vertx.scala.core.Vertx
-import it.cwmp.utils.Logging
+import it.cwmp.utils.{Logging, VertxInstance}
/**
- * Hello class for server
+ * AuthenticationService entry-point
*/
-object AuthenticationServiceMain extends App with Logging {
+object AuthenticationServiceMain extends App with VertxInstance with Logging {
- Vertx.vertx().deployVerticle(new AuthenticationServiceVerticle)
+ vertx.deployVerticle(new AuthenticationServiceVerticle)
log.info("Deploying AuthenticationServiceVerticle... ")
}
diff --git a/services/authentication/src/main/scala/it/cwmp/services/authentication/AuthenticationServiceVerticle.scala b/services/authentication/src/main/scala/it/cwmp/services/authentication/AuthenticationServiceVerticle.scala
index bde9a54f..5f8553d6 100644
--- a/services/authentication/src/main/scala/it/cwmp/services/authentication/AuthenticationServiceVerticle.scala
+++ b/services/authentication/src/main/scala/it/cwmp/services/authentication/AuthenticationServiceVerticle.scala
@@ -2,42 +2,42 @@ package it.cwmp.services.authentication
import io.vertx.core.Handler
import io.vertx.scala.ext.web.{Router, RoutingContext}
-import it.cwmp.services.authentication.storage.StorageAsync
+import it.cwmp.services.authentication.ServerParameters._
+import it.cwmp.utils.Utils.stringToOption
import it.cwmp.utils.{HttpUtils, Logging, VertxServer}
import scala.concurrent.Future
import scala.util.{Failure, Success}
+/**
+ * Class that implements the Authentication micro-service
+ */
case class AuthenticationServiceVerticle() extends VertxServer with Logging {
- import it.cwmp.services.authentication.ServerParameters._
-
override protected val serverPort: Int = DEFAULT_PORT
- import it.cwmp.services.authentication.ServerParameters._
-
- private var storageFuture: Future[StorageAsync] = _
+ private var storageFuture: Future[AuthenticationDAO] = _
override protected def initRouter(router: Router): Unit = {
- router post API_SIGNUP handler handlerSignup
- router post API_SIGNOUT handler handlerSignout
+ router post API_SIGNUP handler handlerSignUp
+ router post API_SIGNOUT handler handlerSignOut
router get API_LOGIN handler handlerLogin
router get API_VALIDATE handler handlerValidation
}
override protected def initServer: Future[_] = {
- val storage = StorageAsync()
- storageFuture = storage.init().map(_ => storage)
+ val storage = AuthenticationLocalDAO()
+ storageFuture = storage.initialize().map(_ => storage)
storageFuture
}
- private def handlerSignup: Handler[RoutingContext] = implicit routingContext => {
+ private def handlerSignUp: Handler[RoutingContext] = implicit routingContext => {
log.debug("Received sign up request.")
(for (
- authorizationHeader <- request.getAuthentication;
+ authorizationHeader <- request.getAuthenticationHeader;
(username, password) <- HttpUtils.readBasicAuthentication(authorizationHeader)
) yield {
- storageFuture flatMap (_.signupFuture(username, password)) onComplete {
+ storageFuture flatMap (_.signUpFuture(username, password)) onComplete {
case Success(_) =>
log.info(s"User $username signed up.")
JwtUtils
@@ -48,15 +48,15 @@ case class AuthenticationServiceVerticle() extends VertxServer with Logging {
}) orElse Some(sendResponse(400))
}
- private def handlerSignout: Handler[RoutingContext] = implicit routingContext => {
+ private def handlerSignOut: Handler[RoutingContext] = implicit routingContext => {
log.debug("Received sign out request.")
(for (
- authorizationHeader <- request.getAuthentication;
+ authorizationHeader <- request.getAuthenticationHeader;
token <- HttpUtils.readJwtAuthentication(authorizationHeader);
username <- JwtUtils.decodeUsernameToken(token)
) yield {
// If every check pass, username contains the username contained in the token and we can check it exists
- storageFuture.map(_.signoutFuture(username).onComplete {
+ storageFuture.map(_.signOutFuture(username).onComplete {
case Success(_) =>
log.info(s"User $username signed out.")
sendResponse(202)
@@ -68,7 +68,7 @@ case class AuthenticationServiceVerticle() extends VertxServer with Logging {
private def handlerLogin: Handler[RoutingContext] = implicit routingContext => {
log.debug("Received login request.")
(for (
- authorizationHeader <- request.getAuthentication;
+ authorizationHeader <- request.getAuthenticationHeader;
(username, password) <- HttpUtils.readBasicAuthentication(authorizationHeader)
) yield {
storageFuture flatMap (_.loginFuture(username, password)) onComplete {
@@ -85,7 +85,7 @@ case class AuthenticationServiceVerticle() extends VertxServer with Logging {
private def handlerValidation: Handler[RoutingContext] = implicit routingContext => {
log.debug("Received token validation request.")
(for (
- authorizationHeader <- request.getAuthentication;
+ authorizationHeader <- request.getAuthenticationHeader;
token <- HttpUtils.readJwtAuthentication(authorizationHeader);
username <- JwtUtils.decodeUsernameToken(token)
) yield {
diff --git a/services/authentication/src/main/scala/it/cwmp/services/authentication/JwtUtils.scala b/services/authentication/src/main/scala/it/cwmp/services/authentication/JwtUtils.scala
index 9880a910..7b4099dc 100644
--- a/services/authentication/src/main/scala/it/cwmp/services/authentication/JwtUtils.scala
+++ b/services/authentication/src/main/scala/it/cwmp/services/authentication/JwtUtils.scala
@@ -5,6 +5,7 @@ import pdi.jwt.{Jwt, JwtAlgorithm, JwtClaim}
import scala.util.{Failure, Success}
+// TODO: add doc to all class methods
object JwtUtils {
private val secretKey = "secretKey"
diff --git a/services/authentication/src/main/scala/it/cwmp/services/authentication/storage/StorageAsync.scala b/services/authentication/src/main/scala/it/cwmp/services/authentication/storage/StorageAsync.scala
deleted file mode 100644
index 0c3ac756..00000000
--- a/services/authentication/src/main/scala/it/cwmp/services/authentication/storage/StorageAsync.scala
+++ /dev/null
@@ -1,112 +0,0 @@
-package it.cwmp.services.authentication.storage
-
-import io.vertx.core.json.JsonArray
-import it.cwmp.utils.{VertxInstance, VertxJDBC}
-
-import scala.concurrent._
-
-trait StorageAsync {
-
- def init(): Future[Unit]
-
- def signupFuture(username: String, password: String): Future[Unit]
-
- def signoutFuture(username: String): Future[Unit]
-
- def loginFuture(username: String, password: String): Future[Unit]
-
- def existsFuture(username: String): Future[Unit]
-}
-
-object StorageAsync {
-
- def apply(): StorageAsync = new StorageAsyncImpl()
-
- def apply(configurationPath: String): StorageAsync = new StorageAsyncImpl(configurationPath)
-
- class StorageAsyncImpl(override val configurationPath: String = "authentication/database.json") extends StorageAsync with VertxInstance with VertxJDBC {
-
- override def init(): Future[Unit] =
- (for (
- connection <- openConnection();
- result <- connection.executeFuture(
- """
- CREATE TABLE IF NOT EXISTS authorization (
- auth_username VARCHAR(45) NOT NULL,
- auth_password VARCHAR(45) NOT NULL,
- auth_salt CHAR(32) NOT NULL,
- PRIMARY KEY (auth_username))
- """)
- ) yield result).closeConnections
-
- override def signupFuture(usernameP: String, passwordP: String): Future[Unit] =
- (for (
- // Controllo l'input
- username <- Option(usernameP) if username.nonEmpty;
- password <- Option(passwordP) if password.nonEmpty
- ) yield {
- (for (
- // Eseguo operazioni sul db in maniera sequenziale
- connection <- openConnection();
- _ <- connection.updateWithParamsFuture(
- """
- INSERT INTO authorization values (?, ?, ?)
- """, new JsonArray().add(username).add(password).add("SALT"))
- ) yield ()).closeConnections
- }).getOrElse(Future.failed(new IllegalArgumentException()))
-
- override def signoutFuture(usernameP: String): Future[Unit] =
- (for (
- // Controllo l'input
- username <- Option(usernameP) if username.nonEmpty
- ) yield {
- (for (
- // Eseguo operazioni sul db in maniera sequenziale
- connection <- openConnection();
- result <- connection.updateWithParamsFuture(
- """
- DELETE FROM authorization WHERE auth_username = ?
- """, new JsonArray().add(username)) if result.getUpdated > 0
- ) yield ()).closeConnections
- }).getOrElse(Future.failed(new IllegalArgumentException()))
-
- override def loginFuture(usernameP: String, passwordP: String): Future[Unit] =
- (for (
- // Controllo l'input
- username <- Option(usernameP) if username.nonEmpty;
- password <- Option(passwordP) if password.nonEmpty
- ) yield {
- (for (
- // Eseguo operazioni sul db in maniera sequenziale
- connection <- openConnection();
- result <- connection.queryWithParamsFuture(
- """
- SELECT *
- FROM authorization
- WHERE auth_username = ?
- AND auth_password = ?
- """, new JsonArray().add(username).add(password)) if result.getResults.nonEmpty
- ) yield ()).closeConnections
- }).getOrElse(Future.failed(new IllegalArgumentException()))
-
-
- override def existsFuture(usernameP: String): Future[Unit] =
- (for (
- // Controllo l'input
- username <- Option(usernameP) if username.nonEmpty
- ) yield {
- (for (
- // Eseguo operazioni sul db in maniera sequenziale
- connection <- openConnection();
- result <- connection.queryWithParamsFuture(
- """
- SELECT *
- FROM authorization
- WHERE auth_username = ?
- """, new JsonArray().add(username)) if result.getResults.nonEmpty
- ) yield ()).closeConnections
- }).getOrElse(Future.failed(new IllegalArgumentException()))
-
- }
-
-}
\ No newline at end of file
diff --git a/services/authentication/src/test/resources/logback-test.xml b/services/authentication/src/test/resources/logback-test.xml
new file mode 100644
index 00000000..b211d12b
--- /dev/null
+++ b/services/authentication/src/test/resources/logback-test.xml
@@ -0,0 +1,10 @@
+
+
+
+ %msg%n
+
+
+
+
+
+
\ No newline at end of file
diff --git a/services/authentication/src/test/scala/it/cwmp/services/authentication/AuthenticationLocalDAOTest.scala b/services/authentication/src/test/scala/it/cwmp/services/authentication/AuthenticationLocalDAOTest.scala
new file mode 100644
index 00000000..72c0467d
--- /dev/null
+++ b/services/authentication/src/test/scala/it/cwmp/services/authentication/AuthenticationLocalDAOTest.scala
@@ -0,0 +1,111 @@
+package it.cwmp.services.authentication
+
+import it.cwmp.testing.{FutureMatchers, VertxTest}
+import it.cwmp.utils.Utils
+import org.scalatest.{BeforeAndAfterEach, Matchers}
+
+import scala.concurrent.Future
+
+/**
+ * Test per la classe AuthenticationLocalDAO
+ *
+ * @author Davide Borficchia
+ */
+class AuthenticationLocalDAOTest extends VertxTest with Matchers with FutureMatchers with BeforeAndAfterEach {
+
+ private var daoFuture: Future[AuthenticationDAO] = _
+ private var username: String = _
+ private var password: String = _
+
+ private val USERNAME_LENGTH = 10
+ private val PASSWORD_LENGTH = 15
+
+ override def beforeEach(): Unit = {
+ super.beforeEach()
+ val storage = AuthenticationLocalDAO()
+ daoFuture = storage.initialize().map(_ => storage)
+ username = Utils.randomString(USERNAME_LENGTH)
+ password = Utils.randomString(PASSWORD_LENGTH)
+ }
+
+ describe("AuthenticationLocalDAO") {
+ describe("sign up") {
+ describe("should fail with error") {
+ it("when username empty") {
+ for (dao <- daoFuture; assertion <- dao.signUpFuture("", password).shouldFail) yield assertion
+ }
+ it("when password empty") {
+ for (dao <- daoFuture; assertion <- dao.signUpFuture(username, "").shouldFail) yield assertion
+ }
+ it("when username already present") {
+ for (dao <- daoFuture;
+ _ <- dao.signUpFuture(username, password);
+ assertion <- dao.signUpFuture(username, password).shouldFail) yield assertion
+ }
+ }
+ describe("should succeed") {
+ it("when all right") {
+ for (dao <- daoFuture;
+ _ <- dao.signUpFuture(username, password);
+ assertion <- dao.signOutFuture(username).shouldSucceed) yield assertion
+ }
+ }
+ }
+
+ describe("sign out") {
+ describe("should fail with error") {
+ it("when username empty") {
+ for (dao <- daoFuture; assertion <- dao.signOutFuture("").shouldFail) yield assertion
+ }
+ it("when username doesn't exists") {
+ for (dao <- daoFuture; assertion <- dao.signOutFuture(username).shouldFail) yield assertion
+ }
+ }
+ describe("should succeed") {
+ it("when all right") {
+ for (dao <- daoFuture; _ <- dao.signUpFuture(username, password);
+ assertion <- dao.signOutFuture(username).shouldSucceed) yield assertion
+ }
+ }
+ }
+
+ describe("login") {
+ describe("should fail with error") {
+ it("when username empty") {
+ for (dao <- daoFuture; assertion <- dao.loginFuture("", password).shouldFail) yield assertion
+ }
+ it("when username doesn't exists") {
+ for (dao <- daoFuture; assertion <- dao.loginFuture(username, "").shouldFail) yield assertion
+ }
+ it("when password is wrong") {
+ val passwordWrong = Utils.randomString(PASSWORD_LENGTH)
+ for (dao <- daoFuture;
+ _ <- dao.signUpFuture(username, password);
+ assertion <- dao.loginFuture(username, passwordWrong).shouldFail) yield assertion
+ }
+ }
+ describe("should succeed") {
+ it("when all right") {
+ for (dao <- daoFuture; _ <- dao.signUpFuture(username, password);
+ _ <- dao.loginFuture(username, password);
+ assertion <- dao.signOutFuture(username).shouldSucceed) yield assertion
+ }
+ }
+ }
+ }
+
+ describe("The Helper shouldn't work") {
+ describe("if not initialized") {
+
+ it("sign up") {
+ AuthenticationLocalDAO().signUpFuture(username, password).shouldFailWith[IllegalStateException]
+ }
+ it("sign out") {
+ AuthenticationLocalDAO().signOutFuture(username).shouldFailWith[IllegalStateException]
+ }
+ it("login") {
+ AuthenticationLocalDAO().loginFuture(username, password).shouldFailWith[IllegalStateException]
+ }
+ }
+ }
+}
diff --git a/services/authentication/src/test/scala/it/cwmp/services/authentication/AuthenticationServiceVerticleTest.scala b/services/authentication/src/test/scala/it/cwmp/services/authentication/AuthenticationServiceVerticleTest.scala
index 84430b01..98fadf33 100644
--- a/services/authentication/src/test/scala/it/cwmp/services/authentication/AuthenticationServiceVerticleTest.scala
+++ b/services/authentication/src/test/scala/it/cwmp/services/authentication/AuthenticationServiceVerticleTest.scala
@@ -6,6 +6,9 @@ import it.cwmp.services.testing.authentication.AuthenticationWebServiceTesting
import it.cwmp.testing.{FutureMatchers, HttpMatchers}
import it.cwmp.utils.VertxClient
+/**
+ * A test class for Authentication service
+ */
class AuthenticationServiceVerticleTest extends AuthenticationWebServiceTesting
with HttpMatchers with FutureMatchers with VertxClient {
@@ -14,7 +17,7 @@ class AuthenticationServiceVerticleTest extends AuthenticationWebServiceTesting
.setDefaultPort(DEFAULT_PORT)
.setKeepAlive(false)
- override protected def singupTests(): Unit = {
+ override protected def singUpTests(): Unit = {
it("when right should succed") {
val username = nextUsername
val password = nextPassword
@@ -54,7 +57,7 @@ class AuthenticationServiceVerticleTest extends AuthenticationWebServiceTesting
}
}
- override protected def signoutTests(): Unit = {
+ override protected def signOutTests(): Unit = {
// TODO implement
}
diff --git a/services/authentication/src/test/scala/it/cwmp/services/authentication/JwtUtilsTest.scala b/services/authentication/src/test/scala/it/cwmp/services/authentication/JwtUtilsTest.scala
index d5b37ea4..cce34be3 100644
--- a/services/authentication/src/test/scala/it/cwmp/services/authentication/JwtUtilsTest.scala
+++ b/services/authentication/src/test/scala/it/cwmp/services/authentication/JwtUtilsTest.scala
@@ -3,6 +3,9 @@ package it.cwmp.services.authentication
import org.scalatest.FunSpec
import pdi.jwt.JwtClaim
+/**
+ * A test class for JwtUtils
+ */
class JwtUtilsTest extends FunSpec {
describe("Generic") {
diff --git a/services/authentication/src/test/scala/it/cwmp/services/authentication/storage/StorageAsyncTest.scala b/services/authentication/src/test/scala/it/cwmp/services/authentication/storage/StorageAsyncTest.scala
deleted file mode 100644
index 84f17419..00000000
--- a/services/authentication/src/test/scala/it/cwmp/services/authentication/storage/StorageAsyncTest.scala
+++ /dev/null
@@ -1,120 +0,0 @@
-package it.cwmp.services.authentication.storage
-
-import it.cwmp.testing.{FutureMatchers, VertxTest}
-import it.cwmp.utils.Utils
-import org.scalatest.{BeforeAndAfterEach, Matchers}
-
-import scala.concurrent.Future
-
-class StorageAsyncTest extends VertxTest with Matchers with FutureMatchers with BeforeAndAfterEach {
-
- var storageFuture: Future[StorageAsync] = _
-
- override def beforeEach(): Unit = {
- super.beforeEach()
- val storage = StorageAsync()
- storageFuture = storage.init().map(_ => storage)
- }
-
- describe("Storage manager") {
- describe("sign up") {
- describe("should fail with error") {
- it("when username empty") {
- val password = Utils.randomString(10)
- storageFuture
- .flatMap(storage => storage.signupFuture("", password))
- .shouldFail
- }
- it("when password empty") {
- val username = Utils.randomString(10)
- storageFuture
- .flatMap(storage => storage.signupFuture(username, ""))
- .shouldFail
- }
- it("when username already present") {
- val username = Utils.randomString(10)
- val password = Utils.randomString(10)
- storageFuture
- .flatMap(storage => storage.signupFuture(username, password).map(_ => storage))
- .flatMap(storage => storage.signupFuture(username, password))
- .shouldFail
- }
- }
- describe("should succeed") {
- it("when all right") {
- val username = Utils.randomString(10)
- val password = Utils.randomString(10)
- storageFuture
- .flatMap(storage => storage.signupFuture(username, password).map(_ => storage))
- .flatMap(storage => storage.signoutFuture(username))
- .shouldSucceed
- }
- }
- }
-
- describe("sign out") {
- describe("should fail with error") {
- it("when username empty") {
- storageFuture
- .flatMap(storage => storage.signoutFuture(""))
- .shouldFail
- }
- it("when username doesn't exists") {
- val username = Utils.randomString(10)
- storageFuture
- .flatMap(storage => storage.signoutFuture(username))
- .shouldFail
- }
- }
- describe("should succeed") {
- it("when all right") {
- val username = Utils.randomString(10)
- val password = Utils.randomString(10)
- storageFuture
- .flatMap(storage => storage.signupFuture(username, password).map(_ => storage))
- .flatMap(storage => storage.signoutFuture(username))
- .shouldSucceed
- }
- }
- }
-
- describe("login") {
- describe("should fail with error") {
- it("when username empty") {
- val password = Utils.randomString(10)
- storageFuture
- .flatMap(storage => storage.loginFuture("", password))
- .shouldFail
- }
- it("when username doesn't exists") {
- val username = Utils.randomString(10)
- storageFuture
- .flatMap(storage => storage.loginFuture(username, ""))
- .shouldFail
- }
- it("when password is wrong") {
- val username = Utils.randomString(10)
- val password = Utils.randomString(10)
- val passwordWrong = Utils.randomString(10)
- storageFuture
- .flatMap(storage => storage.signupFuture(username, password).map(_ => storage))
- .flatMap(storage => storage.loginFuture(username, passwordWrong).map(_ => storage))
- .flatMap(storage => storage.signoutFuture(username))
- .shouldFail
- }
- }
- describe("should succeed") {
- it("when all right") {
- val username = Utils.randomString(10)
- val password = Utils.randomString(10)
- storageFuture
- .flatMap(storage => storage.signupFuture(username, password).map(_ => storage))
- .flatMap(storage => storage.loginFuture(username, password).map(_ => storage))
- .flatMap(storage => storage.signoutFuture(username))
- .shouldSucceed
- }
- }
- }
- }
-
-}
diff --git a/services/authentication/src/test/scala/it/cwmp/services/testing/authentication/AuthenticationTesting.scala b/services/authentication/src/test/scala/it/cwmp/services/testing/authentication/AuthenticationTesting.scala
index fdd2f329..6dd4dd1b 100644
--- a/services/authentication/src/test/scala/it/cwmp/services/testing/authentication/AuthenticationTesting.scala
+++ b/services/authentication/src/test/scala/it/cwmp/services/testing/authentication/AuthenticationTesting.scala
@@ -1,34 +1,45 @@
package it.cwmp.services.testing.authentication
+import it.cwmp.services.testing.authentication.AuthenticationTesting.RANDOM_STRING_LENGTH
import it.cwmp.testing.VertxTest
import it.cwmp.utils.Utils
import org.scalatest.Matchers
+/**
+ * A base class for testing Authentication service
+ */
abstract class AuthenticationTesting extends VertxTest with Matchers {
- protected def nextUsername: String = Utils.randomString(10)
+ /**
+ * @return a new random username
+ */
+ protected def nextUsername: String = Utils.randomString(RANDOM_STRING_LENGTH)
- protected def nextPassword: String = Utils.randomString(10)
+ /**
+ * @return a new random password
+ */
+ protected def nextPassword: String = Utils.randomString(RANDOM_STRING_LENGTH)
private val tokens = Iterator.continually(List(
"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VybmFtZSI6InRpemlvIn0.f6eS98GeBmPau4O58NwQa_XRu3Opv6qWxYISWU78F68"
)).flatten
+ // TODO: add doc
protected def nextToken: String = tokens.next()
protected val invalidToken: String = "INVALID"
- protected def singupTests()
+ protected def singUpTests()
describe("Sign up") {
- singupTests()
+ singUpTests()
}
- protected def signoutTests()
+ protected def signOutTests()
describe("Sign out") {
- signoutTests()
+ signOutTests()
}
protected def loginTests()
@@ -43,3 +54,10 @@ abstract class AuthenticationTesting extends VertxTest with Matchers {
validationTests()
}
}
+
+/**
+ * Companion object
+ */
+object AuthenticationTesting {
+ private val RANDOM_STRING_LENGTH = 10
+}
\ No newline at end of file
diff --git a/services/authentication/src/test/scala/it/cwmp/services/testing/authentication/AuthenticationWebServiceTesting.scala b/services/authentication/src/test/scala/it/cwmp/services/testing/authentication/AuthenticationWebServiceTesting.scala
index 88aa6a54..42bd9c28 100644
--- a/services/authentication/src/test/scala/it/cwmp/services/testing/authentication/AuthenticationWebServiceTesting.scala
+++ b/services/authentication/src/test/scala/it/cwmp/services/testing/authentication/AuthenticationWebServiceTesting.scala
@@ -4,8 +4,10 @@ import io.vertx.lang.scala.ScalaVerticle
import it.cwmp.services.authentication.AuthenticationServiceVerticle
import it.cwmp.testing.VerticleBeforeAndAfterEach
+/**
+ * Abstract base class for testing authentication we service
+ */
abstract class AuthenticationWebServiceTesting extends AuthenticationTesting with VerticleBeforeAndAfterEach {
override protected val verticlesBeforeEach: List[ScalaVerticle] = List(AuthenticationServiceVerticle())
-
}
diff --git a/services/room-receiver/src/main/scala/it/cwmp/services/roomreceiver/RoomReceiverServiceVerticle.scala b/services/room-receiver/src/main/scala/it/cwmp/services/roomreceiver/RoomReceiverServiceVerticle.scala
index e22b2a43..17b7d476 100644
--- a/services/room-receiver/src/main/scala/it/cwmp/services/roomreceiver/RoomReceiverServiceVerticle.scala
+++ b/services/room-receiver/src/main/scala/it/cwmp/services/roomreceiver/RoomReceiverServiceVerticle.scala
@@ -5,8 +5,17 @@ import io.vertx.core.buffer.Buffer
import io.vertx.lang.scala.json.JsonObject
import io.vertx.scala.ext.web.{Router, RoutingContext}
import it.cwmp.model.Participant
+import it.cwmp.model.Participant.Converters._
+import it.cwmp.services.roomreceiver.ServerParameters._
+import it.cwmp.utils.Utils.stringToOption
import it.cwmp.utils.{Logging, VertxServer}
+/**
+ * A class implementing a one-time service provided by clients to receive room infromation
+ *
+ * @param token the token on which to listen for an authorized response
+ * @param receptionStrategy the strategy to use when the data has been received
+ */
case class RoomReceiverServiceVerticle(token: String, receptionStrategy: List[Participant] => Unit) extends VertxServer with Logging {
override protected val serverPort: Int = 0
@@ -14,12 +23,14 @@ case class RoomReceiverServiceVerticle(token: String, receptionStrategy: List[Pa
def port: Int = server.actualPort()
override protected def initRouter(router: Router): Unit = {
- import it.cwmp.services.roomreceiver.ServerParameters._
- router post API_RECEIVE_PARTICIPANTS_URL(token) handler updateRoomParticipantsHandler
+ router post API_RECEIVE_PARTICIPANTS_URL(token) handler createParticipantReceiverHandler
log.info(s"Starting the RoomReceiver service with the token: $token ...")
}
- private def updateRoomParticipantsHandler: Handler[RoutingContext] = implicit routingContext => {
+ /**
+ * Handles the reception of list of participants to the room
+ */
+ private def createParticipantReceiverHandler: Handler[RoutingContext] = implicit routingContext => {
log.info("Receiving participant list...")
request.bodyHandler(body =>
extractIncomingParticipantListFromBody(body) match {
@@ -38,7 +49,6 @@ case class RoomReceiverServiceVerticle(token: String, receptionStrategy: List[Pa
try {
var result = List[Participant]()
val jsonArray = body.toJsonArray
- import Participant.Converters._
jsonArray.forEach {
case jsonObject: JsonObject => result = result :+ jsonObject.toParticipant
}
diff --git a/services/room-receiver/src/test/resources/logback-test.xml b/services/room-receiver/src/test/resources/logback-test.xml
new file mode 100644
index 00000000..b211d12b
--- /dev/null
+++ b/services/room-receiver/src/test/resources/logback-test.xml
@@ -0,0 +1,10 @@
+
+
+
+ %msg%n
+
+
+
+
+
+
\ No newline at end of file
diff --git a/services/room-receiver/src/test/scala/it/cwmp/services/roomreceiver/RoomReceiverServiceVerticleTest.scala b/services/room-receiver/src/test/scala/it/cwmp/services/roomreceiver/RoomReceiverServiceVerticleTest.scala
index e4ae17a6..47273d5f 100644
--- a/services/room-receiver/src/test/scala/it/cwmp/services/roomreceiver/RoomReceiverServiceVerticleTest.scala
+++ b/services/room-receiver/src/test/scala/it/cwmp/services/roomreceiver/RoomReceiverServiceVerticleTest.scala
@@ -11,6 +11,9 @@ import it.cwmp.utils.VertxClient
import scala.util.{Failure, Success}
+/**
+ * A test class for Room receiver service
+ */
class RoomReceiverServiceVerticleTest extends RoomReceiverWebTesting
with HttpMatchers with FutureMatchers with VertxClient {
diff --git a/services/room-receiver/src/test/scala/it/cwmp/services/testing/roomreceiver/RoomReceiverTesting.scala b/services/room-receiver/src/test/scala/it/cwmp/services/testing/roomreceiver/RoomReceiverTesting.scala
index 630e91a6..f039dc59 100644
--- a/services/room-receiver/src/test/scala/it/cwmp/services/testing/roomreceiver/RoomReceiverTesting.scala
+++ b/services/room-receiver/src/test/scala/it/cwmp/services/testing/roomreceiver/RoomReceiverTesting.scala
@@ -1,19 +1,23 @@
package it.cwmp.services.testing.roomreceiver
import it.cwmp.model.Participant
+import it.cwmp.services.testing.roomreceiver.RoomReceiverTesting.TOKEN_LENGTH
import it.cwmp.testing.VertxTest
import it.cwmp.utils.Utils
import org.scalatest.{BeforeAndAfterEach, Matchers}
import scala.concurrent.Promise
+/**
+ * An abstract base class for testing Room receiver service
+ */
abstract class RoomReceiverTesting extends VertxTest with Matchers with BeforeAndAfterEach {
protected var port: Int
- protected val rightToken: String = Utils.randomString(20)
+ protected val rightToken: String = Utils.randomString(TOKEN_LENGTH)
- protected val wrongToken: String = Utils.randomString(20)
+ protected val wrongToken: String = Utils.randomString(TOKEN_LENGTH)
protected val participants: List[Participant] = List(
Participant("Nome", "Indirizzo"),
@@ -28,3 +32,10 @@ abstract class RoomReceiverTesting extends VertxTest with Matchers with BeforeAn
}
}
+
+/**
+ * Companion object
+ */
+object RoomReceiverTesting {
+ private val TOKEN_LENGTH = 20
+}
\ No newline at end of file
diff --git a/services/room-receiver/src/test/scala/it/cwmp/services/testing/roomreceiver/RoomReceiverWebTesting.scala b/services/room-receiver/src/test/scala/it/cwmp/services/testing/roomreceiver/RoomReceiverWebTesting.scala
index ad169a45..b0d83510 100644
--- a/services/room-receiver/src/test/scala/it/cwmp/services/testing/roomreceiver/RoomReceiverWebTesting.scala
+++ b/services/room-receiver/src/test/scala/it/cwmp/services/testing/roomreceiver/RoomReceiverWebTesting.scala
@@ -3,12 +3,15 @@ package it.cwmp.services.testing.roomreceiver
import it.cwmp.services.roomreceiver.RoomReceiverServiceVerticle
import it.cwmp.testing.VerticleBeforeAndAfterEach
+/**
+ * An abstract base class for testing the room receiver web service
+ */
abstract class RoomReceiverWebTesting extends RoomReceiverTesting with VerticleBeforeAndAfterEach {
override var port: Int = _
- override protected val verticlesBeforeEach: List[RoomReceiverServiceVerticle] = List(
- RoomReceiverServiceVerticle(rightToken, s => participantsPromise.success(s)))
+ override protected val verticlesBeforeEach: List[RoomReceiverServiceVerticle] =
+ List(RoomReceiverServiceVerticle(rightToken, s => participantsPromise.success(s)))
override def beforeEach(): Unit = {
super.beforeEach()
diff --git a/services/rooms/src/main/scala/it/cwmp/services/rooms/RoomsLocalDAO.scala b/services/rooms/src/main/scala/it/cwmp/services/rooms/RoomsLocalDAO.scala
index 2cfc9392..82cebd9a 100644
--- a/services/rooms/src/main/scala/it/cwmp/services/rooms/RoomsLocalDAO.scala
+++ b/services/rooms/src/main/scala/it/cwmp/services/rooms/RoomsLocalDAO.scala
@@ -1,16 +1,15 @@
package it.cwmp.services.rooms
-import com.typesafe.scalalogging.Logger
-import io.vertx.lang.scala.json.JsonArray
import io.vertx.scala.ext.sql.{ResultSet, SQLConnection}
import it.cwmp.model.{Address, Participant, Room, User}
import it.cwmp.services.rooms.RoomsLocalDAO._
-import it.cwmp.utils.{Utils, VertxInstance, VertxJDBC}
+import it.cwmp.utils.Utils.emptyString
+import it.cwmp.utils.VertxJDBC.stringsToJsonArray
+import it.cwmp.utils.{Logging, Utils, VertxInstance, VertxJDBC}
import scala.collection.mutable
import scala.concurrent.{ExecutionContext, Future}
import scala.language.implicitConversions
-import scala.util.Random
/**
* A trait that describes the Rooms Data Access Object
@@ -39,7 +38,8 @@ trait RoomDAO {
* or fails if roomID not provided, not present
* or user already inside a room, or room full
*/
- def enterRoom(roomID: String)(implicit user: Participant, notificationAddress: Address): Future[Unit]
+ def enterRoom(roomID: String)
+ (implicit user: Participant, notificationAddress: Address): Future[Unit]
/**
* Retrieves room information, and related user notification addresses
@@ -59,7 +59,8 @@ trait RoomDAO {
* or fails if roomId is not provided or not present
* or user is not inside that room
*/
- def exitRoom(roomID: String)(implicit user: User): Future[Unit]
+ def exitRoom(roomID: String)
+ (implicit user: User): Future[Unit]
/**
* retrieves a list of available public rooms
@@ -78,7 +79,8 @@ trait RoomDAO {
* or fails if players number is not correct,
* or user already inside a room
*/
- def enterPublicRoom(playersNumber: Int)(implicit user: Participant, notificationAddress: Address): Future[Unit]
+ def enterPublicRoom(playersNumber: Int)
+ (implicit user: Participant, notificationAddress: Address): Future[Unit]
/**
* Retrieves information about a public room with specific number of players, and relative user notification addresses
@@ -98,7 +100,8 @@ trait RoomDAO {
* or fails if players number is not correct,
* or user not inside that room
*/
- def exitPublicRoom(playersNumber: Int)(implicit user: User): Future[Unit]
+ def exitPublicRoom(playersNumber: Int)
+ (implicit user: User): Future[Unit]
/**
* Deletes a room if it's full
@@ -126,7 +129,9 @@ trait RoomDAO {
*
* @author Enrico Siboni
*/
-case class RoomsLocalDAO(override val configurationPath: String = "rooms/database.json") extends RoomDAO with VertxInstance with VertxJDBC {
+case class RoomsLocalDAO(override val configurationPath: String = "rooms/database.json") extends RoomDAO
+ with VertxInstance with VertxJDBC with Logging {
+
private var notInitialized = true
private val PUBLIC_ROOM_MAX_SIZE = 4
@@ -139,10 +144,8 @@ case class RoomsLocalDAO(override val configurationPath: String = "rooms/databas
private val NOT_INSIDE_USER_ERROR = "The user is not inside that room: "
private val DELETING_NON_FULL_ROOM_ERROR = "Cannot delete room if it's not full"
- import RoomsLocalDAO.stringsToJsonArray
-
def initialize(): Future[Unit] = {
- logger.info("Initializing RoomLocalDAO...")
+ log.info("Initializing RoomLocalDAO...")
def createDefaultPublicRooms(conn: SQLConnection) =
Future.sequence { // waits all creation future to end, or returns the failed one
@@ -150,132 +153,154 @@ case class RoomsLocalDAO(override val configurationPath: String = "rooms/databas
creationFuture = createPublicRoom(conn, playersNumber)) yield creationFuture
}
- openConnection()
- .flatMap(conn => conn.executeFuture(createRoomTableSql)
- .flatMap(_ => conn.executeFuture(createUserTableSql))
- .map(_ => notInitialized = false)
- .flatMap(_ => listPublicRooms())
- .filter(_.isEmpty)
- .flatMap(_ => {
- logger.info(s"No public rooms found, creating public ones with players from 2 to $PUBLIC_ROOM_MAX_SIZE.")
- createDefaultPublicRooms(conn)
- })
- .recover { case _: NoSuchElementException => logger.info("Room database already exists, skipping creation.") }
- .andThen { case _ => conn.close() })
- .flatMap(_ => Future.successful(()))
+ (for (
+ conn <- openConnection();
+ _ <- conn.executeFuture(createRoomTableSql);
+ _ <- conn.executeFuture(createUserTableSql);
+ _ = notInitialized = false;
+ publicRooms <- listPublicRooms() if publicRooms.isEmpty;
+ _ = log.info(s"No public rooms found, creating public ones with players from 2 to $PUBLIC_ROOM_MAX_SIZE.");
+ _ <- createDefaultPublicRooms(conn)
+ ) yield ())
+ .recover { case _: NoSuchElementException => log.info("Room database already exists, skipping creation.") }
+ .closeConnections
}
override def createRoom(roomName: String, playersNumber: Int): Future[String] = {
- logger.debug(s"createRoom() roomName:$roomName, playersNumber:$playersNumber")
- checkInitialization(notInitialized)
- .flatMap(_ => stringCheckFuture(roomName, EMPTY_ROOM_NAME_ERROR))
- .flatMap(_ => playersNumberCheck(playersNumber, s"$INVALID_PLAYERS_NUMBER$playersNumber"))
- .flatMap(_ => openConnection())
- .flatMap(conn => getNotAlreadyPresentRoomID(conn, generateRandomRoomID())
- .flatMap(roomID => conn.updateWithParamsFuture(insertNewRoomSql, Seq(roomID, roomName, playersNumber.toString))
- .map(_ => roomID))
- .andThen { case _ => conn.close() }
- )
+ log.debug(s"createRoom() roomName:$roomName, playersNumber:$playersNumber")
+ (for (
+ _ <- checkInitialization(notInitialized);
+ _ <- stringCheckFuture(roomName, EMPTY_ROOM_NAME_ERROR);
+ _ <- playersNumberCheck(playersNumber, s"$INVALID_PLAYERS_NUMBER$playersNumber");
+ conn <- openConnection();
+ roomID <- getNotAlreadyPresentRoomID(conn, generateRandomRoomID());
+ _ <- conn.updateWithParamsFuture(insertNewRoomSql, Seq(roomID, roomName, playersNumber.toString))
+ ) yield roomID).closeLastConnection
}
- override def enterRoom(roomID: String)(implicit user: Participant, notificationAddress: Address): Future[Unit] = {
- logger.debug(s"enterRoom() roomID:$roomID, user:$user, notificationAddress:$notificationAddress")
- checkInitialization(notInitialized)
- .flatMap(_ => roomInfo(roomID))
- .flatMap(_ => openConnection())
- .flatMap(conn => checkRoomSpaceAvailable(roomID, conn, ROOM_FULL_ERROR)
- .flatMap(_ => getRoomOfUser(conn, user)) // check if user is inside any room
- .flatMap {
- case Some(room) => Future.failed(new IllegalStateException(s"$ALREADY_INSIDE_USER_ERROR${user.username} -> ${room.identifier}"))
- case None => Future.successful(Unit)
- }
- .flatMap(_ => conn.updateWithParamsFuture(insertUserInRoomSql, Seq(user.username, user.address, notificationAddress.address, roomID)))
- .andThen { case _ => conn.close() })
- .flatMap(_ => Future.successful(()))
+ override def enterRoom(roomID: String)
+ (implicit user: Participant, notificationAddress: Address): Future[Unit] = {
+
+ def giveErrorOnRoomPresent(roomOption: Option[Room]): Future[Unit] = roomOption match {
+ case Some(room) => Future.failed(new IllegalStateException(s"$ALREADY_INSIDE_USER_ERROR${user.username} -> ${room.identifier}"))
+ case None => Future.successful(())
+ }
+
+ log.debug(s"enterRoom() roomID:$roomID, user:$user, notificationAddress:$notificationAddress")
+ (for (
+ _ <- checkInitialization(notInitialized);
+ _ <- roomInfo(roomID);
+ conn <- openConnection();
+ _ <- checkRoomSpaceAvailable(roomID, conn, ROOM_FULL_ERROR);
+ roomOption <- getRoomOfUser(conn, user); // check if user is inside any room
+ _ <- giveErrorOnRoomPresent(roomOption);
+ _ <- conn.updateWithParamsFuture(insertUserInRoomSql, Seq(user.username, user.address, notificationAddress.address, roomID))
+ ) yield ()).closeLastConnection
}
override def roomInfo(roomID: String): Future[(Room, Seq[Address])] = {
- logger.debug(s"roomInfo() roomID:$roomID")
- checkInitialization(notInitialized)
- .flatMap(_ => stringCheckFuture(roomID, EMPTY_ROOM_ID_ERROR))
- .flatMap(_ => openConnection())
- .flatMap(conn => checkRoomPresence(roomID, conn, NOT_PRESENT_ROOM_ID_ERROR)
- .flatMap(_ => conn.queryWithParamsFuture(selectRoomByIDSql, Seq(roomID)))
- .map(resultOfJoinToRooms(_).head)
- .andThen { case _ => conn.close() })
+ log.debug(s"roomInfo() roomID:$roomID")
+ (for (
+ _ <- checkInitialization(notInitialized);
+ _ <- stringCheckFuture(roomID, EMPTY_ROOM_ID_ERROR);
+ conn <- openConnection();
+ _ <- checkRoomPresence(roomID, conn, NOT_PRESENT_ROOM_ID_ERROR);
+ roomResult <- conn.queryWithParamsFuture(selectRoomByIDSql, Seq(roomID));
+ roomAndAddresses = resultOfJoinToRooms(roomResult).head
+ ) yield roomAndAddresses).closeLastConnection
}
- override def exitRoom(roomID: String)(implicit user: User): Future[Unit] = {
- logger.debug(s"exitRoom() roomID:$roomID, user:$user")
- checkInitialization(notInitialized)
- .flatMap(_ => roomInfo(roomID))
- .flatMap(_ => openConnection())
- .flatMap(conn => getRoomOfUser(conn, user) // check user inside room
- .flatMap {
- case Some(room) if room.identifier == roomID => Future.successful(Unit)
- case _ => Future.failed(new IllegalStateException(s"$NOT_INSIDE_USER_ERROR${user.username} -> $roomID"))
- }
- .flatMap(_ => conn.updateWithParamsFuture(deleteUserFormRoomSql, Seq(user.username)))
- .andThen { case _ => conn.close() })
- .flatMap(_ => Future.successful(()))
+ override def exitRoom(roomID: String)
+ (implicit user: User): Future[Unit] = {
+
+ def giveErrorOnWrongOrNotPresentRoom(roomOption: Option[Room], toCheckRoomID: String): Future[Unit] = roomOption match {
+ case Some(room) if room.identifier == toCheckRoomID => Future.successful(())
+ case _ => Future.failed(new IllegalStateException(s"$NOT_INSIDE_USER_ERROR${user.username} -> $toCheckRoomID"))
+ }
+
+ log.debug(s"exitRoom() roomID:$roomID, user:$user")
+ (for (
+ _ <- checkInitialization(notInitialized);
+ _ <- roomInfo(roomID);
+ conn <- openConnection();
+ roomOption <- getRoomOfUser(conn, user); // check user inside room
+ _ <- giveErrorOnWrongOrNotPresentRoom(roomOption, roomID);
+ _ <- conn.updateWithParamsFuture(deleteUserFormRoomSql, Seq(user.username))
+ ) yield ()).closeLastConnection
}
override def listPublicRooms(): Future[Seq[Room]] = {
- logger.debug("listPublicRooms()")
- checkInitialization(notInitialized)
- .flatMap(_ => openConnection())
- .flatMap(conn => conn.queryFuture(selectAllPublicRoomsSql)
- .andThen { case _ => conn.close() })
- .map(resultOfJoinToRooms).map(_.map(_._1))
+ log.debug("listPublicRooms()")
+ (for (
+ _ <- checkInitialization(notInitialized);
+ conn <- openConnection();
+ roomsResult <- conn.queryFuture(selectAllPublicRoomsSql);
+ roomsList = resultOfJoinToRooms(roomsResult).map(_._1)
+ ) yield roomsList).closeLastConnection
}
- override def enterPublicRoom(playersNumber: Int)(implicit user: Participant, address: Address): Future[Unit] = {
- logger.debug(s"enterPublicRoom() playersNumber:$playersNumber, user:$user, address:$address")
- checkInitialization(notInitialized)
- .flatMap(_ => publicRoomIdFromPlayersNumber(playersNumber))
- .flatMap(enterRoom(_)(user, address))
+ override def enterPublicRoom(playersNumber: Int)
+ (implicit user: Participant, address: Address): Future[Unit] = {
+ log.debug(s"enterPublicRoom() playersNumber:$playersNumber, user:$user, address:$address")
+ for (
+ _ <- checkInitialization(notInitialized);
+ roomID <- publicRoomIdFromPlayersNumber(playersNumber);
+ _ <- enterRoom(roomID)(user, address)
+ ) yield ()
}
override def publicRoomInfo(playersNumber: Int): Future[(Room, Seq[Address])] = {
- logger.debug(s"publicRoomInfo() playersNumber:$playersNumber")
- checkInitialization(notInitialized)
- .flatMap(_ => publicRoomIdFromPlayersNumber(playersNumber))
- .flatMap(roomInfo)
+ log.debug(s"publicRoomInfo() playersNumber:$playersNumber")
+ for (
+ _ <- checkInitialization(notInitialized);
+ roomID <- publicRoomIdFromPlayersNumber(playersNumber);
+ info <- roomInfo(roomID)
+ ) yield info
}
- override def exitPublicRoom(playersNumber: Int)(implicit user: User): Future[Unit] = {
- logger.debug(s"exitPublicRoom() playersNumber:$playersNumber, user:$user")
- checkInitialization(notInitialized)
- .flatMap(_ => publicRoomIdFromPlayersNumber(playersNumber))
- .flatMap(exitRoom)
+ override def exitPublicRoom(playersNumber: Int)
+ (implicit user: User): Future[Unit] = {
+ log.debug(s"exitPublicRoom() playersNumber:$playersNumber, user:$user")
+ for (
+ _ <- checkInitialization(notInitialized);
+ roomID <- publicRoomIdFromPlayersNumber(playersNumber);
+ _ <- exitRoom(roomID)
+ ) yield ()
}
override def deleteRoom(roomID: String): Future[Unit] = {
- logger.debug(s"deleteRoom() roomID:$roomID")
- checkInitialization(notInitialized)
- .flatMap(_ => roomInfo(roomID)).map(_._1)
- .flatMap(room => openConnection()
- .flatMap(conn => checkRoomSpaceAvailable(roomID, conn, "")
- .flatMap(_ => Future.failed(new IllegalStateException(DELETING_NON_FULL_ROOM_ERROR))) // checking room full
- .recoverWith({ case ex: IllegalStateException if ex.getMessage.isEmpty => Future.successful(Unit) })
-
- // deleting should consist of removing users from room and then delete room
- .map(_ => for (user <- room.participants;
- deleteFuture = conn.updateWithParamsFuture(deleteUserFormRoomSql, Seq(user.username))) yield deleteFuture)
- .flatMap(Future.sequence(_)) // waits for all to complete
- .flatMap(_ => conn.updateWithParamsFuture(deleteRoomSql, Seq(roomID)))
- .andThen { case _ => conn.close() }))
- .flatMap(_ => Future.successful(()))
+
+ def giveErrorOnNonFullRoom(roomID: String, connection: SQLConnection): Future[Unit] =
+ checkRoomSpaceAvailable(roomID, connection, "") // if no room space available, will give empty error message
+ // if no error received, should fail -> because room non full
+ .flatMap(_ => Future.failed(new IllegalStateException(DELETING_NON_FULL_ROOM_ERROR)))
+ // if first error reaches this point, room is full and can be deleted
+ .recoverWith({ case ex: IllegalStateException if ex.getMessage.isEmpty => Future.successful(()) })
+
+ log.debug(s"deleteRoom() roomID:$roomID")
+ (for (
+ _ <- checkInitialization(notInitialized);
+ room <- roomInfo(roomID).map(_._1);
+ conn <- openConnection();
+ _ <- giveErrorOnNonFullRoom(roomID, conn);
+
+ // outer for will reach this point only if room is full (and can be deleted)
+ userDeletionFutures = for (user <- room.participants;
+ deletionFuture = conn.updateWithParamsFuture(deleteUserFormRoomSql, Seq(user.username))) yield deletionFuture;
+ _ <- Future.sequence(userDeletionFutures); // waits for all deletions to complete
+ _ <- conn.updateWithParamsFuture(deleteRoomSql, Seq(roomID))
+ ) yield ()).closeLastConnection
}
override def deleteAndRecreatePublicRoom(playersNumber: Int): Future[Unit] = {
- logger.debug(s"deleteAndRecreatePublicRoom() playersNumber:$playersNumber")
- checkInitialization(notInitialized)
- .flatMap(_ => publicRoomIdFromPlayersNumber(playersNumber))
- .flatMap(deleteRoom)
- .flatMap(_ => openConnection())
- .flatMap(conn => createPublicRoom(conn, playersNumber)
- .andThen { case _ => conn.close() })
+ log.debug(s"deleteAndRecreatePublicRoom() playersNumber:$playersNumber")
+ (for (
+ _ <- checkInitialization(notInitialized);
+ roomID <- publicRoomIdFromPlayersNumber(playersNumber);
+ _ <- deleteRoom(roomID);
+ conn <- openConnection();
+ _ <- createPublicRoom(conn, playersNumber)
+ ) yield ()).closeLastConnection
}
/**
@@ -298,19 +323,10 @@ case class RoomsLocalDAO(override val configurationPath: String = "rooms/databas
* Companion Object
*/
object RoomsLocalDAO {
- private val logger: Logger = Logger[RoomsLocalDAO]
val publicPrefix: String = "public"
- /**
- * Utility method to check if DAO is initialized
- */
- private def checkInitialization(notInitialized: Boolean): Future[Unit] = {
- if (notInitialized) Future.failed(new IllegalStateException("Not initialized, you should first call initialize()"))
- else Future.successful(Unit)
- }
-
-
+ private val ROOM_ID_LENGTH = 20
private val userToRoomLinkField = "user_room"
private val createRoomTableSql =
s"""
@@ -361,6 +377,7 @@ object RoomsLocalDAO {
private def getNotAlreadyPresentRoomID(connection: SQLConnection, generator: => String)
(implicit executionContext: ExecutionContext): Future[String] = {
+ //noinspection ScalaStyle
def _getNotAlreadyPresentRoomID(toCheckID: String, connection: SQLConnection): Future[String] =
checkRoomPresence(toCheckID, connection, "")
.flatMap(_ => _getNotAlreadyPresentRoomID(generator, connection))
@@ -379,11 +396,11 @@ object RoomsLocalDAO {
}
/**
- * Utility method to convert a result set to a room sequence
+ * Utility method to convert a result set to a room sequence and relative addresses
*/
- private def resultOfJoinToRooms(resultSet: ResultSet): Seq[(Room, Seq[Address])] = { // review maybe with for comprehension
+ private def resultOfJoinToRooms(resultSet: ResultSet): Seq[(Room, Seq[Address])] = {
val roomsUsers: mutable.Map[String, Seq[Participant]] = mutable.HashMap()
- val roomsAddresses: mutable.Map[String, Seq[Address]] = mutable.HashMap() // TODO: refactor
+ val roomsAddresses: mutable.Map[String, Seq[Address]] = mutable.HashMap()
val roomsInfo: mutable.Map[String, (String, Int)] = mutable.HashMap()
val roomIDPos = 0
val roomNamePos = 1
@@ -393,7 +410,6 @@ object RoomsLocalDAO {
val userNotificationAddressPos = 5
for (resultRow <- resultSet.getResults) {
- //logger.debug(s"Database row: $resultRow")
val roomID = resultRow getString roomIDPos
val userName = if (resultRow hasNull userNamePos) None else Some(resultRow getString userNamePos)
val userAddress = if (resultRow hasNull userAddressPos) None else Some(resultRow getString userAddressPos)
@@ -426,72 +442,68 @@ object RoomsLocalDAO {
}
/**
- * Implicit conversion to jsonArray
- *
- * @return the converted data
+ * Method that generates a random room identifier
*/
- protected implicit def stringsToJsonArray(arguments: Iterable[String]): JsonArray = {
- arguments.foldLeft(new JsonArray)(_.add(_))
- }
+ private def generateRandomRoomID() = Utils.randomString(ROOM_ID_LENGTH)
/**
- * Method that generates a random room identifier
+ * Utility method to check if DAO is initialized
*/
- private def generateRandomRoomID() = Random.nextInt(Int.MaxValue).toString
+ private def checkInitialization(notInitialized: Boolean): Future[Unit] = {
+ if (notInitialized) Future.failed(new IllegalStateException("Not initialized, you should first call initialize()"))
+ else Future.successful(())
+ }
/**
* @return a succeeded Future if string is ok, a failed Future otherwise
*/
- private def stringCheckFuture(toCheck: String, errorMessage: String): Future[Unit] = Future {
- import Utils.emptyString
- require(!emptyString(toCheck), errorMessage)
- }(ExecutionContext.Implicits.global)
+ private def stringCheckFuture(toCheck: String, errorMessage: String): Future[Unit] =
+ Future {
+ require(!emptyString(toCheck), errorMessage)
+ }(ExecutionContext.Implicits.global)
/**
* @return a succeeded Future if playersNumber is correct, a failed Future otherwise
*/
private def playersNumberCheck(playersNumber: Int, errorMessage: String): Future[Unit] =
if (playersNumber < 2) Future.failed(new IllegalArgumentException(errorMessage))
- else Future.successful(Unit)
+ else Future.successful(())
/**
* @return a succeeded future if the room with given ID is present, a failed future otherwise
*/
- private def checkRoomPresence(roomID: String,
- connection: SQLConnection,
- errorMessage: String)(implicit executionContext: ExecutionContext): Future[Unit] = {
+ private def checkRoomPresence(roomID: String, connection: SQLConnection, errorMessage: String)
+ (implicit executionContext: ExecutionContext): Future[Unit] = {
connection.queryWithParamsFuture(selectARoomIDSql, Seq(roomID))
.flatMap(_.getResults.size match {
case 0 => Future.failed(new NoSuchElementException(errorMessage))
- case _ => Future.successful(Unit)
+ case _ => Future.successful(())
})
}
- /**
- * Internal method to create public rooms
- *
- * @return the future that completes when the room is created
- */
- private def createPublicRoom(connection: SQLConnection,
- playersNumber: Int)(implicit executionContext: ExecutionContext): Future[Unit] = {
- getNotAlreadyPresentRoomID(connection, s"$publicPrefix${generateRandomRoomID()}")
- .flatMap(publicRoomID =>
- connection.updateWithParamsFuture(insertNewRoomSql, Seq(publicRoomID, s"$publicPrefix$playersNumber", playersNumber.toString)))
- .map(_ => Unit)
- }
-
/**
* @return a succeeded future if the room has available space for other users
*/
- private def checkRoomSpaceAvailable(roomID: String,
- connection: SQLConnection,
- errorMessage: String)(implicit executionContext: ExecutionContext): Future[Unit] = {
+ private def checkRoomSpaceAvailable(roomID: String, connection: SQLConnection, errorMessage: String)
+ (implicit executionContext: ExecutionContext): Future[Unit] = {
connection.queryWithParamsFuture(selectRoomByIDSql, Seq(roomID))
.map(resultOfJoinToRooms(_).map(_._1))
.flatMap(_.head match {
case Room(_, _, playersNumber, actualParticipants)
- if playersNumber > actualParticipants.size => Future.successful(Unit)
+ if playersNumber > actualParticipants.size => Future.successful(())
case _ => Future.failed(new IllegalStateException(errorMessage))
})
}
+
+ /**
+ * Internal method to create public rooms
+ *
+ * @return the future that completes when the room is created
+ */
+ private def createPublicRoom(connection: SQLConnection, playersNumber: Int)
+ (implicit executionContext: ExecutionContext): Future[Unit] =
+ for (
+ publicRoomID <- getNotAlreadyPresentRoomID(connection, s"$publicPrefix${generateRandomRoomID()}");
+ _ <- connection.updateWithParamsFuture(insertNewRoomSql, Seq(publicRoomID, s"$publicPrefix$playersNumber", playersNumber.toString))
+ ) yield ()
}
diff --git a/services/rooms/src/main/scala/it/cwmp/services/rooms/RoomsServiceMain.scala b/services/rooms/src/main/scala/it/cwmp/services/rooms/RoomsServiceMain.scala
index 5134d529..fbc27e80 100644
--- a/services/rooms/src/main/scala/it/cwmp/services/rooms/RoomsServiceMain.scala
+++ b/services/rooms/src/main/scala/it/cwmp/services/rooms/RoomsServiceMain.scala
@@ -1,10 +1,7 @@
package it.cwmp.services.rooms
-import com.typesafe.scalalogging.Logger
-import io.vertx.lang.scala.VertxExecutionContext
-import io.vertx.scala.core.Vertx
import it.cwmp.services.wrapper.{AuthenticationApiWrapper, RoomReceiverApiWrapper}
-import it.cwmp.utils.Logging
+import it.cwmp.utils.{Logging, VertxInstance}
import scala.util.{Failure, Success}
@@ -13,14 +10,11 @@ import scala.util.{Failure, Success}
*
* @author Enrico Siboni
*/
-object RoomsServiceMain extends App with Logging {
- private val vertx: Vertx = Vertx.vertx()
-
+object RoomsServiceMain extends App with VertxInstance with Logging {
log.info("Deploying RoomService...")
vertx.deployVerticleFuture(RoomsServiceVerticle(AuthenticationApiWrapper(), RoomReceiverApiWrapper()))
.onComplete {
case Success(_) => log.info("RoomsService up and running!")
case Failure(ex) => log.info("Error deploying RoomsService", ex)
- }(VertxExecutionContext(vertx.getOrCreateContext()))
-
+ }
}
diff --git a/services/rooms/src/main/scala/it/cwmp/services/rooms/RoomsServiceUtils.scala b/services/rooms/src/main/scala/it/cwmp/services/rooms/RoomsServiceUtils.scala
deleted file mode 100644
index 97c49789..00000000
--- a/services/rooms/src/main/scala/it/cwmp/services/rooms/RoomsServiceUtils.scala
+++ /dev/null
@@ -1,110 +0,0 @@
-package it.cwmp.services.rooms
-
-import io.vertx.core.buffer.Buffer
-import io.vertx.core.json.JsonObject
-import io.vertx.scala.core.Vertx
-import io.vertx.scala.ext.web.RoutingContext
-import it.cwmp.model.{Address, Room}
-import it.cwmp.services.wrapper.RoomReceiverApiWrapper
-import it.cwmp.utils.{Logging, VertxServer}
-
-import scala.concurrent.{ExecutionContext, Future}
-
-trait RoomsServiceUtils {
- this: VertxServer with Logging =>
-
- private[rooms] val TOKEN_NOT_PROVIDED_OR_INVALID = "Token not provided or invalid"
- private[rooms] val INVALID_PARAMETER_ERROR = "Invalid parameters: "
-
- /**
- * A method to retrieve the database configuration
- *
- * @return the JsonObject representing database configuration
- */
- // TODO should be generalized in an exclusive trait
- private[rooms] def loadLocalDBConfig(vertx: Vertx)(implicit executionContext: ExecutionContext): Future[JsonObject] =
- vertx fileSystem() readFileFuture "database/jdbc_config.json" map (_.toJsonObject)
-
- /**
- * @param body the body where to extract
- * @return the extracted Addresses
- */
- private[rooms] def extractAddressesFromBody(body: Buffer): Option[(Address, Address)] = {
- try {
- val jsonArray = body.toJsonArray
- val userAddressJson = jsonArray.getJsonObject(0)
- val userNotificationAddressJson = jsonArray.getJsonObject(1)
- if ((userAddressJson containsKey Address.FIELD_ADDRESS) && (userNotificationAddressJson containsKey Address.FIELD_ADDRESS)) {
- import Address.Converters._
- Some((userAddressJson.toAddress, userNotificationAddressJson.toAddress))
- } else None
- } catch {
- case _: Throwable => None
- }
- }
-
- /**
- * Method that manages the filling of private rooms, fails if the room is not full
- */
- private[rooms] def handlePrivateRoomFilling(roomID: String)
- (implicit roomDAO: RoomDAO,
- routingContext: RoutingContext,
- communicationStrategy: RoomReceiverApiWrapper,
- executionContext: ExecutionContext): Future[Unit] = {
- handleRoomFilling(roomInformationFuture = roomDAO.roomInfo(roomID),
- onRetrievedRoomAction = roomDAO deleteRoom roomID map (_ => sendResponse(200)))
- }
-
- /**
- * Method that manages the filling of public rooms, fails if the room is not full
- */
- private[rooms] def handlePublicRoomFilling(playersNumber: Int)
- (implicit roomDAO: RoomDAO,
- routingContext: RoutingContext,
- communicationStrategy: RoomReceiverApiWrapper,
- executionContext: ExecutionContext): Future[Unit] = {
- handleRoomFilling(roomInformationFuture = roomDAO.publicRoomInfo(playersNumber),
- onRetrievedRoomAction = roomDAO deleteAndRecreatePublicRoom playersNumber map (_ => sendResponse(200)))
- }
-
- /**
- * Describes how to behave when a room is filled out
- *
- * @param roomInformationFuture the future that will contain the room and addresses of users to contact
- * @param onRetrievedRoomAction the action to do if the room is full
- * @return a future that completes when all players received addresses
- */
- private[this] def handleRoomFilling(roomInformationFuture: Future[(Room, Seq[Address])],
- onRetrievedRoomAction: => Future[Unit])
- (implicit communicationStrategy: RoomReceiverApiWrapper,
- executionContext: ExecutionContext): Future[Unit] = {
- roomInformationFuture.map(tuple => tuple._1).filter(roomIsFull)
- .flatMap(_ => {
- onRetrievedRoomAction
- sendParticipantAddresses(roomInformationFuture.value.get.get)
- })
- }
-
- /**
- * Describes when a room is full
- */
- private[rooms] def roomIsFull(room: Room): Boolean = room.participants.size == room.neededPlayersNumber
-
- /**
- * Method to communicate to clients that the game can start because of reaching the specified number of players
- *
- * @param roomInformation the room where players are waiting in
- */
- private[rooms] def sendParticipantAddresses(roomInformation: (Room, Seq[Address]))
- (implicit communicationStrategy: RoomReceiverApiWrapper,
- executionContext: ExecutionContext): Future[Unit] = {
- log.info(s"Preparing to send participant list to room ${roomInformation._1.name} (with id:${roomInformation._1.identifier}) participants ...")
- val notificationAddresses = for (notificationAddress <- roomInformation._2) yield notificationAddress
- val players = for (player <- roomInformation._1.participants) yield player
- log.info(s"Participant notification addresses to contact: $notificationAddresses")
- log.info(s"Information to send -> $players")
- Future.sequence {
- notificationAddresses map (notificationAddress => communicationStrategy.sendParticipants(notificationAddress.address, players))
- } map (_ => Unit)
- }
-}
diff --git a/services/rooms/src/main/scala/it/cwmp/services/rooms/RoomsServiceVerticle.scala b/services/rooms/src/main/scala/it/cwmp/services/rooms/RoomsServiceVerticle.scala
index 00b893ea..17703861 100644
--- a/services/rooms/src/main/scala/it/cwmp/services/rooms/RoomsServiceVerticle.scala
+++ b/services/rooms/src/main/scala/it/cwmp/services/rooms/RoomsServiceVerticle.scala
@@ -4,18 +4,17 @@ import io.vertx.core.Handler
import io.vertx.core.buffer.Buffer
import io.vertx.lang.scala.json.Json
import io.vertx.scala.ext.web.{Router, RoutingContext}
+import it.cwmp.model.Address.Converters._
+import it.cwmp.model.Room.Converters._
import it.cwmp.model.{Address, Participant, Room, User}
+import it.cwmp.services.rooms.RoomsServiceVerticle.{INVALID_PARAMETER_ERROR, _}
import it.cwmp.services.rooms.ServerParameters._
import it.cwmp.services.wrapper.RoomReceiverApiWrapper
+import it.cwmp.utils.Utils.stringToOption
import it.cwmp.utils.{Logging, Validation, VertxServer}
-import scala.concurrent.Future
-import scala.util.{Failure, Success}
-
-object RoomsServiceVerticle {
- def apply(implicit validationStrategy: Validation[String, User], clientCommunicationStrategy: RoomReceiverApiWrapper): RoomsServiceVerticle =
- new RoomsServiceVerticle()(validationStrategy, clientCommunicationStrategy)
-}
+import scala.concurrent.{ExecutionContext, Future}
+import scala.util.{Failure, Success, Try}
/**
* Class that implements the Rooms micro-service
@@ -24,7 +23,7 @@ object RoomsServiceVerticle {
*/
class RoomsServiceVerticle(implicit val validationStrategy: Validation[String, User],
implicit val clientCommunicationStrategy: RoomReceiverApiWrapper)
- extends VertxServer with RoomsServiceUtils with Logging {
+ extends VertxServer with Logging {
private var daoFuture: Future[RoomDAO] = _
@@ -43,8 +42,8 @@ class RoomsServiceVerticle(implicit val validationStrategy: Validation[String, U
}
override protected def initServer: Future[_] = {
- val roomDao = RoomsLocalDAO()
- daoFuture = roomDao.initialize().map(_ => roomDao)
+ val roomDAO = RoomsLocalDAO()
+ daoFuture = roomDAO.initialize().map(_ => roomDAO)
daoFuture
}
@@ -57,11 +56,9 @@ class RoomsServiceVerticle(implicit val validationStrategy: Validation[String, U
request.checkAuthenticationOrReject.map(_ =>
extractIncomingRoomFromBody(body) match {
case Some((roomName, neededPlayers)) =>
- daoFuture.map(_.createRoom(roomName, neededPlayers)
- .onComplete {
- case Success(generatedID) => sendResponse(201, generatedID)
- case Failure(ex) => sendResponse(400, ex.getMessage)
- })
+ daoFuture.map(_.createRoom(roomName, neededPlayers).onComplete {
+ failureHandler orElse successHandler(generatedID => sendResponse(201, generatedID))
+ })
case None =>
log.warn("Request body for room creation is required!")
sendResponse(400, s"$INVALID_PARAMETER_ERROR no Room JSON in body")
@@ -89,15 +86,7 @@ class RoomsServiceVerticle(implicit val validationStrategy: Validation[String, U
(getRequestParameter(Room.FIELD_IDENTIFIER), extractAddressesFromBody(body)) match {
case (Some(roomID), Some(addresses)) =>
daoFuture.map(implicit dao => dao.enterRoom(roomID)(Participant(user.username, addresses._1.address), addresses._2).onComplete {
- case Success(_) => handlePrivateRoomFilling(roomID)
- .transform({
- case Success(_) => Failure(new Exception())
- case Failure(_: NoSuchElementException) => Success(Unit)
- case Failure(ex) => log.error("Error handling Private Room Filling", ex); Success(Unit)
- })
- .map(_ => sendResponse(200))
- case Failure(ex: NoSuchElementException) => sendResponse(404, ex.getMessage)
- case Failure(ex) => sendResponse(400, ex.getMessage)
+ failureHandler orElse successHandler(_ => handlePrivateRoomFilling(roomID) map (_ => sendResponse(200)))
})
case _ =>
@@ -116,11 +105,7 @@ class RoomsServiceVerticle(implicit val validationStrategy: Validation[String, U
getRequestParameter(Room.FIELD_IDENTIFIER) match {
case Some(roomId) =>
daoFuture.map(_.roomInfo(roomId).map(_._1).onComplete {
- case Success(room) =>
- import Room.Converters._
- sendResponse(200, room.toJson.encode())
- case Failure(ex: NoSuchElementException) => sendResponse(404, ex.getMessage)
- case Failure(ex) => sendResponse(400, ex.getMessage)
+ failureHandler orElse successHandler(room => sendResponse(200, room.toJson.encode()))
})
case None => sendResponse(400, s"$INVALID_PARAMETER_ERROR ${Room.FIELD_IDENTIFIER}")
}
@@ -136,9 +121,7 @@ class RoomsServiceVerticle(implicit val validationStrategy: Validation[String, U
getRequestParameter(Room.FIELD_IDENTIFIER) match {
case Some(roomId) =>
daoFuture.map(_.exitRoom(roomId).onComplete {
- case Success(_) => sendResponse(200)
- case Failure(ex: NoSuchElementException) => sendResponse(404, ex.getMessage)
- case Failure(ex) => sendResponse(400, ex.getMessage)
+ failureHandler orElse successHandler(_ => sendResponse(200))
})
case None => sendResponse(400, s"$INVALID_PARAMETER_ERROR ${Room.FIELD_IDENTIFIER}")
}
@@ -150,15 +133,11 @@ class RoomsServiceVerticle(implicit val validationStrategy: Validation[String, U
*/
private def listPublicRoomsHandler: Handler[RoutingContext] = implicit routingContext => {
log.info("List Public Rooms Request received...")
- request.checkAuthenticationOrReject.map(_ => {
+ request.checkAuthenticationOrReject.map(_ =>
daoFuture.map(_.listPublicRooms().onComplete {
- case Success(rooms) =>
- import Room.Converters._
- val jsonArray = rooms.foldLeft(Json emptyArr())(_ add _.toJson)
- sendResponse(200, jsonArray encode())
- case Failure(ex) => sendResponse(400, ex.getMessage)
- })
- })
+ failureHandler orElse
+ successHandler(rooms => sendResponse(200, rooms.foldLeft(Json emptyArr())(_ add _.toJson) encode()))
+ }))
}
/**
@@ -174,17 +153,10 @@ class RoomsServiceVerticle(implicit val validationStrategy: Validation[String, U
}
(playersNumberOption, extractAddressesFromBody(body)) match {
case (Some(playersNumber), Some(addresses)) =>
- daoFuture.map(implicit dao => dao.enterPublicRoom(playersNumber)(Participant(user.username, addresses._1.address), addresses._2).onComplete {
- case Success(_) => handlePublicRoomFilling(playersNumber)
- .transform({
- case Success(_) => Failure(new Exception())
- case Failure(_: NoSuchElementException) => Success(Unit)
- case Failure(ex) => log.error("Error handling Public Room Filling", ex); Success(Unit)
- })
- .map(_ => sendResponse(200))
- case Failure(ex: NoSuchElementException) => sendResponse(404, ex.getMessage)
- case Failure(ex) => sendResponse(400, ex.getMessage)
- })
+ daoFuture.map(implicit dao => dao.enterPublicRoom(playersNumber)(Participant(user.username, addresses._1.address), addresses._2)
+ .onComplete {
+ failureHandler orElse successHandler(_ => handlePublicRoomFilling(playersNumber) map (_ => sendResponse(200)))
+ })
case _ =>
log.warn("The number of players in url and player/notification address in body are required for public room entering")
@@ -205,11 +177,7 @@ class RoomsServiceVerticle(implicit val validationStrategy: Validation[String, U
}) match {
case Some(playersNumber) =>
daoFuture.map(_.publicRoomInfo(playersNumber).map(_._1).onComplete {
- case Success(room) =>
- import Room.Converters._
- sendResponse(200, room.toJson.encode())
- case Failure(ex: NoSuchElementException) => sendResponse(404, ex.getMessage)
- case Failure(ex) => sendResponse(400, ex.getMessage)
+ failureHandler orElse { case Success(room) => sendResponse(200, room.toJson.encode()) }
})
case None =>
log.warn("The number of players in url is required for public room info retrieval")
@@ -230,9 +198,7 @@ class RoomsServiceVerticle(implicit val validationStrategy: Validation[String, U
}) match {
case Some(roomId) =>
daoFuture.map(_.exitPublicRoom(roomId).onComplete {
- case Success(_) => sendResponse(200)
- case Failure(ex: NoSuchElementException) => sendResponse(404, ex.getMessage)
- case Failure(ex) => sendResponse(400, ex.getMessage)
+ failureHandler orElse successHandler(_ => sendResponse(200))
})
case None =>
log.warn("The number of players in url is required for public room exiting")
@@ -240,4 +206,121 @@ class RoomsServiceVerticle(implicit val validationStrategy: Validation[String, U
}
})
}
+
+
+ /**
+ * Method that manages the filling of private rooms, fails if the room is not full
+ */
+ private def handlePrivateRoomFilling(roomID: String)
+ (implicit roomDAO: RoomDAO,
+ routingContext: RoutingContext,
+ communicationStrategy: RoomReceiverApiWrapper,
+ executionContext: ExecutionContext): Future[Unit] = {
+ handleRoomFilling(roomInformationFuture = roomDAO.roomInfo(roomID),
+ onRetrievedRoomAction = (roomDAO deleteRoom roomID)
+ .map(_ => sendResponse(200)))(communicationStrategy, executionContext)
+ }
+
+ /**
+ * Method that manages the filling of public rooms, fails if the room is not full
+ */
+ private def handlePublicRoomFilling(playersNumber: Int)
+ (implicit roomDAO: RoomDAO,
+ routingContext: RoutingContext,
+ communicationStrategy: RoomReceiverApiWrapper,
+ executionContext: ExecutionContext): Future[Unit] = {
+ handleRoomFilling(roomInformationFuture = roomDAO.publicRoomInfo(playersNumber),
+ onRetrievedRoomAction = (roomDAO deleteAndRecreatePublicRoom playersNumber)
+ .map(_ => sendResponse(200)))(communicationStrategy, executionContext)
+ }
+
+ /**
+ * Describes how to behave when a room is filled out
+ *
+ * @param roomInformationFuture the future that will contain the room and addresses of users to contact
+ * @param onRetrievedRoomAction the action to do if the room is full
+ * @return a future that completes when all players received addresses
+ */
+ private def handleRoomFilling(roomInformationFuture: Future[(Room, Seq[Address])], onRetrievedRoomAction: => Future[Unit])
+ (implicit communicationStrategy: RoomReceiverApiWrapper, executionContext: ExecutionContext): Future[Unit] = {
+ roomInformationFuture
+ .map(roomAndAddresses => roomAndAddresses._1)
+ .filter(roomIsFull)
+ .flatMap(_ => {
+ onRetrievedRoomAction
+ sendParticipantAddresses(roomInformationFuture.value.get.get)(communicationStrategy, executionContext)
+ }).transform {
+ case Success(_) => Failure(new Exception()) // if operation successful, outer response sending should block
+ case Failure(_: NoSuchElementException) => Success(()) // if room wasn't full, let outer response sending happen
+ case Failure(ex) => log.error("Error handling Room Filling", ex); Success(())
+ // unexpected error, log and let outer response sending happen
+ }
+ }
+
+ /**
+ * Method to communicate to clients that the game can start because of reaching the specified number of players
+ *
+ * @param roomInformation the room where players are waiting in
+ */
+ private def sendParticipantAddresses(roomInformation: (Room, Seq[Address]))
+ (implicit communicationStrategy: RoomReceiverApiWrapper, executionContext: ExecutionContext): Future[Unit] = {
+ log.info(s"Preparing to send participant list to room ${roomInformation._1.name} (with id:${roomInformation._1.identifier}) participants ...")
+ val notificationAddresses = for (notificationAddress <- roomInformation._2) yield notificationAddress
+ val players = for (player <- roomInformation._1.participants) yield player
+ log.info(s"Participant notification addresses to contact: $notificationAddresses")
+ log.info(s"Information to send -> $players")
+ Future.sequence {
+ notificationAddresses map (notificationAddress => communicationStrategy.sendParticipants(notificationAddress.address, players))
+ } map (_ => log.info("Information sent."))
+ }
+
+ /**
+ * @return the common handler for failures in this service
+ */
+ private def failureHandler(implicit routingContext: RoutingContext): PartialFunction[Try[_], Unit] = {
+ case Failure(ex: NoSuchElementException) => sendResponse(404, ex.getMessage)
+ case Failure(ex) => sendResponse(400, ex.getMessage)
+ }
+
+ /**
+ * @return the common handler for success, that does the specified action with result
+ */
+ private def successHandler[T](action: T => Unit)(implicit routingContext: RoutingContext): PartialFunction[Try[T], Unit] = {
+ case Success(successResult) => action(successResult)
+ }
+}
+
+
+/**
+ * Companion object
+ */
+object RoomsServiceVerticle {
+
+ def apply(implicit validationStrategy: Validation[String, User],
+ clientCommunicationStrategy: RoomReceiverApiWrapper): RoomsServiceVerticle =
+ new RoomsServiceVerticle()(validationStrategy, clientCommunicationStrategy)
+
+ private val INVALID_PARAMETER_ERROR: String = "Invalid parameters: "
+
+ /**
+ * @param body the body where to extract
+ * @return the extracted Addresses
+ */
+ private def extractAddressesFromBody(body: Buffer): Option[(Address, Address)] = {
+ try {
+ val jsonArray = body.toJsonArray
+ val userAddressJson = jsonArray.getJsonObject(0)
+ val userNotificationAddressJson = jsonArray.getJsonObject(1)
+ if ((userAddressJson containsKey Address.FIELD_ADDRESS) && (userNotificationAddressJson containsKey Address.FIELD_ADDRESS)) {
+ Some((userAddressJson.toAddress, userNotificationAddressJson.toAddress))
+ } else None
+ } catch {
+ case _: Throwable => None
+ }
+ }
+
+ /**
+ * Describes when a room is full
+ */
+ private def roomIsFull(room: Room): Boolean = room.participants.size == room.neededPlayersNumber
}
\ No newline at end of file
diff --git a/services/rooms/src/test/scala/it/cwmp/services/rooms/RoomsLocalDAOTest.scala b/services/rooms/src/test/scala/it/cwmp/services/rooms/RoomsLocalDAOTest.scala
index fa3b4998..8f342b2a 100644
--- a/services/rooms/src/test/scala/it/cwmp/services/rooms/RoomsLocalDAOTest.scala
+++ b/services/rooms/src/test/scala/it/cwmp/services/rooms/RoomsLocalDAOTest.scala
@@ -1,10 +1,9 @@
package it.cwmp.services.rooms
-import io.vertx.core.json.JsonObject
-import io.vertx.scala.ext.jdbc.JDBCClient
-import it.cwmp.model.{Address, Participant}
+import it.cwmp.model.{Address, Participant, User}
import it.cwmp.testing.FutureMatchers
import it.cwmp.testing.rooms.RoomsTesting
+import it.cwmp.utils.Utils
import org.scalatest.BeforeAndAfterEach
import scala.concurrent.Future
@@ -23,99 +22,109 @@ class RoomsLocalDAOTest extends RoomsTesting with BeforeAndAfterEach with Future
private val notificationAddress: Address = Address("http://127.0.1.1:8668/api/client/pFU9qOCU3kmYqwk1qqkl/room/participants")
override protected def beforeEach(): Unit = {
- daoFuture = createRoomLocalDAO flatMap (dao => dao.initialize() map (_ => dao))
+ val localDAO = RoomsLocalDAO()
+ daoFuture = localDAO.initialize() map (_ => localDAO)
}
+ /**
+ * @return a fresh random participant
+ */
+ def randomParticipant: Participant = Participant(Utils.randomString(user.username.length), userAddress)
+
override protected def privateRoomCreationTests(roomName: String, playersNumber: Int): Unit = {
it("should succeed returning roomId, if parameters are correct") {
- daoFuture.flatMap(_.createRoom(roomName, playersNumber))
- .flatMap(_ should not be empty)
+ for (dao <- daoFuture; roomID <- dao.createRoom(roomName, playersNumber)) yield roomID should not be empty
}
describe("should fail") {
it("if roomName empty") {
- daoFuture.flatMap(_.createRoom("", playersNumber)).shouldFailWith[IllegalArgumentException]
+ for (dao <- daoFuture; assertion <- dao.createRoom("", playersNumber).shouldFailWith[IllegalArgumentException])
+ yield assertion
}
it("if playersNumber less than 2") {
- daoFuture.flatMap(_.createRoom(roomName, 0)).shouldFailWith[IllegalArgumentException]
+ for (dao <- daoFuture; assertion <- dao.createRoom(roomName, 0).shouldFailWith[IllegalArgumentException])
+ yield assertion
}
}
}
override protected def privateRoomEnteringTests(roomName: String, playersNumber: Int): Unit = {
it("should succeed if roomId is provided") {
- daoFuture.flatMap(dao => dao.createRoom(roomName, playersNumber)
- .flatMap(dao.enterRoom(_)(user, notificationAddress))) flatMap (_ => succeed)
+ for (dao <- daoFuture; roomID <- dao.createRoom(roomName, playersNumber);
+ _ <- dao.enterRoom(roomID)(user, notificationAddress);
+ _ <- cleanUpRoom(roomID)) yield succeed
}
it("user should be inside after it") {
- daoFuture.flatMap(dao => dao.createRoom(roomName, playersNumber)
- .flatMap(roomID => (dao.enterRoom(roomID)(user, notificationAddress) flatMap (_ => dao.roomInfo(roomID)))
- .flatMap(roomInfo => assert(roomInfo._1.participants.contains(user) && roomInfo._2.contains(notificationAddress)))))
+ for (dao <- daoFuture; roomID <- dao.createRoom(roomName, playersNumber);
+ _ <- dao.enterRoom(roomID)(user, notificationAddress); roomInfo <- dao.roomInfo(roomID);
+ assertion <- assert(roomInfo._1.participants.contains(user) && roomInfo._2.contains(notificationAddress));
+ _ <- cleanUpRoom(roomID)) yield assertion
}
describe("should fail") {
- onWrongRoomID(roomID => daoFuture.flatMap(_.enterRoom(roomID)(user, notificationAddress)))
+ onWrongRoomID(roomID => daoFuture flatMap (_.enterRoom(roomID)(user, notificationAddress)))
it("if user is already inside a room") {
- daoFuture.flatMap(dao => dao.createRoom(roomName, playersNumber)
- .flatMap(roomID => dao.enterRoom(roomID)(user, notificationAddress)
- .flatMap(_ => dao.enterRoom(roomID)(user, notificationAddress))))
- .shouldFailWith[IllegalStateException]
+ for (dao <- daoFuture; roomID <- dao.createRoom(roomName, playersNumber);
+ _ <- dao.enterRoom(roomID)(user, notificationAddress);
+ assertion <- dao.enterRoom(roomID)(user, notificationAddress).shouldFailWith[IllegalStateException];
+ _ <- cleanUpRoom(roomID)) yield assertion
}
it("if the room is full") {
val playersNumber = 2
- daoFuture.flatMap(dao => dao.createRoom(roomName, playersNumber)
- .flatMap(roomID => dao.enterRoom(roomID)(Participant("User1", userAddress), notificationAddress)
- .flatMap(_ => dao.enterRoom(roomID)(Participant("User2", userAddress), notificationAddress))
- .flatMap(_ => dao.enterRoom(roomID)(Participant("User3", userAddress), notificationAddress))
- ))
- .shouldFailWith[IllegalStateException]
+ for (dao <- daoFuture; roomID <- dao.createRoom(roomName, playersNumber);
+ _ <- dao.enterRoom(roomID)(randomParticipant, notificationAddress);
+ _ <- dao.enterRoom(roomID)(randomParticipant, notificationAddress);
+ assertion <- dao.enterRoom(roomID)(randomParticipant, notificationAddress).shouldFailWith[IllegalStateException])
+ yield assertion
}
}
}
override protected def privateRoomInfoRetrievalTests(roomName: String, playersNumber: Int): Unit = {
it("should succeed if roomId is correct") {
- daoFuture.flatMap(dao => dao.createRoom(roomName, playersNumber)
- .flatMap(dao.roomInfo)) flatMap (_._1.participants shouldBe empty)
+ for (dao <- daoFuture; roomID <- dao.createRoom(roomName, playersNumber);
+ roomInfo <- dao.roomInfo(roomID)) yield roomInfo._1.participants shouldBe empty
}
it("should succeed and contain entered users") {
- daoFuture.flatMap(dao => dao.createRoom(roomName, playersNumber)
- .flatMap(roomID => dao.enterRoom(roomID)(user, notificationAddress)
- .flatMap(_ => dao.roomInfo(roomID))))
- .flatMap(roomInfo => assert(roomInfo._1.participants.contains(user) && roomInfo._2.contains(notificationAddress)))
+ for (dao <- daoFuture; roomID <- dao.createRoom(roomName, playersNumber);
+ _ <- dao.enterRoom(roomID)(user, notificationAddress);
+ roomInfo <- dao.roomInfo(roomID);
+ assertion <- assert(roomInfo._1.participants.contains(user) && roomInfo._2.contains(notificationAddress));
+ _ <- cleanUpRoom(roomID)) yield assertion
}
describe("should fail") {
- onWrongRoomID(roomID => daoFuture.flatMap(_.roomInfo(roomID)))
+ onWrongRoomID(roomID => daoFuture flatMap (_.roomInfo(roomID)))
}
}
override protected def privateRoomExitingTests(roomName: String, playersNumber: Int): Unit = {
it("should succeed if roomID is correct and user inside") {
- daoFuture.flatMap(dao => dao.createRoom(roomName, playersNumber)
- .flatMap(roomID => dao.enterRoom(roomID)(user, notificationAddress) flatMap (_ => dao.exitRoom(roomID))))
- .map(_ => succeed)
+ for (dao <- daoFuture; roomID <- dao.createRoom(roomName, playersNumber);
+ _ <- dao.enterRoom(roomID)(user, notificationAddress);
+ _ <- dao.exitRoom(roomID)) yield succeed
}
it("user should not be inside after it") {
- daoFuture.flatMap(dao => dao.createRoom(roomName, playersNumber)
- .flatMap(roomID => dao.enterRoom(roomID)(user, notificationAddress)
- .flatMap(_ => dao.exitRoom(roomID)) flatMap (_ => dao.roomInfo(roomID))))
- .flatMap(_._1.participants shouldNot contain(user))
+ for (dao <- daoFuture; roomID <- dao.createRoom(roomName, playersNumber);
+ _ <- dao.enterRoom(roomID)(user, notificationAddress);
+ _ <- dao.exitRoom(roomID);
+ roomInfo <- dao.roomInfo(roomID)) yield roomInfo._1.participants shouldNot contain(user)
}
describe("should fail") {
- onWrongRoomID(roomID => daoFuture.flatMap(_.exitRoom(roomID)))
+ onWrongRoomID(roomID => daoFuture flatMap (_.exitRoom(roomID)))
it("if user is not inside the room") {
- daoFuture.flatMap(dao => dao.createRoom(roomName, playersNumber) flatMap dao.exitRoom)
- .shouldFailWith[IllegalStateException]
+ for (dao <- daoFuture; roomID <- dao.createRoom(roomName, playersNumber);
+ assertion <- dao.exitRoom(roomID).shouldFailWith[IllegalStateException]) yield assertion
}
it("if user is inside another room") {
- daoFuture.flatMap(dao => dao.createRoom(roomName, playersNumber)
- .flatMap(roomID => dao.createRoom(roomName, playersNumber)
- .flatMap(otherRoomID => dao.enterRoom(otherRoomID)(user, notificationAddress)) flatMap (_ => dao.exitRoom(roomID))))
- .shouldFailWith[IllegalStateException]
+ for (dao <- daoFuture; roomID <- dao.createRoom(roomName, playersNumber);
+ otherRoomID <- dao.createRoom(roomName, playersNumber);
+ _ <- dao.enterRoom(roomID)(user, notificationAddress);
+ assertion <- dao.exitRoom(otherRoomID).shouldFailWith[IllegalStateException];
+ _ <- cleanUpRoom(roomID)) yield assertion
}
}
}
@@ -123,75 +132,88 @@ class RoomsLocalDAOTest extends RoomsTesting with BeforeAndAfterEach with Future
override protected def publicRoomEnteringTests(playersNumber: Int): Unit = {
describe("should succeed") {
it("if room with such players number exists") {
- daoFuture.flatMap(_.enterPublicRoom(playersNumber)(user, notificationAddress)) map (_ => succeed)
+ for (dao <- daoFuture;
+ _ <- dao.enterPublicRoom(playersNumber)(user, notificationAddress);
+ _ <- cleanUpRoom(playersNumber)) yield succeed
}
it("and the user should be inside it") {
- daoFuture.flatMap(dao => dao.enterPublicRoom(playersNumber)(user, notificationAddress) flatMap (_ => dao.publicRoomInfo(playersNumber)))
- .flatMap(roomInfo => assert(roomInfo._1.participants.contains(user) && roomInfo._2.contains(notificationAddress)))
+ for (dao <- daoFuture; _ <- dao.enterPublicRoom(playersNumber)(user, notificationAddress);
+ roomInfo <- dao.publicRoomInfo(playersNumber);
+ assertion <- assert(roomInfo._1.participants.contains(user) && roomInfo._2.contains(notificationAddress));
+ _ <- cleanUpRoom(playersNumber)) yield assertion
}
}
describe("should fail") {
- onWrongPlayersNumber(playersNumber => daoFuture.flatMap(_.enterPublicRoom(playersNumber)(user, notificationAddress)))
+ onWrongPlayersNumber(playersNumber => daoFuture flatMap (_.enterPublicRoom(playersNumber)(user, notificationAddress)))
it("if user is already inside a room") {
- daoFuture.flatMap(dao => dao.enterPublicRoom(2)(user, notificationAddress)
- .flatMap(_ => dao.enterPublicRoom(3)(user, notificationAddress)))
- .shouldFailWith[IllegalStateException]
+ for (dao <- daoFuture; _ <- dao.enterPublicRoom(2)(user, notificationAddress);
+ assertion <- dao.enterPublicRoom(3)(user, notificationAddress).shouldFailWith[IllegalStateException];
+ _ <- cleanUpRoom(playersNumber)) yield assertion
}
it("if room is full") {
- daoFuture.flatMap(dao => dao.enterPublicRoom(2)(Participant("User1", userAddress), notificationAddress)
- .flatMap(_ => dao.enterPublicRoom(2)(Participant("User2", userAddress), notificationAddress))
- .flatMap(_ => dao.enterPublicRoom(2)(Participant("User3", userAddress), notificationAddress)))
- .shouldFailWith[IllegalStateException]
+ val firstParticipant = randomParticipant
+ val secondParticipant = randomParticipant
+ for (dao <- daoFuture; _ <- dao.enterPublicRoom(2)(firstParticipant, notificationAddress);
+ _ <- dao.enterPublicRoom(2)(secondParticipant, notificationAddress);
+ assertion <- dao.enterPublicRoom(2)(randomParticipant, notificationAddress).shouldFailWith[IllegalStateException];
+ _ <- cleanUpRoom(playersNumber)(firstParticipant);
+ _ <- cleanUpRoom(playersNumber)(secondParticipant)) yield assertion
}
}
}
override protected def publicRoomListingTests(playersNumber: Int): Unit = {
it("should be nonEmpty") {
- daoFuture flatMap (_.listPublicRooms()) flatMap (_ should not be empty)
+ for (dao <- daoFuture; rooms <- dao.listPublicRooms()) yield rooms should not be empty
}
it("should show only public rooms") {
- daoFuture.flatMap(dao => dao.createRoom("TestRoom", 2)
- .flatMap(_ => dao.listPublicRooms()))
- .flatMap(_.forall(_.identifier.contains(RoomsLocalDAO.publicPrefix)) shouldBe true)
+ for (dao <- daoFuture; _ <- dao.createRoom("TestRoom", 2);
+ rooms <- dao.listPublicRooms())
+ yield rooms.forall(_.identifier.contains(RoomsLocalDAO.publicPrefix)) shouldBe true
}
}
override protected def publicRoomInfoRetrievalTests(playersNumber: Int): Unit = {
it("should succeed if provided playersNumber is correct") {
- daoFuture.flatMap(_.publicRoomInfo(playersNumber)) flatMap (_._1.participants shouldBe empty)
+ for (dao <- daoFuture; roomInfo <- dao.publicRoomInfo(playersNumber))
+ yield roomInfo._1.participants shouldBe empty
}
it("should show entered players") {
- daoFuture.flatMap(dao => dao.enterPublicRoom(playersNumber)(user, notificationAddress) flatMap (_ => dao.publicRoomInfo(playersNumber)))
- .flatMap(roomInfo => assert(roomInfo._1.participants.contains(user) && roomInfo._2.contains(notificationAddress)))
+ for (dao <- daoFuture; _ <- dao.enterPublicRoom(playersNumber)(user, notificationAddress);
+ roomInfo <- dao.publicRoomInfo(playersNumber);
+ assertion <- assert(roomInfo._1.participants.contains(user) && roomInfo._2.contains(notificationAddress));
+ _ <- cleanUpRoom(playersNumber)) yield assertion
}
describe("should fail") {
- onWrongPlayersNumber(playersNumber => daoFuture.flatMap(_.publicRoomInfo(playersNumber)))
+ onWrongPlayersNumber(playersNumber => daoFuture flatMap (_.publicRoomInfo(playersNumber)))
}
}
override protected def publicRoomExitingTests(playersNumber: Int): Unit = {
it("should succeed if players number is correct and user is inside") {
- daoFuture.flatMap(dao => dao.enterPublicRoom(playersNumber)(user, notificationAddress) flatMap (_ => dao.exitPublicRoom(playersNumber)))
- .flatMap(_ => succeed)
+ for (dao <- daoFuture; _ <- dao.enterPublicRoom(playersNumber)(user, notificationAddress);
+ _ <- dao.exitPublicRoom(playersNumber)) yield succeed
}
it("user should not be inside after it") {
- daoFuture.flatMap(dao => (dao.enterPublicRoom(playersNumber)(user, notificationAddress) flatMap (_ => dao.exitPublicRoom(playersNumber)))
- .flatMap(_ => dao.publicRoomInfo(playersNumber))) flatMap (_._1.participants shouldNot contain(user))
+ for (dao <- daoFuture; _ <- dao.enterPublicRoom(playersNumber)(user, notificationAddress);
+ _ <- dao.exitPublicRoom(playersNumber);
+ roomInfo <- dao.publicRoomInfo(playersNumber)) yield roomInfo._1.participants shouldNot contain(user)
}
describe("should fail") {
- onWrongPlayersNumber(playersNumber => daoFuture.flatMap(_.exitPublicRoom(playersNumber)))
+ onWrongPlayersNumber(playersNumber => daoFuture flatMap (_.exitPublicRoom(playersNumber)))
it("if user is not inside the room") {
- daoFuture.flatMap(_.exitPublicRoom(playersNumber)).shouldFailWith[IllegalStateException]
+ for (dao <- daoFuture; assertion <- dao.exitPublicRoom(playersNumber).shouldFailWith[IllegalStateException])
+ yield assertion
}
it("if user is inside another room") {
- daoFuture.flatMap(dao => dao.enterPublicRoom(playersNumber)(user, notificationAddress) flatMap (_ => dao.exitPublicRoom(playersNumber + 1)))
- .shouldFailWith[IllegalStateException]
+ for (dao <- daoFuture; _ <- dao.enterPublicRoom(playersNumber)(user, notificationAddress);
+ assertion <- dao.exitPublicRoom(playersNumber + 1).shouldFailWith[IllegalStateException];
+ _ <- cleanUpRoom(playersNumber)) yield assertion
}
}
}
@@ -202,26 +224,27 @@ class RoomsLocalDAOTest extends RoomsTesting with BeforeAndAfterEach with Future
val playersNumber = 2
it("should succeed if room is full") {
- daoFuture.flatMap(dao => dao.createRoom(roomName, playersNumber)
- .flatMap(roomID => dao.enterRoom(roomID)(Participant("User1", userAddress), notificationAddress)
- .flatMap(_ => dao.enterRoom(roomID)(Participant("User2", userAddress), notificationAddress))
- .flatMap(_ => dao.deleteRoom(roomID)))) map (_ => succeed)
+ for (dao <- daoFuture; roomID <- dao.createRoom(roomName, playersNumber);
+ _ <- dao.enterRoom(roomID)(randomParticipant, notificationAddress);
+ _ <- dao.enterRoom(roomID)(randomParticipant, notificationAddress);
+ _ <- dao.deleteRoom(roomID)) yield succeed
}
it("should succeed removing the room") {
- daoFuture.flatMap(dao => dao.createRoom(roomName, playersNumber)
- .flatMap(roomID => dao.enterRoom(roomID)(Participant("User1", userAddress), notificationAddress)
- .flatMap(_ => dao.enterRoom(roomID)(Participant("User2", userAddress), notificationAddress))
- .flatMap(_ => dao.deleteRoom(roomID)) flatMap (_ => dao.roomInfo(roomID))))
- .shouldFailWith[NoSuchElementException]
+ for (dao <- daoFuture; roomID <- dao.createRoom(roomName, playersNumber);
+ _ <- dao.enterRoom(roomID)(randomParticipant, notificationAddress);
+ _ <- dao.enterRoom(roomID)(randomParticipant, notificationAddress);
+ _ <- dao.deleteRoom(roomID);
+ assertion <- dao.roomInfo(roomID).shouldFailWith[NoSuchElementException]) yield assertion
}
describe("should fail") {
- onWrongRoomID(roomID => daoFuture.flatMap(_.deleteRoom(roomID)))
+ onWrongRoomID(roomID => daoFuture flatMap (_.deleteRoom(roomID)))
it("if room is not full") {
- daoFuture.flatMap(dao => dao.createRoom(roomName, playersNumber)
- .flatMap(roomID => dao.enterRoom(roomID)(user, notificationAddress) flatMap (_ => dao.deleteRoom(roomID))))
- .shouldFailWith[IllegalStateException]
+ for (dao <- daoFuture; roomID <- dao.createRoom(roomName, playersNumber);
+ _ <- dao.enterRoom(roomID)(user, notificationAddress);
+ assertion <- dao.deleteRoom(roomID).shouldFailWith[IllegalStateException];
+ _ <- cleanUpRoom(roomID)) yield assertion
}
}
}
@@ -230,18 +253,19 @@ class RoomsLocalDAOTest extends RoomsTesting with BeforeAndAfterEach with Future
val playersNumber = 2
it("should succeed if room is full, and recreate an empty public room with same players number") {
- daoFuture.flatMap(dao => dao.enterPublicRoom(playersNumber)(Participant("User1", userAddress), notificationAddress)
- .flatMap(_ => dao.enterPublicRoom(playersNumber)(Participant("User2", userAddress), notificationAddress))
- .flatMap(_ => dao.deleteAndRecreatePublicRoom(playersNumber))
- .flatMap(_ => dao.publicRoomInfo(playersNumber))) map (_._1.participants shouldBe empty)
+ for (dao <- daoFuture; _ <- dao.enterPublicRoom(playersNumber)(randomParticipant, notificationAddress);
+ _ <- dao.enterPublicRoom(playersNumber)(randomParticipant, notificationAddress);
+ _ <- dao.deleteAndRecreatePublicRoom(playersNumber);
+ roomInfo <- dao.publicRoomInfo(playersNumber)) yield roomInfo._1.participants shouldBe empty
}
describe("should fail") {
- onWrongPlayersNumber(playersNumber => daoFuture.flatMap(_.deleteAndRecreatePublicRoom(playersNumber)))
+ onWrongPlayersNumber(playersNumber => daoFuture flatMap (_.deleteAndRecreatePublicRoom(playersNumber)))
it("if room is not full") {
- daoFuture.flatMap(dao => dao.enterPublicRoom(playersNumber)(user, notificationAddress) flatMap (_ => dao.deleteAndRecreatePublicRoom(playersNumber)))
- .shouldFailWith[IllegalStateException]
+ for (dao <- daoFuture; _ <- dao.enterPublicRoom(playersNumber)(user, notificationAddress);
+ assertion <- dao.deleteAndRecreatePublicRoom(playersNumber).shouldFailWith[IllegalStateException];
+ _ <- cleanUpRoom(playersNumber)) yield assertion
}
}
}
@@ -262,7 +286,7 @@ class RoomsLocalDAOTest extends RoomsTesting with BeforeAndAfterEach with Future
test(0).shouldFailWith[IllegalArgumentException]
}
it("if there's no room with such players number") {
- test(20).shouldFailWith[NoSuchElementException]
+ test(TOO_BIG_PLAYERS_NUMBER).shouldFailWith[NoSuchElementException]
}
}
@@ -273,49 +297,47 @@ class RoomsLocalDAOTest extends RoomsTesting with BeforeAndAfterEach with Future
val fakePlayersNumber = 2
it("createRoom") {
- createRoomLocalDAO.flatMap(_.createRoom(fakeRoomName, fakePlayersNumber)).shouldFailWith[IllegalStateException]
+ RoomsLocalDAO().createRoom(fakeRoomName, fakePlayersNumber).shouldFailWith[IllegalStateException]
}
it("enterRoom") {
- createRoomLocalDAO.flatMap(_.enterRoom(fakeRoomID)).shouldFailWith[IllegalStateException]
+ RoomsLocalDAO().enterRoom(fakeRoomID).shouldFailWith[IllegalStateException]
}
it("roomInfo") {
- createRoomLocalDAO.flatMap(_.roomInfo(fakeRoomID)).shouldFailWith[IllegalStateException]
+ RoomsLocalDAO().roomInfo(fakeRoomID).shouldFailWith[IllegalStateException]
}
it("exitRoom") {
- createRoomLocalDAO.flatMap(_.exitRoom(fakeRoomID)).shouldFailWith[IllegalStateException]
+ RoomsLocalDAO().exitRoom(fakeRoomID).shouldFailWith[IllegalStateException]
}
it("listPublicRooms") {
- createRoomLocalDAO.flatMap(_.listPublicRooms()).shouldFailWith[IllegalStateException]
+ RoomsLocalDAO().listPublicRooms().shouldFailWith[IllegalStateException]
}
it("enterPublicRoom") {
- createRoomLocalDAO.flatMap(_.enterPublicRoom(fakePlayersNumber)).shouldFailWith[IllegalStateException]
+ RoomsLocalDAO().enterPublicRoom(fakePlayersNumber).shouldFailWith[IllegalStateException]
}
it("publicRoomInfo") {
- createRoomLocalDAO.flatMap(_.publicRoomInfo(fakePlayersNumber)).shouldFailWith[IllegalStateException]
+ RoomsLocalDAO().publicRoomInfo(fakePlayersNumber).shouldFailWith[IllegalStateException]
}
it("exitPublicRoom") {
- createRoomLocalDAO.flatMap(_.exitPublicRoom(fakePlayersNumber)).shouldFailWith[IllegalStateException]
+ RoomsLocalDAO().exitPublicRoom(fakePlayersNumber).shouldFailWith[IllegalStateException]
}
it("deleteRoom") {
- createRoomLocalDAO.flatMap(_.deleteRoom(fakeRoomID)).shouldFailWith[IllegalStateException]
+ RoomsLocalDAO().deleteRoom(fakeRoomID).shouldFailWith[IllegalStateException]
}
it("deleteAndRecreatePublicRoom") {
- createRoomLocalDAO.flatMap(_.deleteAndRecreatePublicRoom(fakePlayersNumber)).shouldFailWith[IllegalStateException]
+ RoomsLocalDAO().deleteAndRecreatePublicRoom(fakePlayersNumber).shouldFailWith[IllegalStateException]
}
}
}
/**
- * @return the created RoomsLocalDAO to test
+ * Cleans up the provided private room, exiting provided player
*/
- private def createRoomLocalDAO: Future[RoomsLocalDAO] =
- loadLocalDBConfig.map(JDBCClient.createShared(vertx, _))
- .flatMap(client => client.querySingleFuture("DROP SCHEMA PUBLIC CASCADE")
- .map(_ => RoomsLocalDAO()))
+ private def cleanUpRoom(roomID: String)(implicit user: User) =
+ for (dao <- daoFuture; _ <- dao.exitRoom(roomID)(user)) yield ()
/**
- * @return the Future containing local database configuration JSON
+ * Cleans up the provided public room, exiting provided user
*/
- private def loadLocalDBConfig: Future[JsonObject] =
- vertx fileSystem() readFileFuture "database/jdbc_config.json" map (_.toJsonObject)
+ private def cleanUpRoom(playersNumber: Int)(implicit user: User) =
+ for (dao <- daoFuture; _ <- dao.exitPublicRoom(playersNumber)(user)) yield ()
}
diff --git a/services/rooms/src/test/scala/it/cwmp/services/rooms/RoomsServiceVerticleTest.scala b/services/rooms/src/test/scala/it/cwmp/services/rooms/RoomsServiceVerticleTest.scala
index 6159a03d..d8af7e94 100644
--- a/services/rooms/src/test/scala/it/cwmp/services/rooms/RoomsServiceVerticleTest.scala
+++ b/services/rooms/src/test/scala/it/cwmp/services/rooms/RoomsServiceVerticleTest.scala
@@ -4,11 +4,11 @@ import io.vertx.core.buffer.Buffer
import io.vertx.core.http.HttpMethod._
import io.vertx.scala.ext.web.client.{HttpResponse, WebClientOptions}
import it.cwmp.model.Room
+import it.cwmp.model.Room.Converters._
import it.cwmp.services.rooms.ServerParameters._
import it.cwmp.testing.HttpMatchers
import it.cwmp.testing.rooms.RoomsWebServiceTesting
import it.cwmp.utils.VertxClient
-import org.scalatest.Assertion
import scala.concurrent.Future
@@ -27,7 +27,8 @@ class RoomsServiceVerticleTest extends RoomsWebServiceTesting with RoomApiWrappe
val creationApi = API_CREATE_PRIVATE_ROOM_URL
it("should succeed when the user is authenticated and input is valid") {
- createPrivateRoomRequest(roomName, playersNumber) flatMap (res => assert(res.statusCode() == 201 && res.bodyAsString().isDefined))
+ for (response <- createPrivateRoomRequest(roomName, playersNumber);
+ assertion <- assert(response.statusCode() == 201 && response.bodyAsString().isDefined)) yield assertion
}
describe("should fail") {
@@ -48,9 +49,9 @@ class RoomsServiceVerticleTest extends RoomsWebServiceTesting with RoomApiWrappe
override protected def privateRoomEnteringTests(roomName: String, playersNumber: Int): Unit = {
it("should succeed when the user is authenticated, input is valid and room non full") {
- createAPrivateRoomAndGetID(roomName, playersNumber)
- .flatMap(roomID => (enterPrivateRoomRequest(roomID, participantList.head, notificationAddress) shouldAnswerWith 200)
- .flatMap(cleanUpRoomRequest(roomID, _)))
+ for (roomID <- createAPrivateRoomAndGetID(roomName, playersNumber);
+ assertion <- enterPrivateRoomRequest(roomID, participantList.head, notificationAddress) shouldAnswerWith 200;
+ _ <- cleanUpRoom(roomID)) yield assertion
}
describe("should fail") {
@@ -63,32 +64,29 @@ class RoomsServiceVerticleTest extends RoomsWebServiceTesting with RoomApiWrappe
client.put(API_ENTER_PRIVATE_ROOM_URL).addAuthentication.sendJsonFuture("Ciao") shouldAnswerWith 400
}
it("if user is already inside a room") {
- createAPrivateRoomAndGetID(roomName, playersNumber)
- .flatMap(roomID => enterPrivateRoomRequest(roomID, participantList.head, notificationAddress)
- .flatMap(_ => enterPrivateRoomRequest(roomID, participantList.head, notificationAddress) shouldAnswerWith 400)
- .flatMap(cleanUpRoomRequest(roomID, _)))
+ for (roomID <- createAPrivateRoomAndGetID(roomName, playersNumber);
+ _ <- enterPrivateRoomRequest(roomID, participantList.head, notificationAddress);
+ assertion <- enterPrivateRoomRequest(roomID, participantList.head, notificationAddress) shouldAnswerWith 400;
+ _ <- cleanUpRoom(roomID)) yield assertion
}
it("if the room was already filled") {
val playersNumber = 2
- createAPrivateRoomAndGetID(roomName, playersNumber)
- .flatMap(roomID => enterPrivateRoomRequest(roomID, participantList.head, notificationAddress)
- .flatMap(_ => enterPrivateRoomRequest(roomID, participantList(1), notificationAddress)(client, tokenList(1)))
- .flatMap(_ => enterPrivateRoomRequest(roomID, participantList(2), notificationAddress)(client, tokenList(2)))) shouldAnswerWith 404
+ for (roomID <- createAPrivateRoomAndGetID(roomName, playersNumber);
+ _ <- enterPrivateRoomRequest(roomID, participantList.head, notificationAddress);
+ _ <- enterPrivateRoomRequest(roomID, participantList(1), notificationAddress)(client, tokenList(1));
+ assertion <- enterPrivateRoomRequest(roomID, participantList(2), notificationAddress)(client, tokenList(2)) shouldAnswerWith 404)
+ yield assertion
}
}
}
override protected def privateRoomInfoRetrievalTests(roomName: String, playersNumber: Int): Unit = {
it("should succeed if roomId is correct") {
- var roomJson = ""
- createAPrivateRoomAndGetID(roomName, playersNumber)
- .flatMap(roomID => privateRoomInfoRequest(roomID)
- .map(response => {
- import Room.Converters._
- roomJson = Room(roomID, roomName, playersNumber, Seq()).toJson.encode()
- response
- }))
- .flatMap(res => assert(res.statusCode() == 200 && res.bodyAsString().get == roomJson))
+ for (roomID <- createAPrivateRoomAndGetID(roomName, playersNumber);
+ response <- privateRoomInfoRequest(roomID);
+ roomJson = Room(roomID, roomName, playersNumber, Seq()).toJson.encode();
+ assertion <- assert(response.statusCode() == 200 && response.bodyAsString().get == roomJson))
+ yield assertion
}
describe("should fail") {
@@ -98,30 +96,32 @@ class RoomsServiceVerticleTest extends RoomsWebServiceTesting with RoomApiWrappe
override protected def privateRoomExitingTests(roomName: String, playersNumber: Int): Unit = {
it("should succeed if roomID is correct and user inside") {
- createAPrivateRoomAndGetID(roomName, playersNumber)
- .flatMap(roomID => enterPrivateRoomRequest(roomID, participantList.head, notificationAddress)
- .flatMap(_ => exitPrivateRoomRequest(roomID))) shouldAnswerWith 200
+ for (roomID <- createAPrivateRoomAndGetID(roomName, playersNumber);
+ _ <- enterPrivateRoomRequest(roomID, participantList.head, notificationAddress);
+ assertion <- exitPrivateRoomRequest(roomID) shouldAnswerWith 200) yield assertion
}
it("user should not be inside after it") {
- createAPrivateRoomAndGetID(roomName, playersNumber)
- .flatMap(roomID => enterPrivateRoomRequest(roomID, participantList.head, notificationAddress)
- .flatMap(_ => exitPrivateRoomRequest(roomID))
- .flatMap(_ => privateRoomInfoRequest(roomID)))
- .flatMap(res => assert(res.statusCode() == 200 && !res.bodyAsString().get.contains(participantList.head.username)))
+ for (roomID <- createAPrivateRoomAndGetID(roomName, playersNumber);
+ _ <- enterPrivateRoomRequest(roomID, participantList.head, notificationAddress);
+ _ <- exitPrivateRoomRequest(roomID);
+ response <- privateRoomInfoRequest(roomID);
+ assertion <- assert(response.statusCode() == 200 && !response.bodyAsString().get.contains(participantList.head.username)))
+ yield assertion
}
describe("should fail") {
onWrongRoomID(exitPrivateRoomRequest)
it("if user is not inside the room") {
- createAPrivateRoomAndGetID(roomName, playersNumber) flatMap (roomID => exitPrivateRoomRequest(roomID)) shouldAnswerWith 400
+ for (roomID <- createAPrivateRoomAndGetID(roomName, playersNumber);
+ assertion <- exitPrivateRoomRequest(roomID) shouldAnswerWith 400) yield assertion
}
it("if user is inside another room") {
- createAPrivateRoomAndGetID(roomName, playersNumber)
- .flatMap(roomID => createAPrivateRoomAndGetID(roomName, playersNumber)
- .flatMap(otherRoomID => enterPrivateRoomRequest(roomID, participantList.head, notificationAddress)
- .flatMap(_ => exitPrivateRoomRequest(otherRoomID) shouldAnswerWith 400))
- .flatMap(cleanUpRoomRequest(roomID, _)))
+ for (roomID <- createAPrivateRoomAndGetID(roomName, playersNumber);
+ otherRoomID <- createAPrivateRoomAndGetID(roomName, playersNumber);
+ _ <- enterPrivateRoomRequest(roomID, participantList.head, notificationAddress);
+ assertion <- exitPrivateRoomRequest(otherRoomID) shouldAnswerWith 400;
+ _ <- cleanUpRoom(roomID)) yield assertion
}
}
}
@@ -129,23 +129,25 @@ class RoomsServiceVerticleTest extends RoomsWebServiceTesting with RoomApiWrappe
override protected def publicRoomEnteringTests(playersNumber: Int): Unit = {
describe("should succeed") {
it("if room with such players number exists") {
- enterPublicRoomRequest(playersNumber, participantList.head, notificationAddress) shouldAnswerWith 200 flatMap (cleanUpRoomRequest(playersNumber, _))
+ for (assertion <- enterPublicRoomRequest(playersNumber, participantList.head, notificationAddress) shouldAnswerWith 200;
+ _ <- cleanUpRoom(playersNumber)) yield assertion
}
it("and the user should be inside it") {
- (enterPublicRoomRequest(playersNumber, participantList.head, notificationAddress) flatMap (_ => publicRoomInfoRequest(playersNumber)))
- .flatMap(res => assert(res.statusCode() == 200 && res.bodyAsString().get.contains(participantList.head.username)))
- .flatMap(cleanUpRoomRequest(playersNumber, _))
+ for (_ <- enterPublicRoomRequest(playersNumber, participantList.head, notificationAddress);
+ response <- publicRoomInfoRequest(playersNumber);
+ assertion <- assert(response.statusCode() == 200 && response.bodyAsString().get.contains(participantList.head.username));
+ _ <- cleanUpRoom(playersNumber)) yield assertion
}
it("even when the room was filled in past") {
- enterPublicRoomRequest(2, participantList.head, notificationAddress)
- .flatMap(_ => enterPublicRoomRequest(2, participantList(1), notificationAddress)(client, tokenList(1)))
- .flatMap(_ => enterPublicRoomRequest(2, participantList(2), notificationAddress)(client, tokenList(2)))
- .flatMap(_ => publicRoomInfoRequest(2))
- .flatMap(res => assert(res.statusCode() == 200 &&
- !res.bodyAsString().get.contains(participantList.head.username) &&
- !res.bodyAsString().get.contains(participantList(1).username) &&
- res.bodyAsString().get.contains(participantList(2).username)))
- .flatMap(cleanUpRoomRequest(playersNumber, _)(tokenList(2)))
+ for (_ <- enterPublicRoomRequest(2, participantList.head, notificationAddress);
+ _ <- enterPublicRoomRequest(2, participantList(1), notificationAddress)(client, tokenList(1));
+ _ <- enterPublicRoomRequest(2, participantList(2), notificationAddress)(client, tokenList(2));
+ response <- publicRoomInfoRequest(2);
+ assertion <- assert(response.statusCode() == 200 &&
+ !response.bodyAsString().get.contains(participantList.head.username) &&
+ !response.bodyAsString().get.contains(participantList(1).username) &&
+ response.bodyAsString().get.contains(participantList(2).username));
+ _ <- cleanUpRoom(playersNumber)(tokenList(2))) yield assertion
}
}
@@ -159,16 +161,18 @@ class RoomsServiceVerticleTest extends RoomsWebServiceTesting with RoomApiWrappe
client.put(API_ENTER_PUBLIC_ROOM_URL).addAuthentication.sendJsonFuture("Ciao") shouldAnswerWith 400
}
it("if same user is already inside a room") {
- enterPublicRoomRequest(2, participantList.head, notificationAddress)
- .flatMap(_ => enterPublicRoomRequest(3, participantList.head, notificationAddress))
- .shouldAnswerWith(400) flatMap (cleanUpRoomRequest(playersNumber, _))
+ for (_ <- enterPublicRoomRequest(2, participantList.head, notificationAddress);
+ assertion <- enterPublicRoomRequest(3, participantList.head, notificationAddress) shouldAnswerWith 400;
+ _ <- cleanUpRoom(playersNumber)) yield assertion
}
}
}
override protected def publicRoomListingTests(playersNumber: Int): Unit = {
it("should be nonEmpty") {
- listPublicRoomsRequest() flatMap (res => assert(res.statusCode() == 200 && res.bodyAsString().get.nonEmpty))
+ for (response <- listPublicRoomsRequest();
+ assertion <- assert(response.statusCode() == 200 && response.bodyAsString().get.nonEmpty))
+ yield assertion
}
}
@@ -177,9 +181,10 @@ class RoomsServiceVerticleTest extends RoomsWebServiceTesting with RoomApiWrappe
publicRoomInfoRequest(playersNumber) shouldAnswerWith 200
}
it("should show entered players") {
- (enterPublicRoomRequest(playersNumber, participantList.head, notificationAddress) flatMap (_ => publicRoomInfoRequest(playersNumber)))
- .flatMap(res => assert(res.statusCode() == 200 && res.bodyAsString().get.contains(participantList.head.username)))
- .flatMap(cleanUpRoomRequest(playersNumber, _))
+ for (_ <- enterPublicRoomRequest(playersNumber, participantList.head, notificationAddress);
+ response <- publicRoomInfoRequest(playersNumber);
+ assertion <- assert(response.statusCode() == 200 && response.bodyAsString().get.contains(participantList.head.username));
+ _ <- cleanUpRoom(playersNumber)) yield assertion
}
describe("should fail") {
@@ -189,13 +194,15 @@ class RoomsServiceVerticleTest extends RoomsWebServiceTesting with RoomApiWrappe
override protected def publicRoomExitingTests(playersNumber: Int): Unit = {
it("should succeed if players number is correct and user is inside") {
- (enterPublicRoomRequest(playersNumber, participantList.head, notificationAddress) flatMap (_ => exitPublicRoomRequest(playersNumber))) shouldAnswerWith 200
+ for (_ <- enterPublicRoomRequest(playersNumber, participantList.head, notificationAddress);
+ assertion <- exitPublicRoomRequest(playersNumber) shouldAnswerWith 200) yield assertion
}
it("user should not be inside after it") {
- enterPublicRoomRequest(playersNumber, participantList.head, notificationAddress)
- .flatMap(_ => exitPublicRoomRequest(playersNumber))
- .flatMap(_ => publicRoomInfoRequest(playersNumber))
- .flatMap(res => assert(res.statusCode() == 200 && !res.bodyAsString().get.contains(participantList.head.username)))
+ for (_ <- enterPublicRoomRequest(playersNumber, participantList.head, notificationAddress);
+ _ <- exitPublicRoomRequest(playersNumber);
+ response <- publicRoomInfoRequest(playersNumber);
+ assertion <- assert(response.statusCode() == 200 && !response.bodyAsString().get.contains(participantList.head.username)))
+ yield assertion
}
describe("should fail") {
@@ -205,8 +212,9 @@ class RoomsServiceVerticleTest extends RoomsWebServiceTesting with RoomApiWrappe
exitPublicRoomRequest(playersNumber) shouldAnswerWith 400
}
it("if user is inside another room") {
- (enterPublicRoomRequest(2, participantList.head, notificationAddress) flatMap (_ => exitPublicRoomRequest(3)))
- .shouldAnswerWith(400) flatMap (cleanUpRoomRequest(playersNumber, _))
+ for (_ <- enterPublicRoomRequest(2, participantList.head, notificationAddress);
+ assertion <- exitPublicRoomRequest(3) shouldAnswerWith 400;
+ _ <- cleanUpRoom(playersNumber)) yield assertion
}
}
}
@@ -233,16 +241,14 @@ class RoomsServiceVerticleTest extends RoomsWebServiceTesting with RoomApiWrappe
client.request(apiCall._1, apiCall._2).addAuthentication("token").sendFuture() shouldAnswerWith 401
}
it(s"if token isn't valid, doing ${apiCall._1.toString} on ${apiCall._2}") {
- client.request(apiCall._1, apiCall._2).addAuthentication("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VybmFtZSI6InRpemlvIn0.f6eS98GeBmPau4O58NwQa_XRu3Opv6qWxYISWU78F68")
+ client.request(apiCall._1, apiCall._2)
+ .addAuthentication("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VybmFtZSI6InRpemlvIn0.f6eS98GeBmPau4O58NwQa_XRu3Opv6qWxYISWU78F68")
.sendFuture() shouldAnswerWith 401
}
}
}
}
- /**
- * Describes the behaviour of private api when the roomID is wrong
- */
override protected def onWrongRoomID(apiCall: String => Future[_]): Unit = {
it("if roomID is empty") {
apiCall("").mapTo[HttpResponse[Buffer]] shouldAnswerWith 400
@@ -252,15 +258,12 @@ class RoomsServiceVerticleTest extends RoomsWebServiceTesting with RoomApiWrappe
}
}
- /**
- * Describes the behaviour of public api when called with wrong players number
- */
override protected def onWrongPlayersNumber(apiCall: Int => Future[_]): Unit = {
it("if players number provided less than 2") {
apiCall(0).mapTo[HttpResponse[Buffer]] shouldAnswerWith 400
}
it("if room with such players number doesn't exist") {
- apiCall(20).mapTo[HttpResponse[Buffer]] shouldAnswerWith 404
+ apiCall(TOO_BIG_PLAYERS_NUMBER).mapTo[HttpResponse[Buffer]] shouldAnswerWith 404
}
}
@@ -268,18 +271,18 @@ class RoomsServiceVerticleTest extends RoomsWebServiceTesting with RoomApiWrappe
* Shorthand to create a room and get it's id from response
*/
private def createAPrivateRoomAndGetID(roomName: String, playersNumber: Int) = {
- createPrivateRoomRequest(roomName, playersNumber) map (_.bodyAsString().get)
+ for (response <- createPrivateRoomRequest(roomName, playersNumber)) yield response.bodyAsString().get
}
/**
* Cleans up the provided room, exiting the user with passed token
*/
- private def cleanUpRoomRequest(roomID: String, assertion: Assertion)(implicit userToken: String) =
- exitPrivateRoomRequest(roomID)(client, userToken) map (_ => assertion)
+ override protected def cleanUpRoom(roomID: String)(implicit userToken: String): Future[Unit] =
+ for (_ <- exitPrivateRoomRequest(roomID)(client, userToken)) yield ()
/**
* Cleans up the provided public room, exiting player with passed token
*/
- private def cleanUpRoomRequest(playersNumber: Int, assertion: Assertion)(implicit userToken: String) =
- exitPublicRoomRequest(playersNumber)(client, userToken) map (_ => assertion)
+ override protected def cleanUpRoom(playersNumber: Int)(implicit userToken: String): Future[Unit] =
+ for (_ <- exitPublicRoomRequest(playersNumber)(client, userToken)) yield ()
}
diff --git a/services/rooms/src/test/scala/it/cwmp/testing/rooms/RoomsTesting.scala b/services/rooms/src/test/scala/it/cwmp/testing/rooms/RoomsTesting.scala
index 28d1ccfc..47846a62 100644
--- a/services/rooms/src/test/scala/it/cwmp/testing/rooms/RoomsTesting.scala
+++ b/services/rooms/src/test/scala/it/cwmp/testing/rooms/RoomsTesting.scala
@@ -1,18 +1,19 @@
package it.cwmp.testing.rooms
import it.cwmp.testing.VertxTest
+import it.cwmp.testing.rooms.RoomsTesting.{privateRoomPlayersNumber, publicRoomPlayersNumber, roomName}
import org.scalatest.Matchers
import scala.concurrent.Future
/**
* A base class that provides common tests structure for Rooms
+ *
+ * @author Enrico Siboni
*/
abstract class RoomsTesting extends VertxTest with Matchers {
- private val roomName = "Stanza"
- private val privateRoomPlayersNumber = 2
- private val publicRoomPlayersNumber = 2
+ protected val TOO_BIG_PLAYERS_NUMBER = 20
describe("Private Room") {
describe("Creation") {
@@ -76,3 +77,12 @@ abstract class RoomsTesting extends VertxTest with Matchers {
*/
protected def onWrongPlayersNumber(test: Int => Future[_])
}
+
+/**
+ * Companion object
+ */
+object RoomsTesting {
+ private val roomName = "Stanza"
+ private val privateRoomPlayersNumber = 2
+ private val publicRoomPlayersNumber = 2
+}
\ No newline at end of file
diff --git a/services/rooms/src/test/scala/it/cwmp/testing/rooms/RoomsWebServiceTesting.scala b/services/rooms/src/test/scala/it/cwmp/testing/rooms/RoomsWebServiceTesting.scala
index 77535b7d..215ba4a3 100644
--- a/services/rooms/src/test/scala/it/cwmp/testing/rooms/RoomsWebServiceTesting.scala
+++ b/services/rooms/src/test/scala/it/cwmp/testing/rooms/RoomsWebServiceTesting.scala
@@ -21,10 +21,12 @@ import scala.concurrent.Future
*/
abstract class RoomsWebServiceTesting extends RoomsTesting with VerticleBeforeAndAfterEach {
+ private val participantAddress = "http://127.0.1.1:8668/api/client/pFU9qOCU3kmYqwk1qqkl/room/participants"
+
protected val participants: Map[String, Participant] = Map(
- "CORRECT_TOKEN_1" -> Participant("Enrico1", "http://127.0.1.1:8668/api/client/pFU9qOCU3kmYqwk1qqkl/room/participants"),
- "CORRECT_TOKEN_2" -> Participant("Enrico2", "http://127.0.1.1:8668/api/client/pFU9qOCU3kmYqwk1qqkl/room/participants"),
- "CORRECT_TOKEN_3" -> Participant("Enrico3", "http://127.0.1.1:8668/api/client/pFU9qOCU3kmYqwk1qqkl/room/participants"))
+ "CORRECT_TOKEN_1" -> Participant("Enrico1", participantAddress),
+ "CORRECT_TOKEN_2" -> Participant("Enrico2", participantAddress),
+ "CORRECT_TOKEN_3" -> Participant("Enrico3", participantAddress))
protected implicit val defaultParticipant: Participant = participants.values.head
protected val participantList: List[Participant] = participants.values.toList
protected implicit val defaultToken: String = participants.keys.head
@@ -62,7 +64,17 @@ abstract class RoomsWebServiceTesting extends RoomsTesting with VerticleBeforeAn
*/
private case class TestRoomReceiverApiWrapper() extends RoomReceiverApiWrapper {
override def sendParticipants(clientAddress: String, toSend: Seq[Participant]): Future[Unit] =
- Future.successful(Unit)
+ Future.successful(())
}
+ /**
+ * Cleans up the provided room, exiting the user with passed token
+ */
+ protected def cleanUpRoom(roomID: String)(implicit userToken: String): Future[_]
+
+ /**
+ * Cleans up the provided public room, exiting player with passed token
+ */
+ protected def cleanUpRoom(playersNumber: Int)(implicit userToken: String): Future[_]
+
}