package code
package lib
import model._
import net.liftweb._
import common._
import http._
/**
* A simple example of a REST style interface
* using the basic Lift tools
*/
object BasicExample {
/*
* Given a suffix and an item, make a LiftResponse
*/
private def toResponse(suffix: String, item: Item) =
suffix match {
case "xml" => XmlResponse(item)
case _ => JsonResponse(item)
}
/**
* Find /simple/item/1234.json
* Find /simple/item/1234.xml
*/
lazy val findItem: LiftRules.DispatchPF = {
case Req("simple" :: "item" :: itemId :: Nil, // path
suffix, // suffix
GetRequest) =>
() => Item.find(itemId).map(toResponse(suffix, _))
}
/**
* Find /simple2/item/1234.json
*/
lazy val extractFindItem: LiftRules.DispatchPF = {
// path with extractor
case Req("simple2" :: "item" :: Item(item) :: Nil,
suffix, GetRequest) =>
// a function that returns the response
() => Full(toResponse(suffix, item))
}
}
// the stateless REST handlers
LiftRules.statelessDispatchTable.append(BasicExample.findItem)
LiftRules.statelessDispatchTable.append(BasicExample.extractFindItem)
// stateful versions of the same
// LiftRules.dispatch.append(BasicExample.findItem)
// LiftRules.dispatch.append(BasicExample.extractFindItem)
lazy val findItem: LiftRules.DispatchPF =
case Req("simple" :: "item" :: itemId :: Nil, // path
suffix, // suffix
GetRequest) =>
() => Item.find(itemId).map(toResponse(suffix, _))
/*
* Given a suffix and an item, make a LiftResponse
*/
private def toResponse(suffix: String, item: Item) =
suffix match {
case "xml" => XmlResponse(item)
case _ => JsonResponse(item)
}
// path with extractor
case Req("simple2" :: "item" :: Item(item) :: Nil,
suffix, GetRequest) =>
// a function that returns the response
() => Full(toResponse(suffix, item))
/** * Extract a String (id) to an Item */ def unapply(id: String): Option[Item] = Item.find(id)
package code
package model
import net.liftweb._
import util._
import Helpers._
import common._
import json._
import scala.xml.Node
/**
* An item in inventory
*/
case class Item(id: String, name: String,
description: String,
price: BigDecimal, taxable: Boolean,
weightInGrams: Int, qnty: Int)
/**
* The Item companion object
*/
object Item {
private implicit val formats =
net.liftweb.json.DefaultFormats + BigDecimalSerializer
private var items: List[Item] = parse(data).extract[List[Item]]
private var listeners: List[Item => Unit] = Nil
/**
* Convert a JValue to an Item if possible
*/
def apply(in: JValue): Box[Item] = Helpers.tryo{in.extract[Item]}
/**
* Extract a String (id) to an Item
*/
def unapply(id: String): Option[Item] = Item.find(id)
/**
* Extract a JValue to an Item
*/
def unapply(in: JValue): Option[Item] = apply(in)
/**
* The default unapply method for the case class.
* We needed to replicate it here because we
* have overloaded unapply methods
*/
def unapply(in: Any): Option[(String, String,
String,
BigDecimal, Boolean,
Int, Int)] = {
in match {
case i: Item => Some((i.id, i.name, i.description,
i.price, i.taxable,
i.weightInGrams, i.qnty))
case _ => None
}
}
/**
* Convert an item to XML
*/
implicit def toXml(item: Item): Node =
- {Xml.toXml(item)}
/**
* Convert the item to JSON format. This is
* implicit and in the companion object, so
* an Item can be returned easily from a JSON call
*/
implicit def toJson(item: Item): JValue =
Extraction.decompose(item)
/**
* Convert a Seq[Item] to JSON format. This is
* implicit and in the companion object, so
* an Item can be returned easily from a JSON call
*/
implicit def toJson(items: Seq[Item]): JValue =
Extraction.decompose(items)
/**
* Convert a Seq[Item] to XML format. This is
* implicit and in the companion object, so
* an Item can be returned easily from an XML REST call
*/
implicit def toXml(items: Seq[Item]): Node =
{
items.map(toXml)
}
/**
* Get all the items in inventory
*/
def inventoryItems: Seq[Item] = items
// The raw data
private def data =
"""[
{"id": "1234", "name": "Cat Food",
"description": "Yummy, tasty cat food",
"price": 4.25,
"taxable": true,
"weightInGrams": 1000,
"qnty": 4
},
{"id": "1235", "name": "Dog Food",
"description": "Yummy, tasty dog food",
"price": 7.25,
"taxable": true,
"weightInGrams": 5000,
"qnty": 72
},
{"id": "1236", "name": "Fish Food",
"description": "Yummy, tasty fish food",
"price": 2,
"taxable": false,
"weightInGrams": 200,
"qnty": 45
},
{"id": "1237", "name": "Sloth Food",
"description": "Slow, slow sloth food",
"price": 18.33,
"taxable": true,
"weightInGrams": 750,
"qnty": 62
},
]
"""
/**
* Select a random Item
*/
def randomItem: Item = synchronized {
items(Helpers.randomInt(items.length))
}
/**
* Find an item by id
*/
def find(id: String): Box[Item] = synchronized {
items.find(_.id == id)
}
/**
* Add an item to inventory
*/
def add(item: Item): Item = {
synchronized {
items = item :: items.filterNot(_.id == item.id)
updateListeners(item)
}
}
/**
* Find all the items with the string in their name or
* description
*/
def search(str: String): List[Item] = {
val strLC = str.toLowerCase()
items.filter(i =>
i.name.toLowerCase.indexOf(strLC) >= 0 ||
i.description.toLowerCase.indexOf(strLC) >= 0)
}
/**
* Deletes the item with id and returns the
* deleted item or Empty if there's no match
*/
def delete(id: String): Box[Item] = synchronized {
var ret: Box[Item] = Empty
val Id = id // an upper case stable ID for pattern matching
items = items.filter {
case i@Item(Id, _, _, _, _, _, _) =>
ret = Full(i) // side effect
false
case _ => true
}
ret.map(updateListeners)
}
/**
* Update listeners when the data changes
*/
private def updateListeners(item: Item): Item = {
synchronized {
listeners.foreach(f =>
Schedule.schedule(() => f(item), 0 seconds))
listeners = Nil
}
item
}
/**
* Add an onChange listener
*/
def onChange(f: Item => Unit) {
synchronized {
// prepend the function to the list of listeners
listeners ::= f
}
}
}
/**
* A helper that will JSON serialize BigDecimal
*/
object BigDecimalSerializer extends Serializer[BigDecimal] {
private val Class = classOf[BigDecimal]
def deserialize(implicit format: Formats): PartialFunction[(TypeInfo, JValue), BigDecimal] = {
case (TypeInfo(Class, _), json) => json match {
case JInt(iv) => BigDecimal(iv)
case JDouble(dv) => BigDecimal(dv)
case value => throw new MappingException("Can't convert " + value + " to " + Class)
}
}
def serialize(implicit format: Formats): PartialFunction[Any, JValue] = {
case d: BigDecimal => JDouble(d.doubleValue)
}
}
dpp@raptor:~/proj/simply_lift/samples/http_rest$ curl http://localhost:8080/simple/item/1234
{
"id":"1234",
"name":"Cat Food",
"description":"Yummy, tasty cat food",
"price":4.25,
"taxable":true,
"weightInGrams":1000,
"qnty":4
}
dpp@raptor:~/proj/simply_lift/samples/http_rest$ curl http://localhost:8080/simple/item/1234.xml
<?xml version="1.0" encoding="UTF-8"?>
<item>
<id>1234</id>
<name>Cat Food</name>
<description>Yummy, tasty cat food</description>
<price>4.25</price>
<taxable>true</taxable>
<weightInGrams>1000</weightInGrams>
<qnty>4</qnty>
</item>
dpp@raptor:~/proj/simply_lift/samples/http_rest$
(C) 2012 David Pollak