alvinalexander.com | career | drupal | java | mac | mysql | perl | scala | uml | unix  
/** * This function is the global (for all MetaMappers that have * not changed their formatFormElement function) that * converts a name and form for a given field in the * model to XHTML for presentation in the browser. By * default, a table row ( <tr> ) is presented, but * you can change the function to display something else. */ var formatFormElement: (NodeSeq, NodeSeq) => NodeSeq = (name, form) => <xml:group> <td>{name} <td>{form} </tr> /** * What are the rules and mechanisms for putting quotes around table names? */ val quoteTableName: FactoryMaker[String => String] = new FactoryMaker[String => String]((s: String) => if (s.indexOf(' ') >= 0) '"'+s+'"' else s) {} /** * What are the rules and mechanisms for putting quotes around column names? */ val quoteColumnName: FactoryMaker[String => String] = new FactoryMaker[String => String]((s: String) => if (s.indexOf(' ') >= 0) '"'+s+'"' else s) {} /** * Function that determines if foreign key constraints are * created by Schemifier for the specified connection. * * Note: The driver choosen must also support foreign keys for * creation to happen */ var createForeignKeys_? : ConnectionIdentifier => Boolean = c => false /** * This function is used to calculate the displayName of a field. Can be * used to easily localize fields based on the locale in the * current request */ val displayNameCalculator: FactoryMaker[(BaseMapper, Locale, String) => String] = new FactoryMaker[(BaseMapper, Locale, String) => String]((m: BaseMapper,l: Locale,name: String) => name) {} /** * Calculate the name of a column based on the name * of the MappedField. Must be set in Boot before any code * that touches the MetaMapper. * * To get snake_case, use this: * * MapperRules.columnName = (_,name) => StringHelpers.snakify(name) */ var columnName: (ConnectionIdentifier,String) => String = (_,name) => name.toLowerCase /** * Calculate the name of a table based on the name * of the Mapper. Must be set in Boot before any code * that tocuhes the MetaMapper. * * To get snake_case, use this * * MapperRules.tableName = (_,name) => StringHelpers.snakify(name) */ var tableName: (ConnectionIdentifier,String) => String = (_,name) => name.toLowerCase } trait MetaMapper[A<:Mapper[A]] extends BaseMetaMapper with Mapper[A] { self: A => private val logger = Logger(classOf[MetaMapper[A]]) case class FieldHolder(name: String, method: Method, field: MappedField[_, A]) type RealType = A def beforeValidation: List[A => Unit] = Nil def beforeValidationOnCreate: List[A => Unit] = Nil def beforeValidationOnUpdate: List[A => Unit] = Nil def afterValidation: List[A => Unit] = Nil def afterValidationOnCreate: List[A => Unit] = Nil def afterValidationOnUpdate: List[A => Unit] = Nil def beforeSave: List[A => Unit] = Nil def beforeCreate: List[(A) => Unit] = Nil def beforeUpdate: List[(A) => Unit] = Nil def afterSave: List[(A) => Unit] = Nil def afterCreate: List[(A) => Unit] = Nil def afterUpdate: List[(A) => Unit] = Nil def beforeDelete: List[(A) => Unit] = Nil def afterDelete: List[(A) => Unit] = Nil /** * If there are model-specific validations to perform, override this * method and return an additional list of validations to perform */ def validation: List[A => List[FieldError]] = Nil private def clearPostCommit(in: A) { in.addedPostCommit = false } private def clearPCFunc: A => Unit = clearPostCommit _ def afterCommit: List[A => Unit] = Nil def dbDefaultConnectionIdentifier: ConnectionIdentifier = DefaultConnectionIdentifier def findAll(): List[A] = findMapDb(dbDefaultConnectionIdentifier, Nil :_*)(v => Full(v)) def findAllDb(dbId:ConnectionIdentifier): List[A] = findMapDb(dbId, Nil :_*)(v => Full(v)) def countByInsecureSql(query: String, checkedBy: IHaveValidatedThisSQL): scala.Long = countByInsecureSqlDb(dbDefaultConnectionIdentifier, query, checkedBy) def countByInsecureSqlDb(dbId: ConnectionIdentifier, query: String, checkedBy: IHaveValidatedThisSQL): scala.Long = DB.use(dbId)(DB.prepareStatement(query, _)(DB.exec(_)(rs => if (rs.next) rs.getLong(1) else 0L))) def findAllByInsecureSql(query: String, checkedBy: IHaveValidatedThisSQL): List[A] = findAllByInsecureSqlDb(dbDefaultConnectionIdentifier, query, checkedBy) /** * Execute a PreparedStatement and return a List of Mapper instances. {@code f} is * where the user will do the work of creating the PreparedStatement and * preparing it for execution. * * @param f A function that takes a SuperConnection and returns a PreparedStatement. * @return A List of Mapper instances. */ def findAllByPreparedStatement(f: SuperConnection => PreparedStatement): List[A] = { DB.use(dbDefaultConnectionIdentifier) { conn => findAllByPreparedStatement(dbDefaultConnectionIdentifier, f(conn)) } } def findAllByPreparedStatement(dbId: ConnectionIdentifier, stmt: PreparedStatement): List[A] = findAllByPreparedStatementDb(dbId, stmt)(a => Full(a)) def findAllByPreparedStatementDb[T](dbId: ConnectionIdentifier, stmt: PreparedStatement)(f: A => Box[T]): List[T] = { DB.exec(stmt) { rs => createInstances(dbId, rs, Empty, Empty, f) } } def findAllByInsecureSqlDb(dbId: ConnectionIdentifier, query: String, checkedBy: IHaveValidatedThisSQL): List[A] = findMapByInsecureSqlDb(dbId, query, checkedBy)(a => Full(a)) def findMapByInsecureSql[T](query: String, checkedBy: IHaveValidatedThisSQL) (f: A => Box[T]): List[T] = findMapByInsecureSqlDb(dbDefaultConnectionIdentifier, query, checkedBy)(f) def findMapByInsecureSqlDb[T](dbId: ConnectionIdentifier, query: String, checkedBy: IHaveValidatedThisSQL)(f: A => Box[T]): List[T] = { DB.use(dbId) { conn => DB.prepareStatement(query, conn) { st => DB.exec(st) { rs => createInstances(dbId, rs, Empty, Empty, f) } } } } def dbAddTable: Box[() => Unit] = Empty def count: Long = countDb(dbDefaultConnectionIdentifier, Nil :_*) def count(by: QueryParam[A]*): Long = countDb(dbDefaultConnectionIdentifier, by:_*) def countDb(dbId: ConnectionIdentifier, by: QueryParam[A]*): Long = { DB.use(dbId) { conn => val bl = by.toList ::: addlQueryParams.is val (query, start, max) = addEndStuffs(addFields("SELECT COUNT(*) FROM "+MapperRules.quoteTableName.vend(_dbTableNameLC)+" ", false, bl, conn), bl, conn) DB.prepareStatement(query, conn) { st => setStatementFields(st, bl, 1, conn) DB.exec(st) { rs => if (rs.next) rs.getLong(1) else 0 } } } } //type KeyDude = T forSome {type T} type OtherMapper = KeyedMapper[_, _] // T forSome {type T <: KeyedMapper[KeyDude, T]} type OtherMetaMapper = KeyedMetaMapper[_, _] // T forSome {type T <: KeyedMetaMapper[KeyDude, OtherMapper]} //type OtherMapper = KeyedMapper[_, (T forSome {type T})] //type OtherMetaMapper = KeyedMetaMapper[_, OtherMapper] def findAllFields(fields: scala.collection.Seq[SelectableField], by: QueryParam[A]*): List[A] = findMapFieldDb(dbDefaultConnectionIdentifier, fields, by :_*)(v => Full(v)) def findAllFieldsDb(dbId: ConnectionIdentifier, fields: Seq[SelectableField], by: QueryParam[A]*): List[A] = findMapFieldDb(dbId, fields, by :_*)(v => Full(v)) private def dealWithPrecache(ret: List[A], by: Seq[QueryParam[A]]): List[A] = { val precache: List[PreCache[A, _, _]] = by.toList.flatMap{case j: PreCache[A, _, _] => List[PreCache[A, _, _]](j) case _ => Nil} for (j <- precache) { type FT = j.field.FieldType type MT = T forSome {type T <: KeyedMapper[FT, T]} val ol: List[MT] = if (!j.deterministic) { def filter(in: Seq[FT]): Seq[FT] = in.flatMap{ case null => Nil case x: Number if x.longValue == 0L => Nil case x => List(x) } val lst: Set[FT] = Set(filter(ret.map(v => v.getSingleton.getActualField(v, j.field).is.asInstanceOf[FT])) :_*) j.field.dbKeyToTable. asInstanceOf[MetaMapper[A]]. findAll(ByList(j.field.dbKeyToTable.primaryKeyField. asInstanceOf[MappedField[FT, A]], lst.toList)).asInstanceOf[List[MT]] } else { j.field.dbKeyToTable. asInstanceOf[MetaMapper[A]]. findAll(new InThing[A]{ type JoinType = FT type InnerType = A val outerField: MappedField[JoinType, A] = j.field.dbKeyToTable.primaryKeyField.asInstanceOf[MappedField[JoinType, A]] val innerField: MappedField[JoinType, A] = j.field.asInstanceOf[MappedField[JoinType, A]] val innerMeta: MetaMapper[A] = j.field.fieldOwner.getSingleton def notIn = false val queryParams: List[QueryParam[A]] = by.toList }.asInstanceOf[QueryParam[A]] ).asInstanceOf[List[MT]] } val map: Map[FT, MT] = Map(ol.map(v => (v.primaryKeyField.is, v)) :_*) for (i <- ret) { val field: MappedForeignKey[FT, A, _] = getActualField(i, j.field).asInstanceOf[MappedForeignKey[FT, A, _]] map.get(field.is) match { case v => field._primeObj(Box(v)) } //field.primeObj(Box(map.get(field.is).map(_.asInstanceOf[QQ]))) } } ret } def findAll(by: QueryParam[A]*): List[A] = dealWithPrecache(findMapDb(dbDefaultConnectionIdentifier, by :_*) (v => Full(v)), by) def findAllDb(dbId: ConnectionIdentifier,by: QueryParam[A]*): List[A] = dealWithPrecache(findMapDb(dbId, by :_*)(v => Full(v)), by) def bulkDelete_!!(by: QueryParam[A]*): Boolean = bulkDelete_!!(dbDefaultConnectionIdentifier, by :_*) def bulkDelete_!!(dbId: ConnectionIdentifier, by: QueryParam[A]*): Boolean = { DB.use(dbId) { conn => val bl = by.toList ::: addlQueryParams.is val (query, start, max) = addEndStuffs(addFields("DELETE FROM "+MapperRules.quoteTableName.vend(_dbTableNameLC)+" ", false, bl, conn), bl, conn) DB.prepareStatement(query, conn) { st => setStatementFields(st, bl, 1, conn) st.executeUpdate true } } } private def distinct(in: Seq[QueryParam[A]]): String = in.find {case Distinct() => true case _ => false}.isDefined match { case false => "" case true => " DISTINCT " } def findMap[T](by: QueryParam[A]*)(f: A => Box[T]) = findMapDb(dbDefaultConnectionIdentifier, by :_*)(f) def findMapDb[T](dbId: ConnectionIdentifier, by: QueryParam[A]*)(f: A => Box[T]): List[T] = findMapFieldDb(dbId, mappedFields, by :_*)(f) /** * Given fields, a connection and the query parameters, build a query and return the query String, * and Start or MaxRows values (depending on whether the driver supports LIMIT and OFFSET) * and the complete List of QueryParams based on any synthetic query parameters calculated during the * query creation. * * @param fields -- a Seq of the fields to be selected * @param conn -- the SuperConnection to be used for calculating the query * @param by -- the varg of QueryParams * * @returns a Tuple of the Query String, Start (offset), MaxRows (limit), and the list of all query parameters * including and synthetic query parameters */ def buildSelectString(fields: Seq[SelectableField], conn: SuperConnection, by: QueryParam[A]*): (String, Box[Long], Box[Long], List[QueryParam[A]]) = { val bl = by.toList ::: addlQueryParams.is val selectStatement = "SELECT "+ distinct(by)+ fields.map(_.dbSelectString). mkString(", ")+ " FROM "+MapperRules.quoteTableName.vend(_dbTableNameLC)+" " val (str, start, max) = addEndStuffs(addFields(selectStatement, false, bl, conn), bl, conn) (str, start, max, bl) } def findMapFieldDb[T](dbId: ConnectionIdentifier, fields: Seq[SelectableField], by: QueryParam[A]*)(f: A => Box[T]): List[T] = { DB.use(dbId) { conn => val (query, start, max, bl) = buildSelectString(fields, conn, by :_*) DB.prepareStatement(query, conn) { st => setStatementFields(st, bl, 1, conn) DB.exec(st)(createInstances(dbId, _, start, max, f)) } } } def create: A = createInstance object addlQueryParams extends net.liftweb.http.RequestVar[List[QueryParam[A]]](Nil) { override val __nameSalt = randomString(10) } private[mapper] def addFields(what: String, whereAdded: Boolean, by: List[QueryParam[A]], conn: SuperConnection): String = { var wav = whereAdded def whereOrAnd = if (wav) " AND " else {wav = true; " WHERE "} class DBFuncWrapper(dbFunc: Box[String]) { def apply(field: String) = dbFunc match { case Full(f) => f+"("+field+")" case _ => field } } implicit def dbfToFunc(in: Box[String]): DBFuncWrapper = new DBFuncWrapper(in) by match { case Nil => what case x :: xs => { var updatedWhat = what x match { case Cmp(field, opr, Full(_), _, dbFunc) => (1 to field.dbColumnCount).foreach { cn => updatedWhat = updatedWhat + whereOrAnd + dbFunc(MapperRules.quoteColumnName.vend(field.dbColumnNames(field.name)(cn - 1)))+" "+opr+" ? " } case Cmp(field, opr, _, Full(otherField), dbFunc) => (1 to field.dbColumnCount).foreach { cn => updatedWhat = updatedWhat + whereOrAnd + dbFunc(MapperRules.quoteColumnName.vend(field.dbColumnNames(field.name)(cn - 1)))+" "+opr+" "+ MapperRules.quoteColumnName.vend(otherField.dbColumnNames(otherField.name)(cn - 1)) } case Cmp(field, opr, Empty, Empty, dbFunc) => (1 to field.dbColumnCount).foreach (cn => updatedWhat = updatedWhat + whereOrAnd + dbFunc(MapperRules.quoteColumnName.vend(field.dbColumnNames(field.name)(cn - 1)))+" "+opr+" ") // For vals, add "AND $fieldname = ? [OR $fieldname = ?]*" to the query. The number // of fields you add onto the query is equal to vals.length case ByList(field, orgVals) => val vals = Set(orgVals :_*).toList // faster than list.removeDuplicates if (vals.isEmpty) updatedWhat = updatedWhat + whereOrAnd + " 0 = 1 " else updatedWhat = updatedWhat + vals.map(v => MapperRules.quoteColumnName.vend(field._dbColumnNameLC)+ " = ?").mkString(whereOrAnd+" (", " OR ", ")") case in: InRaw[A, _] => updatedWhat = updatedWhat + whereOrAnd + (in.rawSql match { case null | "" => " 0 = 1 " case sql => " "+MapperRules.quoteColumnName.vend(in.field._dbColumnNameLC)+" IN ( "+sql+" ) " }) case (in: InThing[A]) => updatedWhat = updatedWhat + whereOrAnd + MapperRules.quoteColumnName.vend(in.outerField._dbColumnNameLC)+in.inKeyword+ "("+in.innerMeta.addEndStuffs(in.innerMeta.addFields("SELECT "+ in.distinct+ MapperRules.quoteColumnName.vend(in.innerField._dbColumnNameLC)+ " FROM "+ MapperRules.quoteTableName.vend(in.innerMeta._dbTableNameLC)+" ",false, in.queryParams, conn), in.queryParams, conn)._1+" ) " // Executes a subquery with {@code query} case BySql(query, _, _*) => updatedWhat = updatedWhat + whereOrAnd + " ( "+ query +" ) " case _ => } addFields(updatedWhat, wav, xs, conn) } } } private[mapper] def setStatementFields(st: PreparedStatement, by: List[QueryParam[A]], curPos: Int, conn: SuperConnection): Int = { by match { case Nil => curPos case Cmp(field, _, Full(value), _, _) :: xs => setPreparedStatementValue(conn, st, curPos, field, field.targetSQLType, field.convertToJDBCFriendly(value), objectSetterFor(field)) setStatementFields(st, xs, curPos + 1, conn) case ByList(field, orgVals) :: xs => { val vals = Set(orgVals :_*).toList var newPos = curPos vals.foreach(v => { setPreparedStatementValue(conn, st, newPos, field, field.targetSQLType, field.convertToJDBCFriendly(v), objectSetterFor(field)) newPos = newPos + 1 }) setStatementFields(st, xs, newPos, conn) } case (in: InThing[A]) :: xs => val newPos = in.innerMeta.setStatementFields(st, in.queryParams, curPos, conn) setStatementFields(st, xs, newPos, conn) case BySql(query, who, params @ _*) :: xs => { params.toList match { case Nil => setStatementFields(st, xs, curPos, conn) case List(i: Int) => st.setInt(curPos, i) setStatementFields(st, xs, curPos + 1, conn) case List(lo: Long) => st.setLong(curPos, lo) setStatementFields(st, xs, curPos + 1, conn) case List(s: String) => st.setString(curPos, s) setStatementFields(st, xs, curPos + 1, conn) // Allow specialization of time-related values based on the input parameter case List(t: java.sql.Timestamp) => st.setTimestamp(curPos, t) setStatementFields(st, xs, curPos + 1, conn) case List(d: java.sql.Date) => st.setDate(curPos, d) setStatementFields(st, xs, curPos + 1, conn) case List(t: java.sql.Time) => st.setTime(curPos, t) setStatementFields(st, xs, curPos + 1, conn) // java.util.Date goes last, since it's a superclass of java.sql.{Date,Time,Timestamp} case List(d: Date) => st.setTimestamp(curPos, new java.sql.Timestamp(d.getTime)) setStatementFields(st, xs, curPos + 1, conn) case List(field: BaseMappedField) => setPreparedStatementValue(conn, st, curPos, field, field.targetSQLType, field.jdbcFriendly, objectSetterFor(field)) setStatementFields(st, xs, curPos + 1, conn) case p :: ps => setStatementFields(st, BySql[A](query, who, p) :: BySql[A](query, who, ps: _*) :: xs, curPos, conn) } } case _ :: xs => { setStatementFields(st, xs, curPos, conn) } } } // def find(by: QueryParam): Box[A] = find(List(by)) private def _addOrdering(in: String, params: List[QueryParam[A]]): String = { params.flatMap{ case OrderBy(field, order, nullOrder) => List(MapperRules.quoteColumnName.vend(field._dbColumnNameLC)+" "+order.sql+" "+(nullOrder.map(_.getSql).openOr(""))) case OrderBySql(sql, _) => List(sql) case _ => Nil } match { case Nil => in case xs => in + " ORDER BY "+xs.mkString(" , ") } } protected def addEndStuffs(in: String, params: List[QueryParam[A]], conn: SuperConnection): (String, Box[Long], Box[Long]) = { val tmp = _addOrdering(in, params) val max = params.foldRight(Empty.asInstanceOf[Box[Long]]){(a,b) => a match {case MaxRows(n) => Full(n); case _ => b}} val start = params.foldRight(Empty.asInstanceOf[Box[Long]]){(a,b) => a match {case StartAt(n) => Full(n); case _ => b}} if (conn.brokenLimit_?) (tmp, start, max) else { val ret = (max, start) match { case (Full(max), Full(start)) => tmp + " LIMIT "+max+" OFFSET "+start case (Full(max), _) => tmp + " LIMIT "+max case (_, Full(start)) => tmp + " LIMIT "+conn.driverType.maxSelectLimit+" OFFSET "+start case _ => tmp } (ret, Empty, Empty) } } def delete_!(toDelete : A): Boolean = toDelete match { case x: MetaMapper[_] => throw new MapperException("Cannot delete the MetaMapper singleton") case _ => thePrimaryKeyField.map(im => DB.use(toDelete.connectionIdentifier) { conn => _beforeDelete(toDelete) val ret = DB.prepareStatement("DELETE FROM "+MapperRules.quoteTableName.vend(_dbTableNameLC) +" WHERE "+im+" = ?", conn) { st => val indVal = indexedField(toDelete) indVal.map{indVal => setPreparedStatementValue(conn, st, 1, indVal, im, objectSetterFor(indVal)) st.executeUpdate == 1 } openOr false } _afterDelete(toDelete) ret } ).openOr(false) } type AnyBound = T forSome {type T} private[mapper] def ??(meth: Method, inst: A) = meth.invoke(inst).asInstanceOf[MappedField[AnyBound, A]] def dirty_?(toTest: A): Boolean = mappedFieldList.exists( mft => ??(mft.method, toTest).dirty_? ) def indexedField(toSave: A): Box[MappedField[Any, A]] = thePrimaryKeyField.map(im => ??(mappedColumns(im.toLowerCase), toSave)) def saved_?(toSave: A): Boolean = toSave match { case x: MetaMapper[_] => throw new MapperException("Cannot test the MetaMapper singleton for saved status") case _ => toSave.persisted_? } /** * This method will update the instance from JSON. It allows for * attacks from untrusted JSON as it bypasses normal security. By * default, the method is protected. You can write a proxy method * to expose the functionality. */ protected def updateFromJSON_!(toUpdate: A, json: JsonAST.JObject): A = { import JsonAST._ toUpdate.runSafe { for { field <- json.obj meth <- _mappedFields.get(field.name) } { val f = ??(meth, toUpdate) f.setFromAny(field.value) } } toUpdate } /** * This method will encode the instance as JSON. It may reveal * data in fields that might otherwise be proprietary. It should * be used with caution and only exposed as a public method * after a security review. */ protected def encodeAsJSON_! (toEncode: A): JsonAST.JObject = { toEncode.runSafe { JsonAST.JObject(JsonAST.JField("$persisted", JsonAST.JBool(toEncode.persisted_?)) :: this.mappedFieldList. flatMap(fh => ??(fh.method, toEncode).asJsonField)) } } /** * Decode the fields from a JSON Object. Should the fields be marked as dirty? */ protected def decodeFromJSON_!(json: JsonAST.JObject, markFieldsAsDirty: Boolean): A = { val ret: A = createInstance import JsonAST._ ret.runSafe { for { field <- json.obj JField("$persisted", JBool(per)) <- field } ret.persisted_? = per for { field <- json.obj meth <- _mappedFields.get(field.name) } { val f = ??(meth, ret) f.setFromAny(field.value) if (!markFieldsAsDirty) f.resetDirty } } ret } def whatToSet(toSave : A) : String = { mappedColumns.filter{c => ??(c._2, toSave).dirty_?}.map{c => c._1 + " = ?"}.toList.mkString("", ",", "") } /** * Run the list of field validations, etc. This is the raw validation, * without the notifications. This method can be over-ridden. */ protected def runValidationList(toValidate: A): List[FieldError] = mappedFieldList.flatMap(f => ??(f.method, toValidate).validate) ::: validation.flatMap{ case pf: PartialFunction[A, List[FieldError]] => if (pf.isDefinedAt(toValidate)) pf(toValidate) else Nil case f => f(toValidate) } final def validate(toValidate: A): List[FieldError] = { logger.debug("Validating dbName=%s, entity=%s".format(dbName, toValidate)) val saved_? = this.saved_?(toValidate) _beforeValidation(toValidate) if (saved_?) _beforeValidationOnUpdate(toValidate) else _beforeValidationOnCreate(toValidate) val ret: List[FieldError] = runValidationList(toValidate) _afterValidation(toValidate) if (saved_?) _afterValidationOnUpdate(toValidate) else _afterValidationOnCreate(toValidate) logger.debug("Validated dbName=%s, entity=%s, result=%s".format(dbName, toValidate, ret)) ret } val elemName = getClass.getSuperclass.getName.split("\\.").toList.last def toXml(what: A): Elem = Elem(null,elemName, mappedFieldList.foldRight[MetaData](Null) {(p, md) => val fld = ??(p.method, what) new UnprefixedAttribute(p.name, Text(fld.toString), md)} ,TopScope) /** * Returns true if none of the fields are dirty */ def clean_?(toCheck: A): Boolean = mappedColumns.foldLeft(true)((bool, ptr) => bool && !(??(ptr._2, toCheck).dirty_?)) /** * Sets a prepared statement value based on the given MappedField's value * and column name. This delegates to the BaseMappedField overload of * setPreparedStatementValue by retrieving the necessary values. * * @param conn The connection for this prepared statement * @param st The prepared statement * @param index The index for this prepared statement value * @param field The field corresponding to this prepared statement value * @param columnName The column name to use to retrieve the type and value * @param setObj A function that we can delegate to for setObject calls */ private def setPreparedStatementValue(conn: SuperConnection, st: PreparedStatement, index: Int, field: MappedField[_, A], columnName : String, setObj : (PreparedStatement, Int, AnyRef, Int) => Unit) { setPreparedStatementValue(conn, st, index, field, field.targetSQLType(columnName), field.jdbcFriendly(columnName), setObj) } /** * Sets a prepared statement value based on the given BaseMappedField's type and value. This * allows us to do special handling based on the type in a central location. * * @param conn The connection for this prepared statement * @param st The prepared statement * @param index The index for this prepared statement value * @param field The field corresponding to this prepared statement value * @param columnType The JDBC SQL Type for this value * @param value The value itself * @param setObj A function that we can delegate to for setObject calls */ private def setPreparedStatementValue(conn: SuperConnection, st: PreparedStatement, index: Int, field: BaseMappedField, columnType : Int, value : Object, setObj : (PreparedStatement, Int, AnyRef, Int) => Unit) { // Remap the type if the driver wants val mappedColumnType = conn.driverType.columnTypeMap(columnType) // We generally use setObject for everything, but we've found some broken JDBC drivers // which has prompted us to use type-specific handling for certain types mappedColumnType match { case Types.VARCHAR => // Set a string with a simple guard for null values st.setString(index, if (value ne null) value.toString else value.asInstanceOf[String]) // Sybase SQL Anywhere and DB2 choke on using setObject for boolean data case Types.BOOLEAN => value match { case intData : java.lang.Integer => st.setBoolean(index, intData.intValue != 0) case b : java.lang.Boolean => st.setBoolean(index, b.booleanValue) // If we can't figure it out, maybe the driver can case other => setObj(st, index, other, mappedColumnType) } // In all other cases, delegate to the driver case _ => setObj(st, index, value, mappedColumnType) } } /** * This is a utility method to simplify using setObject. It's intended use is to * generate a setObject proxy so that the intermediate code doesn't need to be aware * of drivers that ignore column types. */ private def objectSetterFor(field : BaseMappedField) = { (st : PreparedStatement, index : Int, value : AnyRef, columnType : Int) => { if (field.dbIgnoreSQLType_?) { st.setObject(index, value) } else { st.setObject(index, value, columnType) } } } def save(toSave: A): Boolean = { toSave match { case x: MetaMapper[_] => throw new MapperException("Cannot save the MetaMapper singleton") case _ => logger.debug("Saving dbName=%s, entity=%s".format(dbName, toSave)) /** * @return true if there was exactly one row in the result set, false if not. */ def runAppliers(rs: ResultSet) : Boolean = { try { if (rs.next) { val meta = rs.getMetaData toSave.runSafe { for { indexMap <- thePrimaryKeyField auto <- primaryKeyAutogenerated if auto } { findApplier(indexMap, rs.getObject(1)) match { case Full(ap) => ap.apply(toSave, rs.getObject(1)) case _ => } } } !rs.next } else false } finally { rs.close } } /** * Checks whether the result set has exactly one row. */ def hasOneRow(rs: ResultSet) : Boolean = { try { val firstRow = rs.next (firstRow && !rs.next) } finally { rs.close } } if (saved_?(toSave) && clean_?(toSave)) true else { val ret = DB.use(toSave.connectionIdentifier) { conn => _beforeSave(toSave) val ret = if (saved_?(toSave)) { _beforeUpdate(toSave) val ret: Boolean = if (!dirty_?(toSave)) true else { val ret: Boolean = DB.prepareStatement("UPDATE "+MapperRules.quoteTableName.vend(_dbTableNameLC)+" SET "+whatToSet(toSave)+" WHERE "+thePrimaryKeyField.open_! +" = ?", conn) { st => var colNum = 1 // Here we apply each column's value to the prepared statement for (col <- mappedColumns) { val colVal = ??(col._2, toSave) if (!columnPrimaryKey_?(col._1) && colVal.dirty_?) { setPreparedStatementValue(conn, st, colNum, colVal, col._1, objectSetterFor(colVal)) colNum = colNum + 1 } } for { indVal <- indexedField(toSave) indexColumnName <- thePrimaryKeyField } { setPreparedStatementValue(conn, st, colNum, indVal, indexColumnName, objectSetterFor(indVal)) } st.executeUpdate true } ret } _afterUpdate(toSave) ret } else { _beforeCreate(toSave) val query = "INSERT INTO "+MapperRules.quoteTableName.vend(_dbTableNameLC)+" ("+columnNamesForInsert+") VALUES ("+columnQueriesForInsert+")" def prepStat(st : PreparedStatement) { var colNum = 1 for (col <- mappedColumns) { if (!columnPrimaryKey_?(col._1)) { val colVal = col._2.invoke(toSave).asInstanceOf[MappedField[AnyRef, A]] setPreparedStatementValue(conn, st, colNum, colVal, col._1, objectSetterFor(colVal)) colNum = colNum + 1 } } } // Figure out which columns are auto-generated val generatedColumns = (mappedColumnInfo.filter(_._2.dbAutogenerated_?).map(_._1)).toList val ret = conn.driverType.performInsert(conn, query, prepStat, MapperRules.quoteTableName.vend(_dbTableNameLC), generatedColumns) { case Right(count) => count == 1 case Left(rs) => runAppliers(rs) } _afterCreate(toSave) toSave.persisted_? = true ret } _afterSave(toSave) ret } // clear dirty and get rid of history for (col <- mappedColumns) { val colVal = ??(col._2, toSave) if (!columnPrimaryKey_?(col._1) && colVal.dirty_?) { colVal.resetDirty colVal.doneWithSave } } ret } } } /** * This method returns true if the named column is the primary key and * it is autogenerated */ def columnPrimaryKey_?(name: String) = mappedColumnInfo.get(name).map(c => (c.dbPrimaryKey_? && c.dbAutogenerated_?)) getOrElse false def createInstances(dbId: ConnectionIdentifier, rs: ResultSet, start: Box[Long], omax: Box[Long]) : List[A] = createInstances(dbId, rs, start, omax, v => Full(v)) def createInstances[T](dbId: ConnectionIdentifier, rs: ResultSet, start: Box[Long], omax: Box[Long], f: A => Box[T]) : List[T] = { var ret = new ListBuffer[T] val bm = buildMapper(rs) var pos = (start openOr 0L) * -1L val max = omax openOr java.lang.Long.MAX_VALUE while (pos < max && rs.next()) { if (pos >= 0L) { f(createInstance(dbId, rs, bm)).foreach(v => ret += v) } pos = pos + 1L } ret.toList } def appendFieldToStrings(in: A): String = mappedFieldList.map(p => ??(p.method, in).asString).mkString(",") private val columnNameToMappee = new HashMap[String, Box[(ResultSet, Int, A) => Unit]] def buildMapper(rs: ResultSet): List[Box[(ResultSet,Int,A) => Unit]] = columnNameToMappee.synchronized { val meta = rs.getMetaData val colCnt = meta.getColumnCount for { pos <- (1 to colCnt).toList colName = meta.getColumnName(pos).toLowerCase } yield columnNameToMappee.get(colName) match { case None => val colType = meta.getColumnType(pos) Box(mappedColumns.get(colName)).flatMap{ fieldInfo => val setTo = { val tField = fieldInfo.invoke(this).asInstanceOf[MappedField[AnyRef, A]] Some(colType match { case Types.INTEGER | Types.BIGINT => { val bsl = tField.buildSetLongValue(fieldInfo, colName) (rs: ResultSet, pos: Int, objInst: A) => bsl(objInst, rs.getLong(pos), rs.wasNull)} case Types.VARCHAR => { val bsl = tField.buildSetStringValue(fieldInfo, colName) (rs: ResultSet, pos: Int, objInst: A) => bsl(objInst, rs.getString(pos))} case Types.DATE | Types.TIME | Types.TIMESTAMP => val bsl = tField.buildSetDateValue(fieldInfo, colName) (rs: ResultSet, pos: Int, objInst: A) => bsl(objInst, rs.getTimestamp(pos)) case Types.BOOLEAN | Types.BIT =>{ val bsl = tField.buildSetBooleanValue(fieldInfo, colName) (rs: ResultSet, pos: Int, objInst: A) => bsl(objInst, rs.getBoolean(pos), rs.wasNull)} case _ => { (rs: ResultSet, pos: Int, objInst: A) => { val res = rs.getObject(pos) findApplier(colName, res).foreach(f => f(objInst, res)) } } }) } columnNameToMappee(colName) = Box(setTo) setTo } case Some(of) => of } } def createInstance(dbId: ConnectionIdentifier, rs : ResultSet, mapFuncs: List[Box[(ResultSet,Int,A) => Unit]]) : A = { val ret: A = createInstance.connectionIdentifier(dbId) ret.persisted_? = true for { (fb, pos) <- mapFuncs.zipWithIndex f <- fb } f(rs, pos + 1, ret) ret } protected def findApplier(name: String, inst: AnyRef): Box[((A, AnyRef) => Unit)] = synchronized { val clz = inst match { case null => null case _ => inst.getClass.asInstanceOf[Class[(C forSome {type C})]] } val look = (name.toLowerCase, if (clz ne null) Full(clz) else Empty) Box(mappedAppliers.get(look) orElse { val newFunc = createApplier(name, inst) mappedAppliers(look) = newFunc Some(newFunc) }) } private def createApplier(name : String, inst : AnyRef /*, clz : Class*/) : (A, AnyRef) => Unit = { val accessor = mappedColumns.get(name) orElse mappedColumns.get(name.toLowerCase) if ((accessor eq null) || accessor == None) { null } else { (accessor.get.invoke(this).asInstanceOf[MappedField[AnyRef, A]]).buildSetActualValue(accessor.get, inst, name) } } def fieldMapperPF(transform: (BaseOwnedMappedField[A] => NodeSeq), actual: A): PartialFunction[String, NodeSeq => NodeSeq] = { Map.empty ++ mappedFieldList.map ( mf => (mf.name, ((ignore: NodeSeq) => transform(??(mf.method, actual)))) ) } private[mapper] def checkFieldNames(in: A): Unit = { mappedFieldList.foreach(f => ??(f.method, in) match { case field if (field.i_name_! eq null) => field.setName_!(f.name) case _ => }) } /** * Get a field by the field name * @param fieldName -- the name of the field to get * @param actual -- the instance to get the field on * * @return Box[The Field] (Empty if the field is not found) */ def fieldByName[T](fieldName: String, actual: A): Box[MappedField[T, A]] = Box(_mappedFields.get(fieldName)). map(meth => ??(meth, actual).asInstanceOf[MappedField[T,A]]) /** * A partial function that takes an instance of A and a field name and returns the mapped field */ lazy val fieldMatcher: PartialFunction[(A, String), MappedField[Any, A]] = { case (actual, fieldName) if _mappedFields.contains(fieldName) => fieldByName[Any](fieldName, actual).open_! // we know this is defined } def createInstance: A = rootClass.newInstance.asInstanceOf[A] def fieldOrder: List[BaseOwnedMappedField[A]] = Nil protected val rootClass = this.getClass.getSuperclass private val mappedAppliers = new HashMap[(String, Box[Class[(C forSome {type C})]]), (A, AnyRef) => Unit]; private val _mappedFields = new HashMap[String, Method]; private[mapper] var mappedFieldList: List[FieldHolder] = Nil; // new Array[Triple[String, Method, MappedField[Any,Any]]](); private var mappedCallbacks: List[(String, Method)] = Nil private var mappedColumns: SortedMap[String, Method] = TreeMap() private var mappedColumnInfo: SortedMap[String, MappedField[AnyRef, A]] = TreeMap() /** * The primary key column. This used to be indexMap */ private var thePrimaryKeyField: Box[String] = Empty /** * If the primary key field is autogenerated, this will be Full(true) */ private var primaryKeyAutogenerated: Box[Boolean] = Empty this.runSafe { logger.debug("Initializing MetaMapper for %s".format(internalTableName_$_$)) val tArray = new ListBuffer[FieldHolder] def isLifecycle(m: Method) = classOf[LifecycleCallbacks].isAssignableFrom(m.getReturnType) val mapperAccessMethods = new FieldFinder[MappedField[_,_]](this, logger).accessorMethods mappedCallbacks = mapperAccessMethods.filter(isLifecycle).map(v => (v.getName, v)) for (v <- mapperAccessMethods) { v.invoke(this) match { case mf: MappedField[AnyRef, A] if !mf.ignoreField_? => mf.setName_!(v.getName) tArray += FieldHolder(mf.name, v, mf) for (colName <- mf.dbColumnNames(v.getName).map(MapperRules.quoteColumnName.vend).map(_.toLowerCase)) { mappedColumnInfo += colName -> mf mappedColumns += colName -> v } if (mf.dbPrimaryKey_?) { thePrimaryKeyField = Full(MapperRules.quoteColumnName.vend(mf._dbColumnNameLC)) primaryKeyAutogenerated = Full(mf.dbAutogenerated_?) } case _ => } } def findPos(in: AnyRef): Box[Int] = { tArray.toList.zipWithIndex.filter(mft => in eq mft._1.field) match { case Nil => Empty case x :: xs => Full(x._2) } } val resArray = new ListBuffer[FieldHolder]; fieldOrder.foreach(f => findPos(f).foreach(pos => resArray += tArray.remove(pos))) tArray.foreach(mft => resArray += mft) mappedFieldList = resArray.toList mappedFieldList.foreach(ae => _mappedFields(ae.name) = ae.method) logger.trace("Mapped fields for %s: %s".format(dbName, mappedFieldList.map(_.name).mkString(","))) } val columnNamesForInsert = (mappedColumnInfo.filter(c => !(c._2.dbPrimaryKey_? && c._2.dbAutogenerated_?)).map(_._1)).toList.mkString(",") val columnQueriesForInsert = { (mappedColumnInfo.filter(c => !(c._2.dbPrimaryKey_? && c._2.dbAutogenerated_?)).map(p => "?")).toList.mkString(",") } private def fixTableName(name: String) = { val tableName = MapperRules.tableName(connectionIdentifier,clean(name)) if (DB.reservedWords.contains(tableName.toLowerCase)) tableName+"_t" else tableName } private def internalTableName_$_$ = getClass.getSuperclass.getName.split("\\.").toList.last; /** * This function converts a header name into the appropriate * XHTML format for displaying across the headers of a * formatted block. The default is <th> for use * in XHTML tables. If you change this function, the change * will be used for this MetaMapper unless you override the * htmlHeades method */ var displayNameToHeaderElement: String => NodeSeq = MapperRules.displayNameToHeaderElement def htmlHeaders: NodeSeq = mappedFieldList.filter(_.field.dbDisplay_?). flatMap(mft => displayNameToHeaderElement(mft.field.displayName)) /** * The mapped fields */ lazy val mappedFields: Seq[BaseMappedField] = mappedFieldList.map(f => f.field) /** * the mapped fields as MappedField rather than BaseMappedField */ lazy val mappedFieldsForModel: List[MappedField[_, A]] = mappedFieldList.map(_.field) /** * This function converts an element into the appropriate * XHTML format for displaying across a line * formatted block. The default is <td> for use * in XHTML tables. If you change this function, the change * will be used for this MetaMapper unless you override the * doHtmlLine method. */ var displayFieldAsLineElement: NodeSeq => NodeSeq = MapperRules.displayFieldAsLineElement def doHtmlLine(toLine: A): NodeSeq = mappedFieldList.filter(_.field.dbDisplay_?). flatMap(mft => displayFieldAsLineElement(??(mft.method, toLine).asHtml)) def asJs(actual: A): JsExp = { JE.JsObj(("$lift_class", JE.Str(dbTableName)) :: mappedFieldList. map(f => ??(f.method, actual)).filter(_.renderJs_?).flatMap(_.asJs).toList ::: actual.suplementalJs(Empty) :_*) } def asHtml(toLine: A): NodeSeq = Text(internalTableName_$_$) :: Text("={ ") :: (for (mft <- mappedFieldList if mft.field.dbDisplay_? ; val field = ??(mft.method, toLine)) yield <span>{field.displayName}={field.asHtml}  ) :::List(Text(" }")) /** * This function converts a name and form for a given field in the * model to XHTML for presentation in the browser. By * default, a table row ( <tr> ) is presented, but * you can change the function to display something else. */ var formatFormElement: (NodeSeq, NodeSeq) => NodeSeq = MapperRules.formatFormElement def formatFormLine(displayName: NodeSeq, form: NodeSeq): NodeSeq = formatFormElement(displayName, form) def toForm(toMap: A): NodeSeq = mappedFieldList.map(e => ??(e.method, toMap)). filter(f => f.dbDisplay_? && f.dbIncludeInForm_?).flatMap ( field => field.toForm.toList. flatMap(form => formatFormLine(Text(field.displayName), form)) ) /** * Present the model as a HTML using the same formatting as toForm * * @param toMap the instance to generate the HTML for * * @return the html view of the model */ def toHtml(toMap: A): NodeSeq = mappedFieldList.map(e => ??(e.method, toMap)). filter(f => f.dbDisplay_?).flatMap ( field => formatFormLine(Text(field.displayName), field.asHtml) ) /** * Get the fields (in order) for displaying a form */ def formFields(toMap: A): List[MappedField[_, A]] = mappedFieldList.map(e => ??(e.method, toMap)).filter(f => f.dbDisplay_? && f.dbIncludeInForm_?) /** * map the fields titles and forms to generate a list * @param func called with displayHtml, fieldId, form */ def mapFieldTitleForm[T](toMap: A, func: (NodeSeq, Box[NodeSeq], NodeSeq) => T): List[T] = formFields(toMap).flatMap(field => field.toForm. map(fo => func(field.displayHtml, field.fieldId, fo))) /** * flat map the fields titles and forms to generate a list * @param func called with displayHtml, fieldId, form */ def flatMapFieldTitleForm[T](toMap: A, func: (NodeSeq, Box[NodeSeq], NodeSeq) => Seq[T]): List[T] = formFields(toMap).flatMap(field => field.toForm.toList. flatMap(fo => func(field.displayHtml, field.fieldId, fo))) /** * flat map the fields titles and forms to generate a list * @param func called with displayHtml, fieldId, form */ def flatMapFieldTitleForm2[T](toMap: A, func: (NodeSeq, MappedField[_, A], NodeSeq) => Seq[T]): List[T] = formFields(toMap).flatMap(field => field.toForm.toList. flatMap(fo => func(field.displayHtml, field, fo))) /** * Given the prototype field (the field on the Singleton), get the field from the instance * @param actual -- the Mapper instance * @param protoField -- the field from the MetaMapper (Singleton) * * @return the field from the actual object */ def getActualField[T](actual: A, protoField: MappedField[T, A]): MappedField[T, A] = ??(_mappedFields(protoField.name), actual).asInstanceOf[MappedField[T,A]] /** * Given the prototype field (the field on the Singleton), get the field from the instance * @param actual -- the Mapper instance * @param protoField -- the field from the MetaMapper (Singleton) * * @return the field from the actual object */ def getActualBaseField(actual: A, protoField: BaseOwnedMappedField[A]): BaseOwnedMappedField[A] = ??(_mappedFields(protoField.name), actual) // .asInstanceOf[MappedField[T,A]] /** * The name of the database table. Override this method if you * want to change the table to something other than the name of the Mapper class */ def dbTableName = internal_dbTableName /** * The name of the mapped object */ override def dbName: String = internalTableName_$_$ /** * The table name, to lower case... ensures that it works on all DBs */ final def _dbTableNameLC = { val name = dbTableName val conn = DB.currentConnection if (conn.isDefined) { val rc = conn.open_! if (rc.metaData.storesMixedCaseIdentifiers) name else name.toLowerCase } else name } // dbTableName.toLowerCase private[mapper] lazy val internal_dbTableName = fixTableName(internalTableName_$_$) private def setupInstanceForPostCommit(inst: A) { afterCommit match { case Nil => // If there's no post-commit functions, then don't // record (and retain) the instance case pcf => if (!inst.addedPostCommit) { DB.appendPostFunc(inst.connectionIdentifier, () => (clearPCFunc :: pcf).foreach(_(inst))) inst.addedPostCommit = true } } } private def eachField(what: A, toRun: List[(A) => Any])(f: (LifecycleCallbacks) => Any) { mappedCallbacks.foreach (e => e._2.invoke(what) match { case lccb: LifecycleCallbacks => f(lccb) case _ => }) toRun.foreach{tf => tf(what)} } private def _beforeValidation(what: A) {setupInstanceForPostCommit(what); eachField(what, beforeValidation) {field => field.beforeValidation} } private def _beforeValidationOnCreate(what: A) {eachField(what, beforeValidationOnCreate) {field => field.beforeValidationOnCreate} } private def _beforeValidationOnUpdate(what: A) {eachField(what, beforeValidationOnUpdate) {field => field.beforeValidationOnUpdate} } private def _afterValidation(what: A) { eachField(what, afterValidation) {field => field.afterValidation} } private def _afterValidationOnCreate(what: A) {eachField(what, afterValidationOnCreate) {field => field.afterValidationOnCreate} } private def _afterValidationOnUpdate(what: A) {eachField(what, afterValidationOnUpdate) {field => field.afterValidationOnUpdate} } private def _beforeSave(what: A) {setupInstanceForPostCommit(what); eachField(what, beforeSave) {field => field.beforeSave} } private def _beforeCreate(what: A) { eachField(what, beforeCreate) {field => field.beforeCreate} } private def _beforeUpdate(what: A) { eachField(what, beforeUpdate) {field => field.beforeUpdate} } private def _afterSave(what: A) {eachField(what, afterSave) {field => field.afterSave} } private def _afterCreate(what: A) {eachField(what, afterCreate) {field => field.afterCreate} } private def _afterUpdate(what: A) {eachField(what, afterUpdate) {field => field.afterUpdate} } private def _beforeDelete(what: A) {setupInstanceForPostCommit(what); eachField(what, beforeDelete) {field => field.beforeDelete} } private def _afterDelete(what: A) {eachField(what, afterDelete) {field => field.afterDelete} } def beforeSchemifier {} def afterSchemifier {} def dbIndexes: List[BaseIndex[A]] = Nil implicit def fieldToItem[T](in: MappedField[T, A]): IndexItem[A] = IndexField(in) implicit def boundedFieldToItem(in: (MappedField[String, A], Int)): BoundedIndexField[A] = BoundedIndexField(in._1, in._2) // protected def getField(inst : Mapper[A], meth : Method) = meth.invoke(inst, null).asInstanceOf[MappedField[AnyRef,A]] } object OprEnum extends Enumeration { val Eql = Value(1, "=") val <> = Value(2, "<>") val >= = Value(3, ">=") val != = <> val <= = Value(4, "<=") val > = Value(5, ">") val < = Value(6, "<") val IsNull = Value(7, "IS NULL") val IsNotNull = Value(8, "IS NOT NULL") val Like = Value(9, "LIKE") val NotLike = Value(10, "NOT LIKE") } sealed trait BaseIndex[A <: Mapper[A]] { def columns: Seq[IndexItem[A]] } final case class Index[A <: Mapper[A]](columns: List[IndexItem[A]]) extends BaseIndex[A] // (columns :_*) object Index { def apply[A <: Mapper[A]](cols: IndexItem[A] *): Index[A] = new Index[A](cols.toList) } /** * Represents a unique index on the given columns */ final case class UniqueIndex[A <: Mapper[A]](columns: List[IndexItem[A]]) extends BaseIndex[A] // (uniqueColumns : _*) object UniqueIndex { def apply[A <: Mapper[A]](cols: IndexItem[A] *): UniqueIndex[A] = new UniqueIndex[A](cols.toList) } /** * Represents a generic user-specified index on the given columns. The user provides a function to generate the SQL needed to create * the index based on the table and columns. Validation is required since this is raw SQL being run on the database server. */ final case class GenericIndex[A <: Mapper[A]](createFunc: (String,List[String]) => String, validated: IHaveValidatedThisSQL, columns: List[IndexItem[A]]) extends BaseIndex[A] // (indexColumns : _*) object GenericIndex { def apply[A <: Mapper[A]](createFunc: (String,List[String]) => String, validated: IHaveValidatedThisSQL, cols: IndexItem[A] *): GenericIndex[A] = new GenericIndex[A](createFunc, validated, cols.toList) } abstract class IndexItem[A <: Mapper[A]] { def field: BaseMappedField def indexDesc: String } case class IndexField[A <: Mapper[A], T](field: MappedField[T, A]) extends IndexItem[A] { def indexDesc: String = MapperRules.quoteColumnName.vend(field._dbColumnNameLC) } case class BoundedIndexField[A <: Mapper[A]](field: MappedField[String, A], len: Int) extends IndexItem[A] { def indexDesc: String = MapperRules.quoteColumnName.vend(field._dbColumnNameLC)+"("+len+")" } sealed trait QueryParam[O<:Mapper[O]] final case class Cmp[O<:Mapper[O], T](field: MappedField[T,O], opr: OprEnum.Value, value: Box[T], otherField: Box[MappedField[T, O]], dbFunc: Box[String]) extends QueryParam[O] final case class OrderBy[O<:Mapper[O], T](field: MappedField[T,O], order: AscOrDesc, nullOrder: Box[NullOrder]) extends QueryParam[O] sealed trait NullOrder { def getSql: String } case object NullsFirst extends NullOrder { def getSql: String = " NULLS FIRST " } case object NullsLast extends NullOrder { def getSql: String = " NULLS LAST " } object OrderBy { def apply[O <: Mapper[O], T](field: MappedField[T, O], order: AscOrDesc): OrderBy[O, T] = new OrderBy[O, T](field, order, Empty) def apply[O <: Mapper[O], T](field: MappedField[T, O], order: AscOrDesc, no: NullOrder): OrderBy[O, T] = new OrderBy[O, T](field, order, Full(no)) } trait AscOrDesc { def sql: String } case object Ascending extends AscOrDesc { def sql: String = " ASC " } case object Descending extends AscOrDesc { def sql: String = " DESC " } final case class Distinct[O <: Mapper[O]]() extends QueryParam[O] final case class OrderBySql[O <: Mapper[O]](sql: String, checkedBy: IHaveValidatedThisSQL) extends QueryParam[O] final case class ByList[O<:Mapper[O], T](field: MappedField[T,O], vals: Seq[T]) extends QueryParam[O] /** * Represents a query criterion using a parameterized SQL string. Parameters are * substituted in order. For Date/Time types, passing a java.util.Date will result in a * Timestamp parameter. If you want a specific SQL Date/Time type, use the corresponding * java.sql.Date, java.sql.Time, or java.sql.Timestamp classes. */ final case class BySql[O<:Mapper[O]](query: String, checkedBy: IHaveValidatedThisSQL, params: Any*) extends QueryParam[O] final case class MaxRows[O<:Mapper[O]](max: Long) extends QueryParam[O] final case class StartAt[O<:Mapper[O]](start: Long) extends QueryParam[O] final case class Ignore[O <: Mapper[O]]() extends QueryParam[O] sealed abstract class InThing[OuterType <: Mapper[OuterType]] extends QueryParam[OuterType] { type JoinType type InnerType <: Mapper[InnerType] def outerField: MappedField[JoinType, OuterType] def innerField: MappedField[JoinType, InnerType] def innerMeta: MetaMapper[InnerType] def queryParams: List[QueryParam[InnerType]] def notIn: Boolean def inKeyword = if (notIn) " NOT IN " else " IN " def distinct: String = queryParams.find {case Distinct() => true case _ => false}.isDefined match { case false => "" case true => " DISTINCT " } } /** * This QueryParam can be put in a query and will cause the given foreign key field * to be precached. * @param field - the field to precache * @param deterministic - true if the query is deterministic. Will be more efficient. * false if the query is not deterministic. In this case, a SELECT * FROM FK_TABLE WHERE primary_key in (xxx) will * be generated */ final case class PreCache[TheType <: Mapper[TheType], FieldType, OtherType <: KeyedMapper[FieldType, OtherType]](field: MappedForeignKey[FieldType, TheType, OtherType], deterministic: Boolean) extends QueryParam[TheType] object PreCache { def apply[TheType <: Mapper[TheType], FieldType, OtherType <: KeyedMapper[FieldType, OtherType]](field: MappedForeignKey[FieldType , TheType, OtherType]) = new PreCache(field, true) } final case class InRaw[TheType <: Mapper[TheType], T](field: MappedField[T, TheType], rawSql: String, checkedBy: IHaveValidatedThisSQL) extends QueryParam[TheType] object NotIn { def fk[InnerMapper <: Mapper[InnerMapper], JoinTypeA, Zoom <% QueryParam[InnerMapper], OuterMapper <:KeyedMapper[JoinTypeA, OuterMapper]] (fielda: MappedForeignKey[JoinTypeA, InnerMapper, OuterMapper], qp: Zoom*): InThing[OuterMapper] = { new InThing[OuterMapper] { type JoinType = JoinTypeA type InnerType = InnerMapper val outerField: MappedField[JoinType, OuterMapper] = fielda.dbKeyToTable.primaryKeyField val innerField: MappedField[JoinType, InnerMapper] = fielda val innerMeta: MetaMapper[InnerMapper] = fielda.fieldOwner.getSingleton def notIn: Boolean = true val queryParams: List[QueryParam[InnerMapper]] = qp.map{v => val r: QueryParam[InnerMapper] = v; r}.toList } } def apply[InnerMapper <: Mapper[InnerMapper], JoinTypeA, Zoom <% QueryParam[InnerMapper], OuterMapper <: Mapper[OuterMapper]] (_outerField: MappedField[JoinTypeA, OuterMapper], _innerField: MappedField[JoinTypeA, InnerMapper], qp: Zoom*): InThing[OuterMapper] = { new InThing[OuterMapper] { type JoinType = JoinTypeA type InnerType = InnerMapper val outerField: MappedField[JoinType, OuterMapper] = _outerField val innerField: MappedField[JoinType, InnerMapper] = _innerField val innerMeta: MetaMapper[InnerMapper] = innerField.fieldOwner.getSingleton def notIn: Boolean = true val queryParams: List[QueryParam[InnerMapper]] = { qp.map{v => val r: QueryParam[InnerMapper] = v; r}.toList } } } } object In { def fk[InnerMapper <: Mapper[InnerMapper], JoinTypeA, Zoom <% QueryParam[InnerMapper], OuterMapper <:KeyedMapper[JoinTypeA, OuterMapper]] (fielda: MappedForeignKey[JoinTypeA, InnerMapper, OuterMapper], qp: Zoom*): InThing[OuterMapper] = { new InThing[OuterMapper] { type JoinType = JoinTypeA type InnerType = InnerMapper val outerField: MappedField[JoinType, OuterMapper] = fielda.dbKeyToTable.primaryKeyField val innerField: MappedField[JoinType, InnerMapper] = fielda val innerMeta: MetaMapper[InnerMapper] = fielda.fieldOwner.getSingleton def notIn: Boolean = false val queryParams: List[QueryParam[InnerMapper]] = qp.map{v => val r: QueryParam[InnerMapper] = v; r}.toList } } def apply[InnerMapper <: Mapper[InnerMapper], JoinTypeA, Zoom <% QueryParam[InnerMapper], OuterMapper <: Mapper[OuterMapper]] (_outerField: MappedField[JoinTypeA, OuterMapper], _innerField: MappedField[JoinTypeA, InnerMapper], qp: Zoom*): InThing[OuterMapper] = { new InThing[OuterMapper] { type JoinType = JoinTypeA type InnerType = InnerMapper val outerField: MappedField[JoinType, OuterMapper] = _outerField val innerField: MappedField[JoinType, InnerMapper] = _innerField val innerMeta: MetaMapper[InnerMapper] = innerField.fieldOwner.getSingleton def notIn: Boolean = false val queryParams: List[QueryParam[InnerMapper]] = { qp.map{v => val r: QueryParam[InnerMapper] = v; r}.toList } } } } object Like { def apply[O <: Mapper[O]](field: MappedField[String, O], value: String) = Cmp[O, String](field, OprEnum.Like, Full(value), Empty, Empty) } object NotLike { def apply[O <: Mapper[O]](field: MappedField[String, O], value: String) = Cmp[O, String](field, OprEnum.NotLike, Full(value), Empty, Empty) } object By { import OprEnum._ def apply[O <: Mapper[O], T, U <% T](field: MappedField[T, O], value: U) = Cmp[O,T](field, Eql, Full(value), Empty, Empty) def apply[O <: Mapper[O], T](field: MappedNullableField[T, O], value: Box[T]) = value match { case Full(x) => Cmp[O,Box[T]](field, Eql, Full(value), Empty, Empty) case _ => NullRef(field) } def apply[O <: Mapper[O],T, Q <: KeyedMapper[T, Q]](field: MappedForeignKey[T, O, Q], value: Q) = Cmp[O,T](field, Eql, Full(value.primaryKeyField.is), Empty, Empty) def apply[O <: Mapper[O],T, Q <: KeyedMapper[T, Q]](field: MappedForeignKey[T, O, Q], value: Box[Q]) = value match { case Full(v) => Cmp[O,T](field, Eql, Full(v.primaryKeyField.is), Empty, Empty) case _ => Cmp(field, IsNull, Empty, Empty, Empty) } } object NotBy { import OprEnum._ def apply[O <: Mapper[O], T, U <% T](field: MappedField[T, O], value: U) = Cmp[O,T](field, <>, Full(value), Empty, Empty) def apply[O <: Mapper[O], T](field: MappedNullableField[T, O], value: Box[T]) = value match { case Full(x) => Cmp[O,Box[T]](field, <>, Full(value), Empty, Empty) case _ => NotNullRef(field) } def apply[O <: Mapper[O],T, Q <: KeyedMapper[T, Q]](field: MappedForeignKey[T, O, Q], value: Q) = Cmp[O,T](field, <>, Full(value.primaryKeyField.is), Empty, Empty) def apply[O <: Mapper[O],T, Q <: KeyedMapper[T, Q]](field: MappedForeignKey[T, O, Q], value: Box[Q]) = value match { case Full(v) => Cmp[O,T](field, <>, Full(v.primaryKeyField.is), Empty, Empty) case _ => Cmp(field, IsNotNull, Empty, Empty, Empty) } } object ByRef { import OprEnum._ def apply[O <: Mapper[O], T](field: MappedField[T, O], otherField: MappedField[T,O]) = Cmp[O,T](field, Eql, Empty, Full(otherField), Empty) } object NotByRef { import OprEnum._ def apply[O <: Mapper[O], T](field: MappedField[T, O], otherField: MappedField[T,O]) = Cmp[O,T](field, <>, Empty, Full(otherField), Empty) } object By_> { import OprEnum._ def apply[O <: Mapper[O], T, U <% T](field: MappedField[T, O], value: U) = Cmp[O,T](field, >, Full(value), Empty, Empty) def apply[O <: Mapper[O], T](field: MappedField[T, O], otherField: MappedField[T,O]) = Cmp[O,T](field, >, Empty, Full(otherField), Empty) } object By_< { import OprEnum._ def apply[O <: Mapper[O], T, U <% T](field: MappedField[T, O], value: U) = Cmp[O,T](field, <, Full(value), Empty, Empty) def apply[O <: Mapper[O], T](field: MappedField[T, O], otherField: MappedField[T,O]) = Cmp[O,T](field, <, Empty, Full(otherField), Empty) } object NullRef { import OprEnum._ def apply[O <: Mapper[O], T](field: MappedField[T, O]) = Cmp(field, IsNull, Empty, Empty, Empty) } object NotNullRef { import OprEnum._ def apply[O <: Mapper[O], T](field: MappedField[T, O]) = Cmp(field, IsNotNull, Empty, Empty, Empty) } trait LongKeyedMetaMapper[A <: LongKeyedMapper[A]] extends KeyedMetaMapper[Long, A] { self: A => } trait KeyedMetaMapper[Type, A<:KeyedMapper[Type, A]] extends MetaMapper[A] with KeyedMapper[Type, A] { self: A with MetaMapper[A] with KeyedMapper[Type, A] => private def testProdArity(prod: Product): Boolean = { var pos = 0 while (pos < prod.productArity) { if (!prod.productElement(pos).isInstanceOf[QueryParam[A]]) return false pos = pos + 1 } true } type Q = MappedForeignKey[AnyBound, A, OO] with MappedField[AnyBound, A] forSome {type OO <: KeyedMapper[AnyBound, OO]} def asSafeJs(actual: A, f: KeyObfuscator): JsExp = { val pk = actual.primaryKeyField val first = (pk.name, JE.Str(f.obscure(self, pk.is))) JE.JsObj(first :: ("$lift_class", JE.Str(dbTableName)) :: mappedFieldList. map(f => this.??(f.method, actual)). filter(f => !f.dbPrimaryKey_? && f.renderJs_?).flatMap{ case fk: Q => val key = f.obscure(fk.dbKeyToTable, fk.is) List((fk.name, JE.Str(key)), (fk.name+"_obj", JE.AnonFunc("index", JE.JsRaw("return index["+key.encJs+"];").cmd))) case x => x.asJs}.toList ::: actual.suplementalJs(Full(f)) :_*) } private def convertToQPList(prod: Product): Array[QueryParam[A]] = { var pos = 0 val ret = new Array[QueryParam[A]](prod.productArity) while (pos < prod.productArity) { ret(pos) = prod.productElement(pos).asInstanceOf[QueryParam[A]] pos = pos + 1 } ret } private def anyToFindString(in: Any): Box[String] = in match { case Empty | None | null | Failure(_, _, _) => Empty case Full(n) => anyToFindString(n) case Some(n) => anyToFindString(n) case v => Full(v.toString) } private object unapplyMemo extends RequestMemoize[Any, Box[A]] { override protected def __nameSalt = Helpers.randomString(20) } def unapply(key: Any): Option[A] = { if (S.inStatefulScope_?) unapplyMemo(key, this.find(key)) else this.find(key) } def find(key: Any): Box[A] = key match { case qp: QueryParam[A] => find(List(qp.asInstanceOf[QueryParam[A]]) :_*) case prod: Product if (testProdArity(prod)) => find(convertToQPList(prod) :_*) case key => anyToFindString(key) flatMap (find(_)) } def findDb(dbId: ConnectionIdentifier, key: Any): Box[A] = key match { case qp: QueryParam[A] => findDb(dbId, List(qp.asInstanceOf[QueryParam[A]]) :_*) case prod: Product if (testProdArity(prod)) => findDb(dbId, convertToQPList(prod) :_*) case key => anyToFindString(key) flatMap (find(dbId, _)) } /** * Find the element based on the first element of the List */ def find(key: List[String]): Box[A] = key match { case Nil => Empty case x :: _ => find(x) } /** * Find an element by primary key or create a new one */ def findOrCreate(key: Any): A = find(key) openOr create /** * Find an element by primary key or create a new one */ def findOrCreate(key: List[String]): A = find(key) openOr create def find(key: String): Box[A] = dbStringToKey(key) flatMap (realKey => findDbByKey(selectDbForKey(realKey), realKey)) def find(dbId: ConnectionIdentifier, key: String): Box[A] = dbStringToKey(key) flatMap (realKey => findDbByKey(dbId, realKey)) def findByKey(key: Type): Box[A] = findDbByKey(selectDbForKey(key), key) def dbStringToKey(in: String): Box[Type] = primaryKeyField.convertKey(in) private def selectDbForKey(key: Type): ConnectionIdentifier = if (dbSelectDBConnectionForFind.isDefinedAt(key)) dbSelectDBConnectionForFind(key) else dbDefaultConnectionIdentifier def dbSelectDBConnectionForFind: PartialFunction[Type, ConnectionIdentifier] = Map.empty def findDbByKey(dbId: ConnectionIdentifier, key: Type): Box[A] = findDbByKey(dbId, mappedFields, key) def findDbByKey(dbId: ConnectionIdentifier, fields: Seq[SelectableField], key: Type): Box[A] = DB.use(dbId) { conn => val field = primaryKeyField DB.prepareStatement("SELECT "+ fields.map(_.dbSelectString). mkString(", ")+ " FROM "+MapperRules.quoteTableName.vend(_dbTableNameLC)+" WHERE "+MapperRules.quoteColumnName.vend(field._dbColumnNameLC)+" = ?", conn) { st => if (field.dbIgnoreSQLType_?) st.setObject(1, field.makeKeyJDBCFriendly(key)) else st.setObject(1, field.makeKeyJDBCFriendly(key), conn.driverType. columnTypeMap(field. targetSQLType(field._dbColumnNameLC))) DB.exec(st) { rs => val mi = buildMapper(rs) if (rs.next) Full(createInstance(dbId, rs, mi)) else Empty } } } def find(by: QueryParam[A]*): Box[A] = findDb(dbDefaultConnectionIdentifier, by :_*) def findDb(dbId: ConnectionIdentifier, by: QueryParam[A]*): Box[A] = findDb(dbId, mappedFields, by :_*) def findDb(dbId: ConnectionIdentifier, fields: Seq[SelectableField], by: QueryParam[A]*): Box[A] = { DB.use(dbId) { conn => val (query, start, max, bl) = buildSelectString(fields, conn, by :_*) DB.prepareStatement(query, conn) { st => setStatementFields(st, bl, 1, conn) DB.exec(st) { rs => val mi = buildMapper(rs) if (rs.next) Full(createInstance(dbId, rs, mi)) else Empty } } } } override def afterSchemifier { if (crudSnippets_?) { LiftRules.snippets.append(crudSnippets) } } /** * Override this definition in your model to enable CRUD snippets * for that model. Set to false by default. * * Remember to override editSnippetSetup and viewSnippetSetup as well, * as the defaults are broken. * * @return false */ def crudSnippets_? = false /** * Defines the default CRUD snippets. Override if you want to change * the names of the snippets. Defaults are "add", "edit", and "view". * * (No, there's no D in CRUD.) */ def crudSnippets: LiftRules.SnippetPF = { val Name = internal_dbTableName NamedPF("crud "+Name) { case Name :: "add" :: _ => addSnippet case Name :: "edit" :: _ => editSnippet case Name :: "view" :: _ => viewSnippet } } /** * Default snippet for modification. Used by the default add and edit snippets. */ def modSnippet(xhtml: NodeSeq, obj: A, cleanup: (A => Unit)): NodeSeq = { val name = internal_dbTableName def callback() { cleanup(obj) } xbind(name, xhtml)(obj.fieldPF orElse obj.fieldMapperPF(_.toForm.openOr(Text(""))) orElse { case "submit" => label => SHtml.submit(label.text, callback _) }) } /** * Default add snippet. Override to change behavior of the add snippet. */ def addSnippet(xhtml: NodeSeq): NodeSeq = { modSnippet(xhtml, addSnippetSetup, addSnippetCallback _) } /** * Default edit snippet. Override to change behavior of the edit snippet. */ def editSnippet(xhtml: NodeSeq): NodeSeq = { modSnippet(xhtml, editSnippetSetup, editSnippetCallback _) } /** * Default view snippet. Override to change behavior of the view snippet. */ def viewSnippet(xhtml: NodeSeq): NodeSeq = { val Name = internal_dbTableName val obj: A = viewSnippetSetup xbind(Name, xhtml)(obj.fieldPF orElse obj.fieldMapperPF(_.asHtml)) } /** * Lame attempt at automatically getting an object from the HTTP parameters. * BROKEN! DO NOT USE! Only here so that existing sub-classes KeyedMetaMapper * don't have to implement new methods when I commit the CRUD snippets code. */ def objFromIndexedParam: Box[A] = { val found = for ( req <- S.request.toList; (param, value :: _) <- req.params; fh <- mappedFieldList if fh.field.dbIndexed_? == true && fh.name.equals(param) ) yield find(value) found.filter(obj => obj match { case Full(obj) => true case _ => false }) match { case obj :: _ => obj case _ => Empty } } /** * Default setup behavior for the add snippet. Creates a new mapped object. * * @return new mapped object */ def addSnippetSetup: A = { this.create } /** * Default setup behavior for the edit snippet. BROKEN! MUST OVERRIDE IF * USING CRUD SNIPPETS! * * @return a mapped object of this metamapper's type */ def editSnippetSetup: A = { objFromIndexedParam.open_! } /** * Default setup behavior for the view snippet. BROKEN! MUST OVERRIDE IF * USING CRUD SNIPPETS! * * @return a mapped object of this metamapper's type */ def viewSnippetSetup: A = { objFromIndexedParam.open_! } /** * Default callback behavior of the edit snippet. Called when the user * presses submit. Saves the passed in object. * * @param obj mapped object of this metamapper's type */ def editSnippetCallback(obj: A) { obj.save } /** * Default callback behavior of the add snippet. Called when the user * presses submit. Saves the passed in object. * * @param obj mapped object of this metamapper's type */ def addSnippetCallback(obj: A) { obj.save } } class KeyObfuscator { private var to: Map[String, Map[Any, String]] = Map.empty private var from: Map[String, Map[String, Any]] = Map.empty def obscure[KeyType, MetaType <: KeyedMapper[KeyType, MetaType]](theType: KeyedMetaMapper[KeyType, MetaType], key: KeyType): String = synchronized { val local: Map[Any, String] = to.getOrElse(theType._dbTableNameLC, Map.empty) local.get(key) match { case Some(s) => s case _ => val ret = "r"+randomString(15) val l2: Map[Any, String] = local + ( (key -> ret) ) to = to + ( (theType._dbTableNameLC -> l2) ) val lf: Map[String, Any] = from.getOrElse(theType._dbTableNameLC, Map.empty) + ( (ret -> key)) // lf(ret) = key from = from + ( (theType._dbTableNameLC -> lf) ) ret } } def obscure[KeyType, MetaType <: KeyedMapper[KeyType, MetaType]](what: KeyedMapper[KeyType, MetaType]): String = { obscure(what.getSingleton, what.primaryKeyField.is) } def apply[KeyType, MetaType <: KeyedMapper[KeyType, MetaType], Q <% KeyType](theType: KeyedMetaMapper[KeyType, MetaType], key: Q): String = { val k: KeyType = key obscure(theType, k) } def apply[KeyType, MetaType <: KeyedMapper[KeyType, MetaType]](what: KeyedMapper[KeyType, MetaType]): String = { obscure(what) } def recover[KeyType, MetaType <: KeyedMapper[KeyType, MetaType]](theType: KeyedMetaMapper[KeyType, MetaType], id: String): Box[KeyType] = synchronized { for { map <- from.get(theType._dbTableNameLC) item <- map.get(id) } yield item.asInstanceOf[KeyType] } } case class IHaveValidatedThisSQL(who: String, date: String) trait SelectableField { def dbSelectString: String } class MapperException(msg: String) extends Exception(msg)

Other Lift Framework examples (source code examples)

Here is a short list of links related to this Lift Framework MetaMapper.scala source code file:

Lift Framework example source code file (MetaMapper.scala)

This example Lift Framework source code file (MetaMapper.scala) is included in the DevDaily.com "Java Source Code Warehouse" project. The intent of this project is to help you "Learn Java by Example" TM.

Java - Lift Framework tags/keywords

a, a, box, empty, empty, full, jdbc, list, list, mappedfield, mapper, queryparam, reflection, sql, string, string, t, util

The Lift Framework MetaMapper.scala source code

/*
 * Copyright 2006-2011 WorldWide Conferencing, LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package net.liftweb
package mapper

import java.lang.reflect.Method
import java.sql.{ResultSet, Types, PreparedStatement}
import java.util.{Date, Locale}

import collection.mutable.{ListBuffer, HashMap}
import collection.immutable.{SortedMap, TreeMap}
import xml._

import common._
import json._
import util.Helpers._
import util.{NamedPF, FieldError, Helpers}
import http.{LiftRules, S, SHtml, RequestMemoize, Factory}
import http.js._

trait BaseMetaMapper {
  type RealType <: Mapper[RealType]

  def beforeSchemifier: Unit
  def afterSchemifier: Unit

  def dbTableName: String
  def _dbTableNameLC: String
  def mappedFields: scala.collection.Seq[BaseMappedField];
  def dbAddTable: Box[() => Unit]

  def dbIndexes: List[BaseIndex[RealType]]
}

/**
 * Rules and functions shared by all Mappers
 */
object MapperRules extends Factory {
  /**
   * This function converts a header name into the appropriate
   * XHTML format for displaying across the headers of a
   * formatted block.  The default is <th> for use
   * in XHTML tables.  If you change this function, the change
   * will be used for all MetaMappers, unless they've been
   * explicitly changed.
   */
  var displayNameToHeaderElement: String => NodeSeq = in => <th>{in}

  /**
   * This function converts an element into the appropriate
   * XHTML format for displaying across a line
   * formatted block.  The default is <td> for use
   * in XHTML tables.  If you change this function, the change
   * will be used for all MetaMappers, unless they've been
   * explicitly changed.
   */
  var displayFieldAsLineElement: NodeSeq => NodeSeq = in => <td>{in}
... this post is sponsored by my books ...

#1 New Release!

FP Best Seller

 

new blog posts

 

Copyright 1998-2021 Alvin Alexander, alvinalexander.com
All Rights Reserved.

A percentage of advertising revenue from
pages under the /java/jwarehouse URI on this website is
paid back to open source projects.