Skip to content
This repository has been archived by the owner on Sep 8, 2024. It is now read-only.

Commit

Permalink
Ref #4 Add play WS support and Json combinator
Browse files Browse the repository at this point in the history
  • Loading branch information
filosganga committed Apr 6, 2014
1 parent b914728 commit 817ff39
Show file tree
Hide file tree
Showing 12 changed files with 1,297 additions and 669 deletions.
16 changes: 10 additions & 6 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -19,16 +19,15 @@ scmInfo := Some(ScmInfo(
Some("scm:git:[email protected]:filosganga/play-predictionio.git")
))

scalaVersion := "2.10.2"
scalaVersion := "2.10.3"

crossScalaVersions := Seq("2.10.2")
crossScalaVersions := Seq("2.10.3")

scalacOptions ++= Seq("-deprecation", "-unchecked", "-feature")

libraryDependencies ++= {
val playVersion = "2.2.0"
val playVersion = "2.2.2"
Seq(
"io.prediction" % "client" % "0.6.1",
"com.typesafe.play" %% "play" % playVersion % "provided",
"org.specs2" %% "specs2" % "1.14" % "test",
"org.mockito" % "mockito-all" % "1.9.5" % "test",
Expand All @@ -38,14 +37,19 @@ libraryDependencies ++= {

pomIncludeRepository := { _ => false }

pomExtra := (
pomExtra :=
<developers>
<developer>
<id>filosganga</id>
<name>Filippo De Luca</name>
<url>http://filippodeluca.com</url>
<email>me@filippodeluca.com</email>
<roles>
<role>developer</role>
</roles>
<timezone>GMT</timezone>
</developer>
</developers>)
</developers>


publishMavenStyle := true
Expand Down
203 changes: 94 additions & 109 deletions src/main/scala/com/github/filosganga/play/predictionio/Api.scala
Original file line number Diff line number Diff line change
@@ -1,98 +1,87 @@
package com.github.filosganga.play.predictionio

import io.prediction.{User, Item, Client}
import play.api.Logger
import org.joda.time.DateTime

import collection.JavaConversions._
import scala.concurrent._
import play.api.libs.ws.WS
import play.api.libs.json._


/**
*
* @author Filippo De Luca - [email protected]
*/
trait Api {
self: EndpointProvider =>

def getUser(id: String)(implicit ec: ExecutionContext): Future[User] = future {
withClient(_.getUser(id))
}

lazy val format = new JsonFormat(apiKey)

def createUser(uid: String, location: Option[Location] = None)(implicit ec: ExecutionContext): Future[User] = future {
withClient(userFor(uid, location))
}
def getUser(id: String)(implicit ec: ExecutionContext): Future[User] = {

private def userFor(uid: String, location: Option[Location])(client: Client): User = {
client.createUser(location.foldLeft(client.getCreateUserRequestBuilder(uid))((s, x) =>
s.latitude(x.latitude).longitude(x.longitude)
))
import format._

val created = location.foldLeft(new User(uid))((s, x) => s.latitude(x.latitude).longitude(x.longitude))
WS.url(s"$endpoint/users/$id.json").withQueryString("pio_appkey" -> apiKey).get().flatMap {
response =>
response.status match {
case x if x >= 300 => Future.failed(new RuntimeException(response.statusText))
case _ =>
Json.fromJson[User](response.json).fold(invalid => Future.failed(new RuntimeException), Future.successful)
}
}
}

Logger.debug("Created user with uid=" + created.getUid)
def createUser(user: User)(implicit ec: ExecutionContext): Future[User] = {

created
}
import format._

def deleteUser(uid: String)(implicit ec: ExecutionContext): Future[Unit] = future {
withClient(_.deleteUser(uid))
WS.url(s"$endpoint/users.json").post(Json.toJson(user)).flatMap {
case response if response.status >= 300 => Future.failed(new RuntimeException(response.statusText))
case _ => Future.successful(user)
}
}

def getItem(id: String)(implicit ec: ExecutionContext): Future[Item] = future {
withClient(_.getItem(id))
def deleteUser(uid: String)(implicit ec: ExecutionContext): Future[Unit] = {
WS.url(s"$endpoint/users/$uid.json").withQueryString("pio_appkey" -> apiKey).delete().map {
case response if response.status >= 300 => Future.failed(new RuntimeException(response.statusText))
case _ => Future.successful()
}
}

def getItem(id: String)(implicit ec: ExecutionContext): Future[Item] = {

def createItem(id: String,
types: Set[String] = Set.empty,
location: Option[Location] = None,
start: Option[DateTime] = None,
end: Option[DateTime] = None)(implicit ec: ExecutionContext): Future[Item] = future {

withClient(itemFor(id, types, location, start, end))
}
import format._

private def itemFor(id: String, types: Set[String], location: Option[Location], start: Option[DateTime], end: Option[DateTime])(client: Client): Item = {

val request = (((client.getCreateItemRequestBuilder(id, types.toArray) /: location) {
(s, x) =>
s.latitude(x.latitude).longitude(x.longitude)
} /: start) {
(s, x) =>
s.startT(x)
} /: end) {
(s, x) =>
s.endT(x)
WS.url(s"$endpoint/items/$id.json").withQueryString("pio_appkey" -> apiKey).get().flatMap {
case response if response.status >= 300 => Future.failed(new RuntimeException(response.statusText))
case response => Json.fromJson[Item](response.json).fold(invalid => Future.failed(new RuntimeException), Future.successful)
}
}

client.createItem(request)

val created = new Item(id, types.toArray)
def createItem(item: Item)(implicit ec: ExecutionContext): Future[Item] = {

Logger.debug("Created Item with id=" + created.getIid + " and types=" + created.getItypes.mkString(","))
import format._

created
WS.url(s"$endpoint/items.json").post(Json.toJson(item)).flatMap {
case response if response.status >= 300 => {
Future.failed(new RuntimeException(s"Error: ${response.statusText}, json: ${Json.stringify(Json.toJson(item))}"))
}
case _ => Future.successful(item)
}
}

def deleteItem(id: String)(implicit ec: ExecutionContext): Future[Unit] = future {
withClient(_.deleteItem(id))
WS.url(s"$endpoint/items/$id.json").withQueryString("pio_appkey" -> apiKey).delete().flatMap {
case response if response.status >= 300 => Future.failed(new RuntimeException(response.statusText))
case _ => Future.successful()
}
}

def userActionItem(userId: String,
itemId: String,
action: String,
rate: Option[Int] = None,
dateTime: DateTime = DateTime.now(),
location: Option[Location] = None)(implicit ec: ExecutionContext): Future[Unit] = future {
withClient {
client =>
def userAction(action: Action)(implicit ec: ExecutionContext): Future[Action] = {

val request = client.getUserActionItemRequestBuilder(userId, itemId, action).t(dateTime)
rate.foreach(request.rate)
location.foreach(l => request.longitude(l.longitude).latitude(l.latitude))
import format._

client.userActionItem(request)
WS.url(s"$endpoint/actions/u2i.json").post(Json.toJson(action)) flatMap {
case response if response.status >= 300 => Future.failed(new RuntimeException(response.statusText))
case _ => Future.successful(action)
}
}

Expand All @@ -102,25 +91,28 @@ trait Api {
types: Set[String] = Set.empty,
attributes: Set[String] = Set.empty,
location: Option[Location] = None,
distance: Option[Distance] = None)(implicit ec: ExecutionContext): Future[Iterable[ItemInfo]] = future {

withClient {
client =>

val rb = client.getItemRecGetTopNRequestBuilder(engine, userId, n)
rb.attributes(attributes.toArray)
rb.itypes(types.toArray)

location.foreach(l => rb.latitude(l.latitude).longitude(l.longitude))
distance.foreach {
case Km(x) => rb.unit("km").within(x)
case Mi(x) => rb.unit("mi").within(x)
}

client.getItemRecTopNWithAttributes(rb)

}.map {
case (id, atts) => ItemInfo(id, attributes.toSet)
distance: Option[Distance] = None)
(implicit ec: ExecutionContext): Future[Iterable[ItemInfo]] = {

import format._

val parameters = Seq(
"pio_appkey" -> apiKey,
"pio_uid" -> userId,
"pio_n" -> s"$n"
) ++
(if (types.nonEmpty) Seq("pio_itypes" -> types.mkString(",")) else Nil) ++
(if (attributes.nonEmpty) Seq("pio_attributes" -> attributes.mkString(",")) else Nil) ++
location.map(x => Seq("pio_latlng" -> s"${x.latitude},${x.longitude}")).getOrElse(Nil) ++
distance.map {
case Km(value) => Seq("pio_within" -> s"$value", "pio_unit" -> "km")
case Mi(value) => Seq("pio_within" -> s"$value", "pio_unit" -> "mi")
}.getOrElse(Nil)


WS.url(s"$endpoint/engines/itemrec/$engine/topn.json").withQueryString(parameters: _*).get().flatMap {
case response if response.status >= 300 => Future.failed(new RuntimeException(response.statusText))
case response => Json.fromJson[Iterable[ItemInfo]](response.json).fold(invalid => Future.failed(new RuntimeException), Future.successful)
}
}

Expand All @@ -130,37 +122,30 @@ trait Api {
types: Set[String] = Set.empty,
attributes: Set[String] = Set.empty,
location: Option[Location] = None,
distance: Option[Distance] = None)(implicit ec: ExecutionContext): Future[Iterable[ItemInfo]] = future {

withClient {
client =>

val rb = client.getItemSimGetTopNRequestBuilder(engine, targetId, n)
rb.attributes(attributes.toArray)
rb.itypes(types.toArray)

location.foreach(l => rb.latitude(l.latitude).longitude(l.longitude))
distance.foreach {
case Km(x) => rb.unit("km").within(x)
case Mi(x) => rb.unit("mi").within(x)
}

client.getItemSimTopNWithAttributes(rb)

}.map {
case (id, atts) => ItemInfo(id, attributes.toSet)
distance: Option[Distance] = None)
(implicit ec: ExecutionContext): Future[Iterable[ItemInfo]] = {

import format._

val parameters = Seq(
"pio_appkey" -> apiKey,
"pio_iid" -> targetId,
"pio_n" -> s"$n"
) ++
(if (types.nonEmpty) Seq("pio_itypes" -> types.mkString(",")) else Nil) ++
(if (attributes.nonEmpty) Seq("pio_attributes" -> attributes.mkString(",")) else Nil) ++
location.map(x => Seq("pio_latlng" -> s"${x.latitude},${x.longitude}")).getOrElse(Nil) ++
distance.map {
case Km(value) => Seq("pio_within" -> s"$value", "pio_unit" -> "km")
case Mi(value) => Seq("pio_within" -> s"$value", "pio_unit" -> "mi")
}.getOrElse(Nil)


WS.url(s"$endpoint/engines/itemsim/$engine/topn.json").withQueryString(parameters: _*).get().flatMap {
case response if response.status >= 300 => Future.failed(new RuntimeException(response.statusText))
case response => Json.fromJson[Iterable[ItemInfo]](response.json).fold(invalid => Future.failed(new RuntimeException), Future.successful)
}
}

protected def withClient[T](f: Client => T): T

}

abstract sealed class Distance {

def value: Double
}

case class Km(value: Double) extends Distance

case class Mi(value: Double) extends Distance
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,12 @@ package com.github.filosganga.play.predictionio

/**
*
* @author Filippo De Luca - me@filippodeluca.com
* @author Filippo De Luca - fdeluca@expedia.com
*/
case class Location(longitude: Double, latitude: Double)
trait EndpointProvider {

def endpoint: String

def apiKey: String

}
Loading

0 comments on commit 817ff39

Please sign in to comment.