package net.liftweb.mapper
/*
* Copyright 2006-2009 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.
*/
import _root_.scala.collection.mutable._
import _root_.java.lang.reflect.Method
import _root_.java.sql.{ResultSet, Types}
import _root_.scala.xml.{Text, Node, NodeSeq, Group,
Elem, Null, PrefixedAttribute, MetaData}
import _root_.java.util.Date
import _root_.net.liftweb.http.{S, SHtml, FieldError, FieldIdentifier}
import _root_.net.liftweb.http.S._
import _root_.net.liftweb.http.js._
import _root_.net.liftweb.util._
import Helpers._
/**
* The base (not Typed) trait that defines a field that is mapped to a column or more than 1 column
* (e.g., MappedPassword) in the database
*/
@serializable
trait BaseMappedField extends SelectableField with Bindable {
/**
* Get a JDBC friendly representation of the named field (this is used for MappedFields that correspond to more than
* 1 column in the database.)
* @param field -- the name of the field being mapped to
*/
def jdbcFriendly(field : String): AnyRef
/**
* Get a JDBC friendly object for the part of this field that maps to the first
* column in the database
*/
def jdbcFriendly: AnyRef
/**
* Get the JDBC SQL Type for this field
*/
def targetSQLType(field: String): Int
/**
* Get the JDBC SQL Type for this field
*/
def targetSQLType: Int
/**
* Validate this field and return a list of Validation Issues
*/
def validate: List[FieldError]
/**
* Given the driver type, return the string required to create the column in the database
*/
def fieldCreatorString(dbType: DriverType, colName: String): String
/**
* Given the driver type, return a list of statements to create the columns in the database
*/
def fieldCreatorString(dbType: DriverType): List[String]
/**
* The human name of this field
*/
def name: String
/**
* Convert the field to its name/value pair (e.g., name=David)
*/
def asString: String
/**
* The number of database columns that this field represents
*/
def dbColumnCount: Int
def dbColumnNames(in: String): List[String]
def dbColumnName: String
/**
* Should the field be indexed?
*/
def dbIndexed_? : Boolean
/**
* Is the field the table's primary key
*/
def dbPrimaryKey_? : Boolean
/**
* Is the field a foreign key reference
*/
def dbForeignKey_? : Boolean
/**
* Called when a column has been added to the database via Schemifier
*/
def dbAddedColumn: Box[() => Unit]
/**
* Called when a column has indexed via Schemifier
*/
def dbAddedIndex: Box[() => Unit]
/**
* Create an input field for the item
*/
def toForm: Box[NodeSeq]
/**
* A unique 'id' for the field for form generation
*/
def fieldId: Option[NodeSeq] = None
def displayNameHtml: Box[NodeSeq] = Empty
def displayHtml: NodeSeq = displayNameHtml openOr Text(displayName)
/**
* This is where the instance creates its "toForm" stuff.
* The actual toForm method wraps the information based on
* mode.
*/
def _toForm: Box[NodeSeq]
def asHtml: NodeSeq
/**
* Called after the field is saved to the database
*/
protected[mapper] def doneWithSave()
/**
* The display name of this field (e.g., "First Name")
*/
def displayName: String
def asJsExp: JsExp
def asJs: List[(String, JsExp)] = List((name, asJsExp))
def renderJs_? = true
}
/**
* Mix this trait into a BaseMappedField and it will be indexed
*/
trait DBIndexed extends BaseMappedField {
override def dbIndexed_? = true
}
/**
* The Trait that defines a field that is mapped to a foreign key
*/
trait MappedForeignKey[KeyType, MyOwner <: Mapper[MyOwner], Other <: KeyedMapper[KeyType, Other]] extends MappedField[KeyType, MyOwner] {
type FieldType <: KeyType
type ForeignType <: KeyedMapper[KeyType, Other]
override def equals(other: Any) = other match {
case km: KeyedMapper[KeyType, Other] => this.is == km.primaryKeyField.is
case _ => super.equals(other)
}
def dbKeyToTable: KeyedMetaMapper[KeyType, Other]
def validSelectValues: Box[List[(KeyType, String)]] = Empty
def immutableMsg: NodeSeq = Text(?("Can't change"))
override def _toForm: Box[NodeSeq] = Full(validSelectValues.flatMap{
case Nil => Empty
case xs =>
val mapBack: HashMap[String, KeyType] = new HashMap
var selected: Box[String] = Empty
Full(SHtml.selectObj(xs, Full(this.is), this.set))
}.openOr(immutableMsg))
/**
* Is the key defined
*/
def defined_? : Boolean
/**
* Is the obj field cached
*/
def cached_? : Boolean = synchronized{ _calcedObj}
/**
* Load and cache the record that this field references
*/
def obj: Box[Other] = synchronized {
if (!_calcedObj) {
_calcedObj = true
this._obj = if(defined_?) dbKeyToTable.find(i_is_!) else Empty
}
_obj
}
/**
* Prime the reference of this FK reference
*/
def primeObj(obj: Box[Other]) = synchronized {
_obj
_calcedObj = true
}
private var _obj: Box[Other] = Empty
private var _calcedObj = false
}
trait BaseOwnedMappedField[OwnerType <: Mapper[OwnerType]] extends BaseMappedField
trait TypedField[FieldType] {
/**
* The default value for the field
*/
def defaultValue: FieldType
/**
* What is the real class that corresponds to FieldType
*/
def dbFieldClass: Class[FieldType]
}
/**
* The strongly typed field that's mapped to a column (or many columns) in the database.
* FieldType is the type of the field and OwnerType is the Owner of the field
*/
trait MappedField[FieldType <: Any,OwnerType <: Mapper[OwnerType]] extends TypedField[FieldType] with BaseOwnedMappedField[OwnerType] with FieldIdentifier {
/**
* Should the field be ignored by the OR Mapper?
*/
def ignoreField_? = false
/**
* Get the field that this prototypical field represents
*
* @param actual the object to find the field on
*/
def actualField(actual: OwnerType): MappedField[FieldType, OwnerType] = actual.getSingleton.getActualField(actual, this)
/**
* Given the driver type, return the string required to create the column in the database
*/
def fieldCreatorString(dbType: DriverType, colName: String): String
/**
* Given the driver type, return a list of SQL creation strings for the columns represented by this field
*/
def fieldCreatorString(dbType: DriverType): List[String] = dbColumnNames(name).map{c => fieldCreatorString(dbType, c)}
/**
* Is the field dirty
*/
private var _dirty_? = false
/**
* Is the field dirty (has it been changed since the record was loaded from the database
*/
def dirty_? = !dbPrimaryKey_? && _dirty_?
/**
* Make the field dirty
*/
protected def dirty_?(b: Boolean) = _dirty_? = b
/**
* Called when a column has been added to the database via Schemifier
*/
def dbAddedColumn: Box[() => Unit] = Empty
/**
* Called when a column has indexed via Schemifier
*/
def dbAddedIndex: Box[() => Unit] = Empty
/**
* override this method in indexed fields to indicate that the field has been saved
*/
def dbIndexFieldIndicatesSaved_? = false;
/**
* Return the owner of this field
*/
def fieldOwner: OwnerType
/**
* Are we in "safe" mode (i.e., the value of the field can be read or written without any security checks.)
*/
final def safe_? : Boolean = fieldOwner.safe_?
/**
* Given the current execution state, can the field be written?
*/
def writePermission_? = false
/**
* Given the current execution state, can the field be read?
*/
def readPermission_? = false
/**
* Assignment from the underlying type. It's ugly, but:<br />
* field() = new_value <br />
* field := new_value <br />
* field set new_value <br />
* field.set(new_value) <br />
* are all the same
*/
def update[Q <% FieldType](v: Q) {
this.set(v)
}
def apply[Q <% FieldType](v: Q): OwnerType = {
this.set(v)
fieldOwner
}
private var _name : String = null
/**
* The internal name of this field. Use name
*/
private[mapper] final def i_name_! = _name
/**
* The name of this field
*/
final def name = synchronized {
if (_name eq null) {
fieldOwner.checkNames
}
_name
}
/**
* Set the name of this field
*/
private[mapper] final def setName_!(newName : String) : String = {
if(safe_?) _name = newName.toLowerCase
_name
}
/**
* The display name of this field (e.g., "First Name")
*/
override def displayName: String = name
def resetDirty {
if (safe_?) dirty_?(false)
}
def dbDisplay_? = true
/**
* pascal-style assignment for syntactic sugar
*/
/*
def ::=(v : Any) : T
*/
/**
* Attempt to figure out what the incoming value is and set the field to that value. Return true if
* the value could be assigned
*/
def setFromAny(value: Any): FieldType
def toFormAppendedAttributes: MetaData =
if (Props.mode == Props.RunModes.Test)
new PrefixedAttribute("lift", "field_name", Text(calcFieldName), Null)
else Null
def calcFieldName: String = fieldOwner.getSingleton._dbTableName+":"+name
final def toForm: Box[NodeSeq] = {
def mf(in: Node): NodeSeq = in match {
case g: Group => g.nodes.flatMap(mf)
case e: Elem => e % toFormAppendedAttributes
case other => other
}
_toForm.map(_.flatMap(mf) )
}
/**
* Create an input field for the item
*/
override def _toForm: Box[NodeSeq] =
S.fmapFunc({s: List[String] => this.setFromAny(s)}){funcName =>
Full(<input type='text' id={fieldId}
name={funcName} lift:gc={funcName}
value={is match {case null => "" case s => s.toString}}/>)
}
/**
* Set the field to the value
*/
def set(value: FieldType): FieldType = {
if (safe_? || writePermission_?) i_set_!(value)
else throw new Exception("Do not have permissions to set this field")
}
/**
* Set the field to the Box value if the Box is Full
*/
def set_?(value: Box[FieldType]): Box[FieldType] = {
value.foreach(v => this.set(v))
value
}
/**
* A list of functions that transform the value before it is set. The transformations
* are also applied before the value is used in a query. Typical applications
* of this are trimming and/or toLowerCase-ing strings
*/
protected def setFilter: List[FieldType => FieldType] = Nil
protected final def i_set_!(value: FieldType): FieldType = {
real_i_set_!(runFilters(value, setFilter))
}
def runFilters(in: FieldType, filter: List[FieldType => FieldType]): FieldType =
filter match {
case Nil => in
case x :: xs => runFilters(x(in), xs)
}
/**
* Must be implemented to store the value of the field
*/
protected def real_i_set_!(value: FieldType): FieldType
def buildSetActualValue(accessor: Method, inst : AnyRef, columnName : String) : (OwnerType, AnyRef) => Unit
def buildSetLongValue(accessor: Method, columnName: String): (OwnerType, Long, Boolean) => Unit
def buildSetStringValue(accessor: Method, columnName: String): (OwnerType, String) => Unit
def buildSetDateValue(accessor: Method, columnName: String): (OwnerType, Date) => Unit
def buildSetBooleanValue(accessor: Method, columnName: String) : (OwnerType, Boolean, Boolean) => Unit
protected def getField(inst: OwnerType, meth: Method) = meth.invoke(inst).asInstanceOf[MappedField[FieldType,OwnerType]];
protected def doField(inst: OwnerType, meth: Method, func: PartialFunction[MappedField[FieldType, OwnerType], Unit]) {
val f = getField(inst, meth)
if (func.isDefinedAt(f)) func(f)
}
/**
* Convert the field to its "context free" type (e.g., String, Int, Long, etc.)
* If there are no read permissions, the value will be obscured
*/
def is: FieldType = {
if (safe_? || readPermission_?) i_is_!
else i_obscure_!(i_is_!)
}
/**
* What value was the field's value when it was pulled from the DB?
*/
def was: FieldType = {
if (safe_? || readPermission_?) i_was_!
else i_obscure_!(i_was_!)
}
/**
* The actual value of the field
*/
protected def i_is_! : FieldType
/**
* The value of the field when it was pulled from the DB
*/
protected def i_was_! : FieldType
/**
* Obscure the incoming value to a "safe" value (e.g., if there are
* not enough rights to view the entire social security number 123-45-5678, this
* method might return ***-**-*678
*/
protected def i_obscure_!(in : FieldType): FieldType
/**
* Return the field name and field value, delimited by an '='
*/
def asString = displayName + "=" + toString
def dbColumnCount = 1
def dbColumnNames(in : String) = if (dbColumnCount == 1) List(dbColumnName) else List(in.toLowerCase)
def dbColumnName = name.toLowerCase match {
case name if DB.reservedWords.contains(name) => name+"_c"
case name => name
}
lazy val dbSelectString = fieldOwner.getSingleton.
dbTableName + "." + dbColumnName
def dbIndexed_? : Boolean = false
def dbPrimaryKey_? : Boolean = false
/**
* Is the field a foreign key reference
*/
def dbForeignKey_? : Boolean = false
def jdbcFriendly(field : String) : Object
def jdbcFriendly: Object = jdbcFriendly(dbColumnName)
/**
* Get the JDBC SQL Type for this field
*/
def targetSQLType(field : String): Int = targetSQLType
/**
* Get the JDBC SQL Type for this field
*/
def targetSQLType: Int
override def toString : String =
is match {
case null => ""
case v => v.toString
}
def validations: List[FieldType => List[FieldError]] = Nil
def validate : List[FieldError] = {
val cv = is
validations.flatMap{
case pf: PartialFunction[FieldType, List[FieldError]] =>
if (pf.isDefinedAt(cv)) pf(cv)
else Nil
case f => f(cv)
}
}
final def convertToJDBCFriendly(value: FieldType): Object = real_convertToJDBCFriendly(runFilters(value, setFilter))
protected def real_convertToJDBCFriendly(value: FieldType): Object
/**
* Does the "right thing" comparing mapped fields
*/
override def equals(other: Any): Boolean = {
other match {
case mapped: MappedField[Any, Nothing] => this.is == mapped.is
case ov: AnyRef if (ov ne null) && dbFieldClass.isAssignableFrom(ov.getClass) => this.is == runFilters(ov.asInstanceOf[FieldType], setFilter)
case ov => this.is == ov
}
}
override def asHtml : Node = Text(toString)
}
object MappedField {
implicit def mapToType[T, A<:Mapper[A]](in : MappedField[T, A]): T = in.is
}
trait IndexedField[O] extends BaseIndexedField {
def convertKey(in: String): Box[O]
def convertKey(in: Int): Box[O]
def convertKey(in: Long): Box[O]
def convertKey(in: AnyRef): Box[O]
def makeKeyJDBCFriendly(in: O): AnyRef
def dbDisplay_? = false
}
trait BaseIndexedField extends BaseMappedField {
}
/**
* A trait that defines foreign key references
*/
trait BaseForeignKey extends BaseMappedField {
type KeyType
type KeyedForeignType <: KeyedMapper[KeyType, KeyedForeignType]
type OwnerType <: Mapper[OwnerType]
/**
* Is the key defined?
*/
def defined_? : Boolean
/**
* get the object referred to by this foreign key
*/
def dbKeyToTable: BaseMetaMapper
def dbKeyToColumn: BaseMappedField
def findFor(key: KeyType): List[OwnerType]
def findFor(key: KeyedForeignType): List[OwnerType]
/**
* Called when Schemifier adds a foreign key. Return a function that will be called when Schemifier
* is done with the schemification.
*/
def dbAddedForeignKey: Box[() => Unit]
}
trait LifecycleCallbacks {
def beforeValidation {}
def beforeValidationOnCreate {}
def beforeValidationOnUpdate {}
def afterValidation {}
def afterValidationOnCreate {}
def afterValidationOnUpdate {}
def beforeSave {}
def beforeCreate {}
def beforeUpdate {}
def afterSave {}
def afterCreate {}
def afterUpdate {}
def beforeDelete {}
def afterDelete {}
}