All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.liftweb.record.Field.scala Maven / Gradle / Ivy

There is a newer version: 2.4
Show newest version
/*
 * Copyright 2007-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 record

import net.liftweb.common._
import net.liftweb.http.js.{JsExp}
import net.liftweb.json.JsonAST.{JNothing, JNull, JString, JValue}
import net.liftweb.util._
import scala.reflect.Manifest
import scala.xml._

/** Base trait of record fields, with functionality common to any type of field owned by any type of record */
trait BaseField extends FieldIdentifier with util.BaseField {
  private[record] var fieldName: String = _
  private[record] var dirty = false

  protected def dirty_?(b: Boolean) = dirty = b

  def resetDirty {
    if (safe_?) dirty_?(false)
  }

  def dirty_? : Boolean = dirty

  /**
   * Should the field be ignored by the OR Mapper?
   */
  def ignoreField_? = false

  /**
   * Is the value of this field optional (e.g. NULLable)?
   */
  def optional_? = false

  /**
   * The text name of this field
   */
  def name: String = fieldName

  /**
   * Can the value of this field be read without obscuring the result?
   */
  def canRead_? : Boolean = safe_? || checkCanRead_?

  /**
   * If the owner is not in "safe" mode, check the current environment to see if
   * the field can be read
   */
  def checkCanRead_? = true

  /**
   * Can the value of this field be written?
   */
  def canWrite_? : Boolean = safe_? || checkCanWrite_?

  /**
   * If the owner is not in "safe" mode, check the current environment to see if
   * the field can be written
   */
  def checkCanWrite_? = true

  /**
   * Convert the field value to an XHTML representation
   */
  def toXHtml: NodeSeq = Text(toString)

  /**
   * Generate a form control for the field
   */
  def toForm: Box[NodeSeq]

  /**
   * Returns the field's value as a valid JavaScript expression
   */
  def asJs: JsExp

  /** Encode the field value into a JValue */
  def asJValue: JValue

  /**
   * Set the name of this field
   */
  private[record] final def setName_!(newName : String) : String = {
    if (safe_?) fieldName = newName
    fieldName
  }

  /**
   * The error message used when the field value could not be set
   */
  def noValueErrorMessage : String = "Value cannot be changed"

  /**
   * The error message used when the field value must be set
   */
  def notOptionalErrorMessage : String = "Value required"


  def tabIndex: Int = 1

  override def uniqueFieldId: Box[String] = Full(name+"_id")

  def label: NodeSeq = uniqueFieldId match {
    case Full(id) =>  
    case _ => NodeSeq.Empty
  }

  def asString: String

  def safe_? : Boolean = true // let owned fields make it unsafe some times
}

/** Refined trait for fields owned by a particular record type */
trait OwnedField[OwnerType <: Record[OwnerType]] extends BaseField {
  /**
   * Return the owner of this field
   */
  def owner: OwnerType

  /**
   * Are we in "safe" mode (i.e., the value of the field can be read or written without any security checks.)
   */
  override final def safe_? : Boolean = owner.safe_?
}

/** Refined trait for fields holding a particular value type */
trait TypedField[ThisType] extends BaseField {
  type MyType = ThisType // For backwards compatability

  type ValidationFunction = ValueType => List[FieldError]

  private[record] var data: Box[MyType] = Empty
  private[record] var needsDefault: Boolean = true
  
  /**
   * Helper for implementing asJValue for a conversion to an encoded JString
   *
   * @param encode function to transform the field value into a String
   */
  protected def asJString(encode: MyType => String): JValue =
    valueBox.map(v => JString(encode(v))) openOr (JNothing: JValue)

  /** Decode the JValue and set the field to the decoded value. Returns Empty or Failure if the value could not be set */
  def setFromJValue(jvalue: JValue): Box[MyType]

  /**
   * Helper for implementing setFromJValue for a conversion from an encoded JString
   *
   * @param decode function to try and transform a String into a field value
   */
  protected def setFromJString(jvalue: JValue)(decode: String => Box[MyType]): Box[MyType] = jvalue match {
    case JNothing|JNull if optional_? => setBox(Empty)
    case JString(s)                   => setBox(decode(s))
    case other                        => setBox(FieldHelpers.expectedA("JString", other))
  }

  def validations: List[ValidationFunction] = Nil

  /** Validate this field's setting, returning any errors found */
  def validate: List[FieldError] = runValidation(valueBox)

  /** Helper function that does validation of a value by using the validators specified for the field */
  protected def runValidation(in: Box[MyType]): List[FieldError] = in match {
    case Full(_) => validations.flatMap(_(toValueType(in))).distinct
    case Empty => Nil
    case Failure(msg, _, _) => Text(msg)
  }

  protected implicit def boxNodeToFieldError(in: Box[Node]): List[FieldError] =
    in match {
      case Full(node) => List(FieldError(this, node))
      case _ => Nil
    }

  protected implicit def nodeToFieldError(node: Node): List[FieldError] =
    List(FieldError(this, node))

  protected implicit def boxNodeFuncToFieldError(in: Box[MyType] => Box[Node]):
  Box[MyType] => List[FieldError] =
    param => boxNodeToFieldError(in(param))

  /** The default value of the field when no value is set. Must return a Full Box unless optional_? is true */
  def defaultValueBox: Box[MyType]

  /**
   * Convert the field to a String... usually of the form "displayName=value"
   */
  def asString = displayName + "=" + data

  def obscure(in: MyType): Box[MyType] = Failure("value obscured")

  def setBox(in: Box[MyType]): Box[MyType] = synchronized {
    needsDefault = false
    data = in match {
      case _ if !checkCanWrite_? => Failure(noValueErrorMessage)
      case Full(_)               => set_!(in)
      case _ if optional_?       => set_!(in)
      case (f: Failure)          => set_!(f) // preserve failures set in
      case _                     => Failure(notOptionalErrorMessage)
    }
    dirty_?(true)
    data
  }

  // Helper methods for things to easily use mixins and so on that use ValueType instead of Box[MyType], regardless of the optional-ness of the field
  protected def toValueType(in: Box[MyType]): ValueType
  protected def toBoxMyType(in: ValueType): Box[MyType]

  protected def set_!(in: Box[MyType]): Box[MyType] = runFilters(in, setFilterBox)

  def setFilter: List[ValueType => ValueType] = Nil

  /** OptionalTypedField and MandatoryTypedField implement this to do the appropriate lifting of Box[MyType] to ValueType */
  protected def liftSetFilterToBox(in: Box[MyType]): Box[MyType]

  /**
   * 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 setFilterBox: List[Box[MyType] => Box[MyType]] = liftSetFilterToBox _ :: Nil

  def runFilters(in: Box[MyType], filter: List[Box[MyType] => Box[MyType]]): Box[MyType] = filter match {
    case Nil => in
    case x :: xs => runFilters(x(in), xs)
  }

  /**
   * Set the value of the field from anything.
   * Implementations of this method should accept at least the following (pattern => valueBox)
   *   - value: MyType       => setBox(Full(value))
   *   - Some(value: MyType) => setBox(Full(value))
   *   - Full(value: MyType) => setBox(Full(value))
   *   - (value: MyType)::_  => setBox(Full(value))
   *   - s: String           => setFromString(s)
   *   - Some(s: String)     => setFromString(s)
   *   - Full(s: String)     => setFromString(s)
   *   - null|None|Empty     => setBox(defaultValueBox)
   *   - f: Failure          => setBox(f)
   * And usually convert the input to a string and uses setFromString as a last resort.
   * 
   * Note that setFromAny should _always_ call setBox, even if the conversion fails. This is so that validation
   * properly notes the error.
   *
   * The method genericSetFromAny implements this guideline.
   */
  def setFromAny(in: Any): Box[MyType]

  /** Generic implementation of setFromAny that implements exactly what the doc for setFromAny specifies, using a Manifest to check types */
  protected final def genericSetFromAny(in: Any)(implicit m: Manifest[MyType]): Box[MyType] = in match {
    case value       if m.erasure.isInstance(value) => setBox(Full(value.asInstanceOf[MyType]))
    case Some(value) if m.erasure.isInstance(value) => setBox(Full(value.asInstanceOf[MyType]))
    case Full(value) if m.erasure.isInstance(value) => setBox(Full(value.asInstanceOf[MyType]))
    case (value)::_  if m.erasure.isInstance(value) => setBox(Full(value.asInstanceOf[MyType]))
    case     (value: String) => setFromString(value)
    case Some(value: String) => setFromString(value)
    case Full(value: String) => setFromString(value)
    case (value: String)::_  => setFromString(value)
    case null|None|Empty     => setBox(defaultValueBox)
    case (failure: Failure)  => setBox(failure)
    case Some(other)         => setFromString(String.valueOf(other)) 
    case Full(other)         => setFromString(String.valueOf(other))
    case other               => setFromString(String.valueOf(other))
  }
  
  /**
   * Set the value of the field using some kind of type-specific conversion from a String.
   * By convention, if the field is optional_?, then the empty string should be treated as no-value (Empty).
   * Note that setFromString should _always_ call setBox, even if the conversion fails. This is so that validation
   * properly notes the error.
   * 
   * @return Full(convertedValue) if the conversion succeeds (the field value will be set by side-effect)
   *         Empty or Failure if the conversion does not succeed
   */
  def setFromString(s: String): Box[MyType]

  def valueBox: Box[MyType] = synchronized {
    if (needsDefault) {
      needsDefault = false
      data = defaultValueBox
    }

    if (canRead_?) data
    else data.flatMap(obscure)
  }

  /** Clear the value of this field */
  def clear: Unit = optional_? match {
    case true  => setBox(Empty)
    case false => setBox(defaultValueBox)
  }
}

trait MandatoryTypedField[ThisType] extends TypedField[ThisType] with Product1[ThisType] {
  type ValueType = ThisType // For util.BaseField

  //TODO: fullfil the contract of Product1[ThisType]
  def canEqual(a:Any) = false
  
  def _1 = value

  override def optional_? = false

  /**
   * Set the value of the field to the given value.
   * Note: Because setting a field can fail (return non-Full), this method will
   * return defaultValue if the field could not be set.
   */
  def set(in: MyType): MyType = setBox(Full(in)) openOr defaultValue

  def toValueType(in: Box[MyType]) = in openOr defaultValue
  def toBoxMyType(in: ValueType) = Full(in)

  def value: MyType = valueBox openOr defaultValue

  def get: MyType = value
  def is: MyType = value

  protected def liftSetFilterToBox(in: Box[MyType]): Box[MyType] = in.map(v => setFilter.foldLeft(v)((prev, f) => f(prev)))

  /**
   * The default value of the field when a field has no value set and is optional, or a method that must return a value (e.g. value) is used
   */
  def defaultValue: MyType

  def defaultValueBox: Box[MyType] = if (optional_?) Empty else Full(defaultValue)

  override def toString = valueBox match {
    case Full(null)|null => "null"
    case Full(v) => v.toString
    case _ => defaultValueBox.map(v => if (v != null) v.toString else "null") openOr ""
  }
}
  
trait OptionalTypedField[ThisType] extends TypedField[ThisType] with Product1[Box[ThisType]] {
  type ValueType = Option[ThisType] // For util.BaseField

  //TODO: fullfil the contract of Product1[ThisType]
  def canEqual(a:Any) = false
  
  def _1 = value

  final override def optional_? = true

  /**
   * Set the value of the field to the given value.
   * Note: Because setting a field can fail (return non-Full), this method will
   * return defaultValueBox if the field could not be set.
   */
  def set(in: Option[MyType]): Option[MyType] = setBox(in) or defaultValueBox

  def toValueType(in: Box[MyType]) = in
  def toBoxMyType(in: ValueType) = in

  def value: Option[MyType] = valueBox

  def get: Option[MyType] = value
  def is: Option[MyType] = value

  protected def liftSetFilterToBox(in: Box[MyType]): Box[MyType] = setFilter.foldLeft(in)((prev, f) => f(prev))


  def defaultValueBox: Box[MyType] = Empty

  override def toString = valueBox match {
    case Full(null)|null => "null"
    case Full(v) => v.toString
    case _ => defaultValueBox.map(v => if (v != null) v.toString else "null") openOr ""
  }

}

/**
 * A simple field that can store and retreive a value of a given type
 */
trait Field[ThisType, OwnerType <: Record[OwnerType]] extends OwnedField[OwnerType] with TypedField[ThisType] {

  def apply(in: MyType): OwnerType = apply(Full(in))

  def apply(in: Box[MyType]): OwnerType = if (owner.meta.mutable_?) {
    this.setBox(in)
    owner
  } else {
    owner.meta.createWithMutableField(owner, this, in)
  }
}

/**
 * Mix in to a field to change its form display to be formatted with the label aside.
 *
 * E.g.
 *   
*
* { control } *
*/ trait DisplayWithLabel[OwnerType <: Record[OwnerType]] extends OwnedField[OwnerType] { override abstract def toForm: Box[NodeSeq] = for (id <- uniqueFieldId; control <- super.toForm) yield
{ control }
} import java.sql.{ResultSet, Types} import net.liftweb.mapper.{DriverType} /** * Desribes common aspects related with JDBC */ trait JDBCFieldFlavor[MyType] { def jdbcFriendly(field : String) : MyType def targetSQLType : Int /** * Given the driver type, return the string required to create the column in the database */ def fieldCreatorString(dbType: DriverType, colName: String): String } trait KeyField[MyType, OwnerType <: Record[OwnerType] with KeyedRecord[OwnerType, MyType]] extends Field[MyType, OwnerType] { def ===(other: KeyField[MyType, OwnerType]): Boolean = this.valueBox == other.valueBox } object FieldHelpers { def expectedA(what: String, notA: AnyRef): Failure = Failure("Expected a " + what + ", not a " + (if (notA == null) "null" else notA.getClass.getName)) }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy