scala: play-framework future execution sequence within a single request - scala

I am quite new with functional programming in scala and play-framework
I have an API request that need to add a chapter to a comic book authored by that particular user, thus I have to verify that the comic exist and belongs to that user. What complicates is the Future, Option etc and I can't make sure that the last command is executed last. Indeed, my problem occurs because the last command is executed before all my db access is completed
Here are the code, hope it is clear
def addchapter(): Action[AnyContent] = Action.async { implicit request =>
var hashMap: HashMap[String, JsValue] = HashMap()
var apiResult: ApiResult = ApiResult(
ReturnCode.COMIC_ADDCHAPTER.id,
ReturnCode.COMIC_ADDCHAPTER.toString(),
ReturnResult.RESULT_ERROR.toString(),
"init"
)
var fHashMap: Future[HashMap[String, JsValue]] = Future {
hashMap
}
try {
val oReq = request.body.asJson
val jsReq = oReq.getOrElse(JsString("null"))
val sessionid = (jsReq \
"sessionid").getOrElse(JsString("0")).as[String]
val comicid = (jsReq \
"comicid").getOrElse(JsString("comicid")).as[String]
val foUser = userRepo.getUserBySessionId(sessionid)
LogManager.DebugLog(this, "add chapter: " + sessionid + " => " +
comicid)
fHashMap = foUser.flatMap( oUser => {
oUser match {
case Some(user) => {
val foComic = comicRepo.getComicByComicId(comicid)
fHashMap = foComic.flatMap( oComic => {
oComic match {
case Some(comic) => {
LogManager.DebugLog(this, "about to add chapter")
val fTup = comicRepo.addChapterToComic(comic)
fHashMap = fTup.map( tuple => {
val wr = tuple._1
val mc = tuple._2
apiResult = ApiResult(
ReturnCode.COMIC_ADDCHAPTER.id,
ReturnCode.COMIC_ADDCHAPTER.toString(),
ReturnResult.RESULT_ERROR.toString(),
"successfully added chapter!"
)
val payloadcomic =
PayloadComicFactory.createWithComic(mc)
hashMap("apiresult") = Json.toJson(apiResult)
hashMap += "comic" -> Json.toJson(payloadcomic)
LogManager.DebugLog(this, "successfully added
chapter!")
hashMap
})
// return
fHashMap
}
case None => {
apiResult = ApiResult(
ReturnCode.COMIC_ADDCHAPTER.id,
ReturnCode.COMIC_ADDCHAPTER.toString(),
ReturnResult.RESULT_ERROR.toString(),
"comic not found"
)
hashMap("apiresult") = Json.toJson(apiResult)
Future { hashMap }
}
}
})
Future { hashMap }
}
case None => {
apiResult = ApiResult(
ReturnCode.COMIC_ADDCHAPTER.id,
ReturnCode.COMIC_ADDCHAPTER.toString(),
ReturnResult.RESULT_ERROR.toString(),
"unauthorized to add chapter to this comic"
)
hashMap("apiresult") = Json.toJson(apiResult)
Future { hashMap }
}
}
})
// I cannot put the return here, it is compile error saying that the return value is a Unit
// fHashMap.map( hashmap => {
// Ok(Json.toJson(hashmap))
// })
} catch {
case t: Throwable => {
LogManager.DebugException(this, "ex: ", t)
// I cannot put it here, it is compile error saying that the return value is Unit
// fHashMap.map( hashmap => {
// Ok(Json.toJson(hashmap))
// })
fHashMap
}
}
// I have to return here, but "return of the request" will be executed first before all my db access is completed, thus the ApiResult is still returning wrong state
LogManager.DebugLog(this, "return of the request")
fHashMap.map( hashmap => {
Ok(Json.toJson(hashmap))
})
}

Related

how should I optimise the scala code and make it more readable [closed]

Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 2 years ago.
Improve this question
I have this ugly massive piece of code. It does many things, mostly around querying databases. Each query depends on the result of the previous query or on the parameters of the incoming request body. Queries are done asynchronously using Future. The code works but is not readable. How can I restructure it to make it concise? I have thought of using for instead of map and flatMap but due to dependencies on previous queries, I am not able to figure out how to use result of previous Future in new ones and how to handle error paths.
I am using play 2.6 and scala 2.12.2
def oldSignupUser:Action[AnyContent] = silhouette.UserAwareAction.async {
implicit request => {
val body: AnyContent = request.body
val jsonBody: Option[JsValue] = body.asJson
jsonBody match {
case Some(json) => {
val userProfile: Option[UserProfile] = json.asOpt[UserProfile] userProfile match {
case Some(profile) => {
val loginInfo = LoginInfo(CredentialsProvider.ID, profile.externalProfileDetails.email)
val userKeys = UserKeys(utilities.bucketIDFromEmail(profile.externalProfileDetails.email),profile.externalProfileDetails.email,loginInfo,profile.externalProfileDetails.firstName,profile.externalProfileDetails.lastName)
val findUserFuture: Future[Option[User]] = userRepo.findOne(userKeys) // userFuture will eventually contain the result of database query i.e Some(user) or None
findUserFuture.flatMap { (userOption: Option[User]) =>
userOption match {
case Some(user) => {
if(user.profile.internalProfileDetails.get.confirmed) {
Future {
Ok(Json.toJson(JsonResultError(messagesApi("error.duplicateUser")(langs.availables(0)))))
}
} else {
val userKeys = UserKeys(utilities.bucketIDFromEmail(user.profile.externalProfileDetails.email),user.profile.externalProfileDetails.email,loginInfo,user.profile.externalProfileDetails.firstName,user.profile.externalProfileDetails.lastName)
val userToken:UserToken = utilities.createUserToken(user.id,userKeys,UserTokenType.RegistrationConfirmation)
val userTokenSaveFuture:Future[Option[UserToken]] = userTokenRepo.save(userToken)
logger.trace(s"user token save future ${userTokenSaveFuture}")
userTokenSaveFuture.map( (userTokenOption:Option[UserToken])=>{
userTokenOption match {
case Some(userToken) => {
val signupEmailOption:Option[SignupEmail] = createEmailMessageForUserToken(userToken)
signupEmailOption match {
case Some(signupEmail:SignupEmail) =>{
val _:String = mailerService.sendEmail(signupEmail.subject, signupEmail.from,List(user.profile.externalProfileDetails.email),None,Some(signupEmail.html))
Ok(Json.toJson(JsonResultSuccess(messagesApi("success.userSignupConfirmationEmailSent")(langs.availables(0)))))
}
case None =>{
InternalServerError(Json.toJson(JsonResultError("Internal Server Error")))
}
}
}
case None => {
Ok(Json.toJson(JsonResultError("user not added"))) //Todom - this is misleading as user is added but token isn't
}
}
})
}
}
case None => {
val passwordInfo:PasswordInfo = userRepo.hashPassword(profile.externalProfileDetails.password.get)
val bucketId = utilities.bucketIDFromEmail(profile.externalProfileDetails.email)
val newUser:User = User(
utilities.getUniqueID(),//UUID.randomUUID(),
UserProfile(Some(InternalUserProfile(loginInfo,bucketId,false,Some(passwordInfo))),
profile.externalProfileDetails))
val saveUserFuture:Future[Option[User]] = userRepo.save(newUser)
saveUserFuture.flatMap { (userOption:Option[User]) =>{ userOption match {
case Some(user) => {
val initialPortfolio = user.profile.externalProfileDetails.portfolio
val profileAndPortfolio = profile.externalProfileDetails.copy(portfolio = initialPortfolio)
logger.trace(s"saving external profile and portfolio ${profileAndPortfolio}")
val savedProfileAndPortfolioOptionFuture = userProfileAndPortfolioRepo.save(profileAndPortfolio)
savedProfileAndPortfolioOptionFuture.flatMap(profileAndPortfolioOption =>{
profileAndPortfolioOption match {
case Some(profileAndPortfolio) => {
val userKeys = UserKeys(utilities.bucketIDFromEmail(user.profile.externalProfileDetails.email),user.profile.externalProfileDetails.email,loginInfo,user.profile.externalProfileDetails.firstName,user.profile.externalProfileDetails.lastName)
val userToken:UserToken = utilities.createUserToken(user.id,userKeys,UserTokenType.RegistrationConfirmation)
val userTokenSaveFuture:Future[Option[UserToken]] = userTokenRepo.save(userToken)
userTokenSaveFuture.flatMap( (userTokenOption:Option[UserToken])=>{
userTokenOption match {
case Some(userToken) => {
val signupEmailOption = createEmailMessageForUserToken(userToken)
signupEmailOption match {
case Some(signupEmail) =>{
val _:String = mailerService.sendEmail(signupEmail.subject,signupEmail.from,List(user.profile.externalProfileDetails.email),None,Some(signupEmail.html))
Future{Ok(Json.toJson(JsonResultSuccess((messagesApi("success.userSignupConfirmationEmailSent"))(langs.availables(0)))))}
}
case None =>{
Future{InternalServerError(Json.toJson(JsonResultError("Internal Server Error")))}
}
}
}
case None => {
Future{Ok(Json.toJson(JsonResultError("user not added"))) }
}
}
})
}
case None =>{
Future{InternalServerError(Json.toJson(JsonResultError("Internal Server Error")))}
}
}
})
}
case None => {
Future{Ok(Json.toJson(JsonResultError("unable to add user")))}
}
}
}
}
.recover { case x => {
x match {
case _:EmailException =>InternalServerError(Json.toJson(JsonResultError("The server encountered internal error and couldn't sent email to the email id.")))
case _ => InternalServerError(Json.toJson(JsonResultError("Internal Server Error")))
}
}
}
}
}
}
.recover { case x => {
logger.trace("Future failed in signupUser. In recover. Returning Internal Server Error"+x)
x match {
case _:EmailException =>InternalServerError(Json.toJson(JsonResultError("The server encountered internal error and couldn't sent email to the email id.")))
case _ => InternalServerError(Json.toJson(JsonResultError("Internal Server Error")))
}
}
}
}
case None => Future {
logger.trace("invalid profile structure")
Ok(Json.toJson(JsonResultError(messagesApi("error.incorrectBodyStructure")(langs.availables(0))))) }
}
}
case None => Future {
Ok(Json.toJson(JsonResultError(messagesApi("error.incorrectBodyType")(langs.availables(0))))) }
}
}
}
Update - the question was closed before I could solve the problem. Many thanks to mfirry and tim for their answers. this is the version I could come up with which I believe is modular.
def getUserProfileFromBody(json:JsValue): Option[UserProfile] ={
val userProfile = json.asOpt[UserProfile] //check if json conforms with UserProfile structure
userProfile
}
def getJsonBody(body:AnyContent) = {
val jsonBody: Option[JsValue] = body.asJson
jsonBody
}
def generateUserKeysFromUserProfile(profile:UserProfile):UserKeys = {
val loginInfo = LoginInfo(/*provider id eg "credentials"*/CredentialsProvider.ID, /*provider Key eg email*/profile.externalProfileDetails.email)
val userKeys = UserKeys(utilities.bucketIDFromEmail(profile.externalProfileDetails.email),profile.externalProfileDetails.email,loginInfo,profile.externalProfileDetails.firstName,profile.externalProfileDetails.lastName)
logger.trace(s"generated userkey ${userKeys}")
userKeys
}
def findIfUserIsNewOrExisting(userKey:UserKeys): Future[Boolean] ={
logger.trace(s"looking for user with keys ${userKey}")
val findUserFuture = userRepo.findOne(userKey) // userFuture will eventually contain the result of database query i.e Some(user) or None
logger.trace(s"user future is ${findUserFuture}")
for(userOption <- findUserFuture) yield {
logger.trace(s"user option ${userOption}")
userOption match {
case Some(user) => {
val userConfirmed = isUserConfirmed(user)
if(userConfirmed) {
throw new DuplicateUserException(user,"duplicateuser", new Throwable("duplicateuser"))
} else throw new UnconfirmedUserException(user,"unconfirmeduser", new Throwable("unconfirmeduser"))
}
case None => {
false
}
}
}
}
def returnError(error:String) = {
println(s"returning error ${error}")
Future {
/*
note the distintion between langs and messages. Lang means languages this application supports
eg English, French
Messages are the messages defined per language. Eg app.title is a message defined inn English. It might not be defined in French
*/
/*logger.trace("langs array"+langs.availables) //languages available
logger.trace("app.title: "+messagesApi.isDefinedAt("app.title")(langs.availables(0)))//pick the first language and see taht app.title is defined in it
logger.trace("error: "+messagesApi.isDefinedAt("error.incorrectBodyType")(langs.availables(0)))//see taht error.incorrectBodyType is defined in the 1st language
*/
Ok(Json.toJson(JsonResultError(error)))
}/*TODOM - Standardise error messages. Use as constants*/
}
def isUserConfirmed(user:User):Boolean = user.profile.internalProfileDetails.get.confirmed
def sendConfirmationTokenForUser(user:User) = {
val userKeys = generateUserKeysFromUserProfile(user.profile)
//for this user, create a token which could be sent in the email for verification
val userToken:UserToken = utilities.createUserToken(user.id,userKeys,UserTokenType.RegistrationConfirmation)
logger.trace(s"saving token ${userToken}")
val userTokenSaveFuture:Future[Option[UserToken]] = userTokenRepo.save(userToken)
logger.trace(s"user token save future ${userTokenSaveFuture}")
for(userTokenOption <- userTokenSaveFuture) yield {
logger.trace(s"user token ${userTokenOption}")
userTokenOption match {
case Some(userToken) => {
val signupEmailOption:Option[SignupEmail] = createEmailMessageForUserToken(userToken)
signupEmailOption match {
case Some(signupEmail:SignupEmail) =>{
val email = mailerService.sendEmail(signupEmail.subject, signupEmail.from,List(user.profile.externalProfileDetails.email),None,Some(signupEmail.html))
println(s"sent email message ${email}")
email
//Ok(Json.toJson(JsonResultSuccess(messagesApi("success.userSignupConfirmationEmailSent")(langs.availables(0)))))
}
case None =>{
logger.trace("unable to create html response for email confirmation")
//InternalServerError(Json.toJson(JsonResultError("Internal Server Error")))
throw EmailMessageCreationException("errorInCreatingHTML", new Throwable("errorInCreatingHTML"))
}
}
}
case None => {
logger.trace("error in adding token")
throw EmailTokenSaveException("emailTokenSaveException", new Throwable("emailTokenSaveException"))
//Ok(Json.toJson(JsonResultError("user not added"))) //Todom - this is misleading as user is added but token isn't
}
}
}
}
def addUserToDatabase(user:User) = {
println(s"saving user ${user}")
val saveUserFuture:Future[Option[User]] = userRepo.save(user)
for(userOption <- saveUserFuture) yield {
userOption match {
case Some(user) => {
logger.trace("user added successfully "+user)
user
}
case None => throw new NewUserAdditionException("newUserAdditionError", new Throwable("newUserAdditionError"))
}
}
}
def addNewUser(profile:UserProfile) = {
logger.trace(s"new user sign up request with profile ${profile}")
////NOTE - //salt is empty for BCryptSha256PasswordHasher. The 'hash' method of BCryptSha256PasswordHasher does not return the salt separately because it is embedded in the hashed password.
//should creation of passwordInfo be moved to UserRepo? Not sure.
//A profile associated with the credentials provider stores a Silhouette PasswordInfo object holding the hashed password
//val passwordInfo:PasswordInfo = userRepo.passwordHasher.hash(profile.externalProfileDetails.password.get)
val passwordInfo: PasswordInfo = userRepo.hashPassword(profile.externalProfileDetails.password.get)
//logger.trace("password info is ",passwordInfo)
val bucketId = utilities.bucketIDFromEmail(profile.externalProfileDetails.email)
val loginInfo = LoginInfo(/*provider id eg "credentials"*/ CredentialsProvider.ID, /*provider Key eg email*/ profile.externalProfileDetails.email)
val newUser: User = User(
utilities.getUniqueID(), //UUID.randomUUID(),
UserProfile(Some(InternalUserProfile(loginInfo, bucketId, false, Some(passwordInfo))),
profile.externalProfileDetails))
logger.trace("adding new user" + newUser)
addUserToDatabase(newUser)
}
def saveProfileAndPortfolio(profileAndPortfolio:ExternalUserProfile) = {
logger.trace(s"saving external profile and portfolio ${profileAndPortfolio}")
val savedProfileAndPortfolioOptionFuture = userProfileAndPortfolioRepo.save(profileAndPortfolio)
for(savedProfileAndPortfolio <- savedProfileAndPortfolioOptionFuture) yield {
savedProfileAndPortfolio match {
case Some(profileAndPortfolio) => profileAndPortfolio
case None => throw ProfileAndPortfolioAdditionException("profileAndPortfolioAdditionException",new Throwable("profileAndPortfolioAdditionException"))
}
}
}
def createUserProfileAndPortfolioInformation(user:User) = {
val profile = user.profile
val initialPortfolio = user.profile.externalProfileDetails.portfolio //Some(TagsOfInterestToAUserAPI(Set(),Set(),Set()))
val profileAndPortfolio = profile.externalProfileDetails.copy(portfolio = initialPortfolio)
saveProfileAndPortfolio(profileAndPortfolio)
}
def signupUser = silhouette.UserAwareAction.async {
implicit request => {
logger.trace(s"received request ${request}")
val jsonBody = getJsonBody(request.body)
/*
TODOM - testcase - check what happens if some other body type is sent.
*/
jsonBody match {
case Some(json) => { //got json in message body.
//TODOM - convert to pretty print only if logger level is trace
val readableString: String = Json.prettyPrint(json)
logger.trace(s"received Json ${readableString}")
val userProfile = getUserProfileFromBody(json)
userProfile match {
case Some(profile) => { //json conforms to UserProfile.
logger.trace(s"received correct profile structure ${profile}")
val userKeys = generateUserKeysFromUserProfile(profile)
val res = for{isNewUser <- findIfUserIsNewOrExisting(userKeys) //this will throw error if user is duplicate
newUserDetails <- addNewUser(profile)
profileAndPortfolioInfo <- createUserProfileAndPortfolioInformation(newUserDetails)
confirmationEmail <- sendConfirmationTokenForUser(newUserDetails)} yield {
Ok(Json.toJson(JsonResultSuccess((messagesApi("success.userSignupConfirmationEmailSent"))(langs.availables(0)))))
}
res.recover {
case exception: DuplicateUserException => Ok(Json.toJson(JsonResultError(messagesApi("error.duplicateUser")(langs.availables(0)))))
case exception: UnconfirmedUserException => {
sendConfirmationTokenForUser(exception.user)
Ok(Json.toJson(JsonResultSuccess((messagesApi("success.userSignupConfirmationEmailSent"))(langs.availables(0)))))
}
case _:EmailException =>InternalServerError(Json.toJson(JsonResultError("The server encountered internal error and couldn't sent email to the email id.")))
case exception:ProfileAndPortfolioAdditionException => Ok(Json.toJson(JsonResultError("user not added")))
case _:EmailMessageCreationException => Ok(Json.toJson(JsonResultError("user not added")))
case _: EmailTokenSaveException=> Ok(Json.toJson(JsonResultError("user not added")))
case x => {
logger.error(s"unknown exception ${x}")
InternalServerError(Json.toJson(JsonResultError("Internal Server Error")))
}
}
res
}
//Json doesn't conform to UserProfile
case None => Future {
logger.trace("invalid profile structure")
Ok(Json.toJson(JsonResultError(messagesApi("error.incorrectBodyStructure")(langs.availables(0))))) } /*TODOM - Standardise error messages. Use as constants*/
}
}
//message body is not json. Error.
//TODOM - langs contains array of lang. picking the 1st one but would need too pick based on locale.
//langs.availables(0) maps to array defined in application.conf eg. - play.i18n.langs = [ "en", "en-US", "fr" ]
case None =>{
logger.trace("incorrect body type")
returnError(messagesApi("error.incorrectBodyType")(langs.availables(0)))
}
}
}
}
The most obvious change is to put code in functions. By breaking up the logic into functions with meaningful names you make the code much clearer to read. If you define them as local functions within the method they can access context from the method without it having to be passed in as parameters.
Put all the error returns in static vals outside the method rather than creating them each time.
Consider using Option.fold rather than match:
option.fold(error){ result =>
// Further code
}
Remove the {} after case x =>, they are not required.
The first case in case x => { x match { is redundant.
And don't worry about performance until you can prove that the performance of this section of code has a material effect on the behaviour of the application.
One problem is that you are not using the Failure case of Future to indicate failure so you can't use map/flatMap to chain operations that might fail.
Create your own subclass of Exception and have the methods return Future.failed(MyException(error)) on failure rather than Future(error)
Then do this
val res =
for {
res1 <- futureAction1
res2 <- futureAction2(res1)
res3 <- futureAction3(res2)
} yield {
res3
}
and finally
res.recover{ case MyException(err) => err }
The for will stop at the first Failure return value, and the recover will turn this into the appropriate Success value.

How to use `flatMap` and `map` to fill a `list` on Play framework + Scala?

I am struggling to use flatMap and map with Play framework + Scala. This method has a bunch of other issues, but I am trying to go through them one for each time. The first thing that I cannot figure out how to implement is how to fill a Seq inside nested flatMap and map and return a Json output. Here is my method:
def getRacks(at: String) = Action.async { implicit request: Request[AnyContent] =>
var rackSeq: Seq[Rack] = Seq.empty
var gpuSeq: Seq[Gpu] = Seq.empty
rackRepository.get(Util.toTime(at)).flatMap { resultRack: Seq[RackRow] =>
resultRack.map { r: RackRow =>
gpuRepository.getByRack(r.id).map { result: Seq[GpuRow] =>
result.map { gpuRow: GpuRow =>
gpuSeq = gpuSeq :+ Gpu(gpuRow.id, gpuRow.rackId, gpuRow.produced, Util.toDate(gpuRow.installedAt))
println(gpuRow)
}
}
val rack = Rack(r.id, r.produced, Util.toDate(r.currentHour), gpuSeq)
rackSeq = rackSeq :+ rack
}
println("rackSeq: " + rackSeq)
Future.successful(Ok(Json.toJson(rackSeq)).as(JSON))
}.recover {
case pe: ParseException => BadRequest(Json.toJson("Error on parse String to time."))
case e: Exception => BadRequest(Json.toJson("Error to get racks."))
case _ => BadRequest(Json.toJson("Unknow error to get racks."))
}
}
I was expecting that rackSeq will be filled with GpuRow. but my output is like this:
rackSeq: List(Rack(rack-1,0.2,2018-01-23T14:15:00.79Z,List()))
GpuRow(rack-1-gpu-0,rack-1,0.2,1515867048515)
How to evaluate both lists to the output?
Instead of mutating variables, stay within the context of a Future and perform transformations until you reach the desired result. Assuming the following types...
rackRepository.get(Util.toTime(at)) // Future[Seq[RackRow]]
gpuRepository.getByRack(r.id) // Future[Seq[GpuRow]]
...you could do this instead:
def gpuRowToGpu(gpuRow: GpuRow): Gpu = {
Gpu(gpuRow.id, gpuRow.rackId, gpuRow.produced, Util.toDate(gpuRow.installedAt))
}
def getRacks(at: String) = Action.async { implicit request: Request[AnyContent] =>
rackRepository.get(Util.toTime(at)).flatMap { resultRack: Seq[RackRow] =>
val seqFutRack: Seq[Future[Rack]] = resultRack.map { r: RackRow =>
gpuRepository.getByRack(r.id).map { result: Seq[GpuRow] =>
val gpus = result.map(gpuRowToGpu) // Seq[Gpu]
Rack(r.id, r.produced, Util.toDate(r.currentHour), gpus)
} // Future[Rack]
}
val futSeqRack: Future[Seq[Rack]] = Future.sequence(seqFutRack)
futSeqRack.map(racks => Ok(Json.toJson(racks)).as(JSON))
}.recover {
...
}
}

scala returns doesn't conform to required S_

I got the error
found : scala.concurrent.Future[Option[models.ProcessTemplatesModel]]
required: Option[models.ProcessTemplatesModel]
My function is below
def createCopyOfProcessTemplate(processTemplateId: Int): Future[Option[ProcessTemplatesModel]] = {
val action = processTemplates.filter(_.id === processTemplateId).result.map(_.headOption)
val result: Future[Option[ProcessTemplatesModel]] = db.run(action)
result.map { case (result) =>
result match {
case Some(r) => {
var copy = (processTemplates returning processTemplates.map(_.id)) += ProcessTemplatesModel(None, "[Copy of] " + r.title, r.version, r.createdat, r.updatedat, r.deadline, r.status, r.comment, Some(false), r.checkedat, Some(false), r.approvedat, false, r.approveprocess, r.trainingsprocess)
val composedAction = copy.flatMap { id =>
processTemplates.filter(_.id === id).result.headOption
}
db.run(composedAction)
}
}
}
}
what is my problem in this case?
edit:
my controller function looks like this:
def createCopyOfProcessTemplate(processTemplateId: Int) = Action.async {
processTemplateDTO.createCopyOfProcessTemplate(processTemplateId).map { process =>
Ok(Json.toJson(process))
}
}
Is there my failure?
According to the your code - there are the following issues:
You use two db.run which return futures, but inner future will
not complete. For resolving it you should compose futures with
flatMap or for-comprehension.
You use only one partial-function case Some(_) => for pattern matching
and don't handle another value None.
You can use only one db.run and actions composition.
Your code can be like as:
def createCopyOfProcessTemplate(processTemplateId: Int): Future[Option[ProcessTemplatesModel]] = {
val action = processTemplates.filter(...).result.map(_.headOption)
val composedAction = action.flatMap {
case Some(r) =>
val copyAction = (processTemplates returning processTemplates...)
copyAction.flatMap { id =>
processTemplates.filter(_.id === id).result.headOption
}
case _ =>
DBIO.successful(None) // issue #2 has been resolved here
}
db.run(composedAction) // issue #3 has been resolved here
}
We get rid of issue #1 (because we use actions composition).

Extracting and accumulate result from multiple HTTP request

I met a problem that I cannot solve these days. I'm doing multiple http requests and in each response, it should have a Array[DTAnnotation]. I want to accumulate all the resulting list into one (this is not the problem here). My problem is that I cannot return the results from a WSResponse. What I try :
import mymodel.{DTFeatures, DTResponse, DTRequest, DTAnnotations}
def checkForSpike
(
awsKey : String,
collection : JSONCollection,
record : Record,
features : Array[DTFeatures]
) : Unit = {
val url = config.getString("url").getOrElse
val holder = WS.url(url)
val complexHolder =
holder.withHeaders(("Content-Type","application/json"))
// excepting result is List[Array[DTAnnotations]]
val test : List[Array[DTAnnotations]] =
for(feature <- features) yield {
val dtFeature = Json.stringify(Json.toJson(DTRequest(feature)))
val futureResponse = complexHolder.post(dtFeature)
Logger.info("Make the HTTP POST Request in " + (t1 - t0) + " msecs")
futureResponse.map { response =>
Logger.info("Get response in " + (System.currentTimeMillis - t1))
if(response.status == 200) {
response.json.validate[DTResponse].map { dtResponse =>
// I want to return this
dtResponse.annotations
}.recoverTotal { _ =>
Logger.debug("invalid json")
}
} else {
Logger.debug(Json.prettyPrint(Json.obj("status" -> response.status, "body" -> response.body)))
}
}
Await.result(futureResponse, 10.seconds)
}
}
Because the response is a Future, I try to add a Await to get the annotations but I have one error at the typing phase :
[error] found : Array[play.api.libs.ws.WSResponse]
[error] required: List[Array[DTAnnotation]]
How I can fix this ? Thank you !
There are a number of errors that avoid this to work. I add a version that compiles with your expected type and if you have questions I will answer in the comments.
def checkForSpike
(
awsKey: String,
collection: JSONCollection,
record: Record,
features: Array[DTFeatures]
): Unit = {
val url = config.getString("url").getOrElse
val holder = WS.url(url)
val complexHolder =
holder.withHeaders(("Content-Type", "application/json"))
// excepting result is List[Array[DTAnnotations]]
val test: List[Array[DTAnnotations]] =
for (feature <- features.toList) yield {
val dtFeature = Json.stringify(Json.toJson(DTRequest(feature)))
val futureResponse = complexHolder.post(dtFeature)
val futureAnnotations: Future[Array[DTAnnotations]] = futureResponse.map { response =>
if (response.status == 200) {
response.json.validate[DTResponse].map { dtResponse =>
// I want to return this
dtResponse.annotations
}.recoverTotal { _ =>
Logger.debug("invalid json")
??? // An Array response should be expected, maybe empty
}
} else {
Logger.debug(Json.prettyPrint(Json.obj("status" -> response.status, "body" -> response.body)))
??? // An Array response should be expected, maybe empty
}
}
Await.result(futureAnnotations, 10.seconds)
}
}
Issues:
Features must be converted to List if you expect a list to be
returned by the for comprehension
The map on future response returns
another future and this value should be used in the Await
To make sure the type of futureAnnotations is correct in all the branches the type should be valid

Why does Future.firstCompletedOf not invoke callback on timeout?

I am doing Exercises from Learning Concurrent Programming in Scala.
For an exercise question in code comment.
Program prints proper output of HTML contents for proper URL and timeout sufficiently enough.
Program prints "Error occured" for proper URL and low timeout.
However for invalid URL "Error occured" is not printed. What is the problem with the code below?
/*
* Implement a command-line program that asks the user to input a URL of some website,
* and displays the HTML of that website. Between the time that the user hits ENTER and
* the time that the HTML is retrieved, the program should repetitively print a . to the
* standard output every 50 milliseconds, with a two seconds timeout. Use only futures
* and promises, and avoid the synchronization primitives from the previous chapters.
* You may reuse the timeout method defined in this chapter.
*/
object Excersices extends App {
val timer = new Timer()
def timeout(t: Long = 1000): Future[Unit] = {
val p = Promise[Unit]
val timer = new Timer(true)
timer.schedule(new TimerTask() {
override def run() = {
p success ()
timer cancel()
}
}, t)
p future
}
def printDot = println(".")
val taskOfPrintingDot = new TimerTask {
override def run() = printDot
}
println("Enter a URL")
val lines = io.Source.stdin.getLines()
val url = if (lines hasNext) Some(lines next) else None
timer.schedule(taskOfPrintingDot, 0L, 50.millisecond.toMillis)
val timeOut2Sec = timeout(2.second.toMillis)
val htmlContents = Future {
url map { x =>
blocking {
Source fromURL (x) mkString
}
}
}
Future.firstCompletedOf(Seq(timeOut2Sec, htmlContents)) map { x =>
timer cancel ()
x match {
case Some(x) =>
println(x)
case _ =>
println("Error occured")
}
}
Thread sleep 5000
}
As #Gábor Bakos said exception produces Failure which doesn't handled by map:
val fut = Future { Some(Source fromURL ("hhhttp://google.com")) }
scala> fut map { x => println(x) } //nothing printed
res12: scala.concurrent.Future[Unit] = scala.concurrent.impl.Promise$DefaultPromise#5e025724
To process failure - use recover method :
scala> fut recover { case failure => None } map { x => println(x) }
None
res13: scala.concurrent.Future[Unit] = scala.concurrent.impl.Promise$DefaultPromise#578afc83
In your context it's something like:
Future.firstCompletedOf(Seq(timeOut2Sec, htmlContents)) recover {case x => println("Error:" + x); None} map { x => ...}
The Complete Code after using recover as advised by #dk14:
object Exercises extends App {
val timer = new Timer()
def timeout(t: Long = 1000): Future[Unit] = {
val p = Promise[Unit]
val timer = new Timer(true)
timer.schedule(new TimerTask() {
override def run() = {
p success ()
timer cancel ()
}
}, t)
p future
}
def printDot = println(".")
val taskOfPrintingDot = new TimerTask {
override def run() = {
printDot
}
}
println("Enter a URL")
val lines = io.Source.stdin.getLines()
val url = if (lines hasNext) Some(lines next) else None
timer.schedule(taskOfPrintingDot, 0L, 50.millisecond.toMillis)
val timeOut2Sec = timeout(2.second.toMillis)
val htmlContents = Future {
url map { x =>
blocking {
Source fromURL (x) mkString
}
}
}
Future.firstCompletedOf(Seq(timeOut2Sec, htmlContents))
.recover { case x => println("Error:" + x); None }
.map { x =>
timer cancel ()
x match {
case Some(x) =>
println(x)
case _ =>
println("Timeout occurred")
}
}
Thread sleep 5000
}