This repository has been archived by the owner on Sep 8, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 6
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Ref #4 Add play WS support and Json combinator
- Loading branch information
1 parent
b914728
commit 817ff39
Showing
12 changed files
with
1,297 additions
and
669 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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", | ||
|
@@ -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 | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} | ||
} | ||
|
||
|
@@ -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) | ||
} | ||
} | ||
|
||
|
@@ -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 | ||
} |
15 changes: 0 additions & 15 deletions
15
src/main/scala/com/github/filosganga/play/predictionio/ClientProvider.scala
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.