The logic needs the mappers to be singletons. My_trait is defined identically to CRUDify. What I'm attempting to build is a generic REST api. I don't really expect you or anyone to look through this as it's pretty huge, but I wanted to follow up. The commented code at the top of RestAPI is what I'm having so much trouble with. It would eventually replace the serve { } portion beneath it.
object RestAPI extends RestHelper {
/*
var mappers:List[LongKeyedMetaMapper[_] with RESTify[Long, _]] = List(foo,bar)
def add(tbl:String,h:PartialFunction[Req, () => Box[LiftResponse]]) {
serve(h)
}
def setMappers {
mappers.map(m => {
val name = m.getSingleton.dbTableName.toString
serve( {case Req("api" :: name :: "findBy" :: key :: _, "xml", GetRequest) => findBy(m.getSingleton,key) } )
serve( {case Req("api" :: name :: "findBySql" :: key :: _, "xml", GetRequest) => findBySql(m.getSingleton,key) } )
serve( {case Req("api" :: name :: "create" :: key :: _, "xml", GetRequest) => create(m.getSingleton,key) } )
serve( {case Req("api" :: name :: AsLong(id) :: "update" :: key :: _, "xml", GetRequest) => update(m.getSingleton,id,key) } )
serve( {case Req("api" :: name :: AsLong(id) :: "delete" :: key :: _, "xml", GetRequest) => delete(m.getSingleton,id,key) } )
serve( {case Req("api" :: name :: AsLong(id) :: "get" :: key :: _, "xml", GetRequest) => get(m.getSingleton,id,key) } )
serve( {case Req("api" :: name :: "meta" :: key :: _, "xml", GetRequest) => metaData(m.getSingleton,key) } )
})
} */
def authenticate(key: String):Boolean = if (key == "secret") true else false
serve {
case Req("api" :: "user" :: "findBy" :: key :: _, "xml", GetRequest) => user_findBy(User,key)
case Req("api" :: "user" :: "findBySql" :: key :: _, "xml", GetRequest) => user_findBySql(User,key)
case Req("api" :: "user" :: "create" :: key :: _, "xml", GetRequest) => user_create(User,key)
case Req("api" :: "user" :: AsLong(id) :: "update" :: key :: _, "xml", GetRequest) => user_update(User,id,key)
case Req("api" :: "user" :: AsLong(id) :: "delete" :: key :: _, "xml", GetRequest) => user_delete(User,id,key)
case Req("api" :: "user" :: AsLong(id) :: "get" :: key :: _, "xml", GetRequest) => user_get(User,id,key)
case Req("api" :: "user" :: "meta" :: key :: _, "xml", GetRequest) => user_metaData(User,key)
case Req("api" :: "foo" :: "findBy" :: key :: _, "xml", GetRequest) => findBy(foo,key)
case Req("api" :: "foo" :: "findBySql" :: key :: _, "xml", GetRequest) => findBySql(foo,key)
case Req("api" :: "foo" :: "create" :: key :: _, "xml", GetRequest) => create(foo,key)
case Req("api" :: "foo" :: AsLong(id) :: "update" :: key :: _, "xml", GetRequest) => update(foo,id,key)
case Req("api" :: "foo" :: AsLong(id) :: "delete" :: key :: _, "xml", GetRequest) => delete(foo,id,key)
case Req("api" :: "foo" :: AsLong(id) :: "get" :: key :: _, "xml", GetRequest) => get(foo,id,key)
case Req("api" :: "foo" :: "meta" :: key :: _, "xml", GetRequest) => metaData(foo,key)
}
def findBy[M <: LongKeyedMapper[M]](m: LongKeyedMetaMapper[M] with RESTify[Long, M],key:String):LiftResponse = { if (RestAPI.authenticate(key)) {
var u = m.findAll(m.generateQuery:_*) //Query Mapper
if (u.isEmpty) PlainTextResponse("Not found") else XmlResponse(<results>{u.map(f => f.toXml)}</results>)
} else PlainTextResponse("Auth fail or wrong db table name") }
def findBySql[M <: LongKeyedMapper[M]](m: LongKeyedMetaMapper[M] with RESTify[Long, M],key:String):LiftResponse = { if (RestAPI.authenticate(key)) {
if (S.param("sql").isEmpty) PlainTextResponse("Param 'sql' not found")
else {
try {
var u = m.findAll(BySql(S.param("sql").open_!,IHaveValidatedThisSQL("adam","2012-03-19")))
XmlResponse(<results>{u.map(f => f.toXml)}</results>)
} catch {
case e: Exception => PlainTextResponse("Bad SQL: " + e.toString)
}
}
} else PlainTextResponse("Auth fail or wrong db table name") }
def create[M <: LongKeyedMapper[M]](m: LongKeyedMetaMapper[M] with RESTify[Long, M],key:String):LiftResponse = { if (RestAPI.authenticate(key)) {
var u = m.createInstance
if (m.setFieldsFromParams(u)) XmlResponse(u.toXml) else PlainTextResponse("Forgot a non_null field or did not set any fields") } else PlainTextResponse("Auth fail or wrong db table name") }
def update[M <: LongKeyedMapper[M] with IdPK](m: LongKeyedMetaMapper[M] with RESTify[Long, M],id:Long,key:String):LiftResponse = { if (RestAPI.authenticate(key)) {
try {
var ubox = m.find(BySql("id="+id,IHaveValidatedThisSQL("adam",""))) //dangerous
if (!ubox.isEmpty) {
val u = ubox.open_!
if (m.setFieldsFromParams(u)) XmlResponse(u.toXml) else PlainTextResponse("Forgot a non_null field or did not set any fields")
} else PlainTextResponse("Not found")
} catch {
case e: Exception => PlainTextResponse("Bad SQL: " + e.toString)
}
} else PlainTextResponse("Auth fail or wrong db table name") }
def delete[M <: LongKeyedMapper[M] with IdPK](m: LongKeyedMetaMapper[M] with RESTify[Long, M],id:Long,key:String):LiftResponse = { if (RestAPI.authenticate(key)) { // this will eventually archive instead of delete
try { if ((!S.param("auth").isEmpty) && (id >= 0))
if (S.param("auth").open_! == "true") {
val toDelete = m.find(BySql("id="+id,IHaveValidatedThisSQL("adam","")))
if (!toDelete.isEmpty)
if (toDelete.open_!.delete_!)
PlainTextResponse("Delete successful")
else PlainTextResponse("Delete unsuccessful")
else PlainTextResponse("Item not found")
} else PlainTextResponse("Not authorized to delete")
else PlainTextResponse("Not authorized or ID wrong") } catch { case e:Exception => PlainTextResponse(e.toString)}
} else PlainTextResponse("Auth fail or wrong db table name") }
def get[M <: LongKeyedMapper[M] with IdPK](m: LongKeyedMetaMapper[M] with RESTify[Long, M],id:Long,key:String):LiftResponse = { if (RestAPI.authenticate(key)) {
try {
var ubox = m.find(BySql("id="+id,IHaveValidatedThisSQL("adam",""))) //dangerous
if (!ubox.isEmpty) {
XmlResponse(ubox.open_!.toXml)
} else PlainTextResponse("Not found")
} catch { case e: Exception => PlainTextResponse(e.toString) }
} else PlainTextResponse("Forgot a non_null field or did not set any fields") }
def metaData[M <: LongKeyedMapper[M] with IdPK](m: LongKeyedMetaMapper[M] with RESTify[Long, M],key:String):LiftResponse = { if (RestAPI.authenticate(key)) XmlResponse(
<model dbTableName={m.dbTableName.toString}>
<fields>
{m.mappedFields.map(f => <field dbColumnName={f.dbColumnName.toString} type={f.targetSQLType.toString} not_null={f.dbNotNull_?.toString} /> )}
</fields>
</model>
) else PlainTextResponse("Forgot a non_null field or did not set any fields") }
//and, annoyingly, because the user mapper is a different type, we have another set of these for user
def user_findBy[M <: MegaProtoUser[M]](m: MetaMegaProtoUser[M] with RESTify[Long, M],key:String):LiftResponse = { if (RestAPI.authenticate(key)) {
var u = m.findAll(m.generateQuery:_*) //Query Mapper
if (u.isEmpty) PlainTextResponse("Not found") else XmlResponse(<results>{u.map(f => f.toXml)}</results>)
} else PlainTextResponse("Auth fail or wrong db table name") }
def user_findBySql[M <: MegaProtoUser[M]](m: MetaMegaProtoUser[M] with RESTify[Long, M],key:String):LiftResponse = { if (RestAPI.authenticate(key)) {
if (S.param("sql").isEmpty) PlainTextResponse("Param 'sql' not found")
else {
try {
var u = m.findAll(BySql(S.param("sql").open_!,IHaveValidatedThisSQL("adam","2012-03-19")))
XmlResponse(<results>{u.map(f => f.toXml)}</results>)
} catch {
case e: Exception => PlainTextResponse("Bad SQL: " + e.toString)
}
}
} else PlainTextResponse("Auth fail or wrong db table name") }
def user_create[M <: MegaProtoUser[M]](m: MetaMegaProtoUser[M] with RESTify[Long, M],key:String):LiftResponse = { if (RestAPI.authenticate(key)) {
var u = m.createInstance
if (m.setFieldsFromParams(u)) XmlResponse(u.toXml) else PlainTextResponse("Forgot a non_null field or did not set any fields")
} else PlainTextResponse("Auth fail or wrong db table name") }
def user_update[M <: MegaProtoUser[M]](m: MetaMegaProtoUser[M] with RESTify[Long, M],id:Long,key:String):LiftResponse = { if (RestAPI.authenticate(key)) {
try {
var ubox = m.find(BySql("id="+id,IHaveValidatedThisSQL("adam",""))) //dangerous
if (!ubox.isEmpty) {
val u = ubox.open_!
if (m.setFieldsFromParams(u)) XmlResponse(u.toXml) else PlainTextResponse("Forgot a non_null field or did not set any fields")
} else PlainTextResponse("Not found")
} catch {
case e: Exception => PlainTextResponse("Bad SQL: " + e.toString)
}
} else PlainTextResponse("Auth fail or wrong db table name") }
def user_delete[M <: MegaProtoUser[M]](m: MetaMegaProtoUser[M] with RESTify[Long, M],id:Long,key:String):LiftResponse = { if (RestAPI.authenticate(key)) {
try { if ((!S.param("auth").isEmpty) && (id >= 0))
if (S.param("auth").open_! == "true") {
val toDelete = m.find(BySql("id="+id,IHaveValidatedThisSQL("adam","")))
if (!toDelete.isEmpty)
if (toDelete.open_!.delete_!)
PlainTextResponse("Delete successful")
else PlainTextResponse("Delete unsuccessful")
else PlainTextResponse("Item not found")
} else PlainTextResponse("Not authorized to delete")
else PlainTextResponse("Not authorized or ID wrong") } catch { case e:Exception => PlainTextResponse(e.toString)}
} else PlainTextResponse("Auth fail or wrong db table name") }
def user_get[M <: MegaProtoUser[M]](m: MetaMegaProtoUser[M] with RESTify[Long, M],id:Long,key:String):LiftResponse = { if (RestAPI.authenticate(key)) {
try {
var ubox = m.find(BySql("id="+id,IHaveValidatedThisSQL("adam",""))) //dangerous
if (!ubox.isEmpty) {
XmlResponse(ubox.open_!.toXml)
} else PlainTextResponse("Not found")
} catch { case e: Exception => PlainTextResponse(e.toString) }
} else PlainTextResponse("Forgot a non_null field or did not set any fields") }
def user_metaData[M <: MegaProtoUser[M]](m: MetaMegaProtoUser[M] with RESTify[Long, M],key:String):LiftResponse = { if (RestAPI.authenticate(key)) { XmlResponse(
<model dbTableName={m.dbTableName.toString}>
<fields>
{m.mappedFields.map(f => <field dbColumnName={f.dbColumnName.toString} type={f.targetSQLType.toString} not_null={f.dbNotNull_?.toString} /> )}
</fields>
</model>
) } else PlainTextResponse("Forgot a non_null field or did not set any fields") }
}
trait RESTify[KeyType, RESTType <: KeyedMapper[KeyType, RESTType]] {
self: RESTType with KeyedMetaMapper[KeyType, RESTType] =>
val tn:String = dbTableName
def generateQuery:List[QueryParam[RESTType]] = {
var result:List[QueryParam[RESTType]] = Nil
mappedFields.foreach(z => {
val f = z.asInstanceOf[MappedField[KeyType,RESTType]] //cast our BaseMappedField to the correct type of MappedField
if (!S.param(f.dbColumnName.toString()).isEmpty) { //if the url query string with a param name of f's database column name is set
var param = S.param(f.dbColumnName.toString()).open_! //get that param value
if (f.get.isInstanceOf[Long]) { //to handle Ids. Might need more here for some other stuff.
val value = java.lang.Long.parseLong(param).asInstanceOf[f.ValueType] //cast the param value for use by Mapper QueryParam
val r = List(By[RESTType, KeyType, f.ValueType](f, value)) //Create the QueryParam
result = result ::: r //add it to the list
} else { //strings
val value = param.asInstanceOf[f.ValueType] //cast the param value for use by Mapper QueryParam
val r = List(By[RESTType, KeyType, f.ValueType](f, value)) //Create the QueryParam
result = result ::: r //add it to the list
}
}})
result //return
}
def setFieldsFromParams(item:RESTType):Boolean = {
var success = true
var i = 0
item.getSingleton.mappedFields.foreach(z => {
val f = item.fieldByName(z.name).open_!
if (success) {
if ((f.dbNotNull_?) && (S.param(f.dbColumnName.toString()).isEmpty) && !f.dbColumnName.equals("id")) {
success = false
} else if (!(S.param(f.dbColumnName.toString).isEmpty)) {
i = i + 1
var param = S.param(f.dbColumnName.toString()).open_! //get that param value
if (f.get.isInstanceOf[Long]) { //to handle Ids. Might need more here for some other stuff.
val value = java.lang.Long.parseLong(param).asInstanceOf[z.ValueType] //cast the param value for use by Mapper QueryParam
f.setFromAny(value)
} else { //strings
val value = param.asInstanceOf[z.ValueType] //cast the param value for use by Mapper QueryParam
f.setFromAny(value)
}
}
}
})
item.getSingleton.mappedFields.foreach(f => println(item.fieldByName(f.name).open_!.dbColumnName + "=" + f.get))
if (i > 0) item.save() else false
}
}