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

net.liftweb.mapper.Mapper.scala Maven / Gradle / Ivy

The newest version!
/*
 * 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.util.Date

import scala.xml.{Elem, NodeSeq}
import http.S
import http.js._
import util._
import common.{Box, Empty, Full, ParamFailure}

import collection.mutable.StringBuilder

trait BaseMapper extends FieldContainer {
  type MapperType <: Mapper[MapperType]

  def dbName: String
  def save: Boolean
}

trait Mapper[A<:Mapper[A]] extends BaseMapper with Serializable with SourceInfo {
  self: A =>
  type MapperType = A

  private var was_deleted_? = false
  private var dbConnectionIdentifier: Box[ConnectionIdentifier] = Empty
  private[mapper] var addedPostCommit = false
  @volatile private[mapper] var persisted_? = false

  def getSingleton : MetaMapper[A];
  final def safe_? : Boolean = {
    util.Safe.safe_?(System.identityHashCode(this))
  }

  def dbName:String = getSingleton.dbName

  implicit def thisToMappee(in: Mapper[A]): A = this.asInstanceOf[A]

  def runSafe[T](f : => T) : T = {
    util.Safe.runSafe(System.identityHashCode(this))(f)
  }

  def connectionIdentifier(id: ConnectionIdentifier): A = {
    if (id != getSingleton.dbDefaultConnectionIdentifier || dbConnectionIdentifier.isDefined) dbConnectionIdentifier = Full(id)
    thisToMappee(this)
  }

  def connectionIdentifier = dbConnectionIdentifier openOr calcDbId

  def dbCalculateConnectionIdentifier: PartialFunction[A, ConnectionIdentifier] = Map.empty

  private def calcDbId = if (dbCalculateConnectionIdentifier.isDefinedAt(this)) dbCalculateConnectionIdentifier(this)
  else getSingleton.dbDefaultConnectionIdentifier

  /**
   * Append a function to perform after the commit happens
   * @param func - the function to perform after the commit happens
   */
  def doPostCommit(func: () => Unit): A = {
    DB.appendPostTransaction(connectionIdentifier, dontUse => func())
    this
  }

  /**
   * Save the instance and return the instance
   */
  def saveMe(): A = {
    this.save
    this
  }

  def save: Boolean = {
    runSafe {
      getSingleton.save(this)
    }
  }

  def htmlLine : NodeSeq = {
    getSingleton.doHtmlLine(this)
  }

  def asHtml : NodeSeq = {
    getSingleton.asHtml(this)
  }

  /**
   * If the instance calculates any additional
   * fields for JSON object, put the calculated fields
   * here
   */
  def suplementalJs(ob: Box[KeyObfuscator]): List[(String, JsExp)] = Nil

  def validate : List[FieldError] = {
    runSafe {
      getSingleton.validate(this)
    }
  }

  /**
   * Returns the instance in a Full Box if the instance is valid, otherwise
   * returns a Failure with the validation errors
   */
  def asValid: Box[A] = validate match {
    case Nil => Full(this)
    case xs => ParamFailure(xs.map(_.msg.text).mkString(", "), Empty, Empty, xs)
  }

  /**
   * Convert the model to a JavaScript object
   */
  def asJs: JsExp = getSingleton.asJs(this)


  /**
   * Given a name, look up the field
   * @param name the name of the field
   * @return the metadata
   */
  def findSourceField(name: String): Box[SourceFieldInfo] =
  for {
    mf <- getSingleton.fieldNamesAsMap.get(name.toLowerCase)
    f <- fieldByName[mf.ST](name)
  } yield SourceFieldInfoRep[mf.ST](f.get.asInstanceOf[mf.ST], mf).asInstanceOf[SourceFieldInfo]

  /**
   * Get a list of all the fields
   * @return a list of all the fields
   */
  def allFieldNames(): Seq[(String, SourceFieldMetadata)] = getSingleton.doAllFieldNames

  /**
   * Delete the model from the RDBMS
   */
  def delete_! : Boolean = {
    if (!db_can_delete_?) false else
    runSafe {
      was_deleted_? = getSingleton.delete_!(this)
      was_deleted_?
    }
  }

  /**
   * Get the fields (in order) for displaying a form
   */
  def formFields: List[MappedField[_, A]] =
  getSingleton.formFields(this)

  def allFields: Seq[BaseField] = formFields

  /**
   * map the fields titles and forms to generate a list
   * @param func called with displayHtml, fieldId, form
   */
  def mapFieldTitleForm[T](func: (NodeSeq, Box[NodeSeq], NodeSeq) => T): List[T] =
  getSingleton.mapFieldTitleForm(this, func)


  /**
   * flat map the fields titles and forms to generate a list
   * @param func called with displayHtml, fieldId, form
   */
  def flatMapFieldTitleForm[T]
  (func: (NodeSeq, Box[NodeSeq], NodeSeq) => Seq[T]): List[T] =
  getSingleton.flatMapFieldTitleForm(this, func)

    /**
   * flat map the fields titles and forms to generate a list
   * @param func called with displayHtml, fieldId, form
   */
  def flatMapFieldTitleForm2[T]
  (func: (NodeSeq, MappedField[_, A], NodeSeq) => Seq[T]): List[T] =
  getSingleton.flatMapFieldTitleForm2(this, func)

  /**
   * Present the model as a form and execute the function on submission of the form
   *
   * @param button - If it's Full, put a submit button on the form with the value of the parameter
   * @param onSuccess - redirect to the URL if the model validates, otherwise display the errors
   *
   * @return the form
   */
  def toForm(button: Box[String], onSuccess: String): NodeSeq =
  toForm(button, (what: A) => {what.validate match {
        case Nil => what.save ; S.redirectTo(onSuccess)
        case xs => S.error(xs)
      }})

  /**
   * Present the model as a HTML using the same formatting as toForm
   *
   * @return the html view of the model
   */
  def toHtml: NodeSeq = getSingleton.toHtml(this)

  /**
   * Present the model as a form and execute the function on submission of the form
   *
   * @param button - If it's Full, put a submit button on the form with the value of the parameter
   * @param f - the function to execute on form submission
   *
   * @return the form
   */
  def toForm(button: Box[String], f: A => Any): NodeSeq =
  getSingleton.toForm(this) ++
  S.fmapFunc((ignore: List[String]) => f(this)){
    (name: String) =>
    ()} ++
  (button.map(b => getSingleton.formatFormElement(   ,  )) openOr scala.xml.Text(""))

  def toForm(button: Box[String], redoSnippet: NodeSeq => NodeSeq, onSuccess: A => Unit): NodeSeq = {
    val snipName = S.currentSnippet
    def doSubmit(): Unit = {
      this.validate match {
        case Nil => onSuccess(this)
        case xs => S.error(xs)
          snipName.foreach(n => S.mapSnippet(n, redoSnippet))
      }
    }

    getSingleton.toForm(this) ++
    S.fmapFunc((ignore: List[String]) => doSubmit())(name => ) ++
    (button.map(b => getSingleton.formatFormElement(   ,  )) openOr scala.xml.Text(""))
  }

  def saved_? : Boolean = getSingleton.saved_?(this)

  /**
   * Can this model object be deleted?
   */
  def db_can_delete_? : Boolean =  getSingleton.saved_?(this) && !was_deleted_?

  def dirty_? : Boolean = getSingleton.dirty_?(this)

  override def toString: String =
    new StringBuilder(this.getClass.getName)
      .append("={")
      .append(getSingleton.appendFieldToStrings(this))
      .append("}")
      .toString

  def toXml: Elem = {
    getSingleton.toXml(this)
  }

  def checkNames(): Unit = {
    runSafe {
      getSingleton match {
        case null =>
        case s => s.checkFieldNames(this)
      }
    }
  }

  def comparePrimaryKeys(other: A) = false

  /**
   * Find the field by name
   * @param fieldName -- the name of the field to find
   *
   * @return Box[MappedField]
   */
  def fieldByName[T](fieldName: String): Box[MappedField[T, A]] = getSingleton.fieldByName[T](fieldName, this)

  type FieldPF = PartialFunction[String, NodeSeq => NodeSeq]

  /**
   * Given a function that takes a mapper field and returns a NodeSeq
   * for the field, return, for this mapper instance, a set of CSS
   * selector transforms that will transform a form for those fields
   * into a fully-bound form that will interact with this instance.
   */
  def fieldMapperTransforms(fieldTransform: (BaseOwnedMappedField[A] => NodeSeq)): scala.collection.Seq[CssSel] = {
    getSingleton.fieldMapperTransforms(fieldTransform, this)
  }
  
  private var fieldTransforms_i: scala.collection.Seq[CssSel] = Vector()

  /**
   * A list of CSS selector transforms that will help render the fields
   * of this mapper object.
   */
  def fieldTransforms = fieldTransforms_i

  def appendFieldTransform(transform: CssSel): Unit = {
    fieldTransforms_i = fieldTransforms_i :+ transform
  }

  def prependFieldTransform(transform: CssSel): Unit = {
    fieldTransforms_i = transform +: fieldTransforms_i
  }

  /**
   * If there's a field in this record that defines the locale, return it
   */
  def localeField: Box[MappedLocale[A]] = Empty

  def timeZoneField: Box[MappedTimeZone[A]] = Empty

  def countryField: Box[MappedCountry[A]] = Empty
}

trait LongKeyedMapper[OwnerType <: LongKeyedMapper[OwnerType]] extends KeyedMapper[Long, OwnerType] with BaseLongKeyedMapper {
  self: OwnerType =>
}

trait BaseKeyedMapper extends BaseMapper {
  type TheKeyType
  type KeyedMapperType <: KeyedMapper[TheKeyType, KeyedMapperType]

  def primaryKeyField: MappedField[TheKeyType, MapperType] with IndexedField[TheKeyType]
  /**
   * Delete the model from the RDBMS
   */
  def delete_! : Boolean
}

trait BaseLongKeyedMapper extends BaseKeyedMapper {
  override type TheKeyType = Long
}

trait IdPK /* extends BaseLongKeyedMapper */ {
  self: BaseLongKeyedMapper =>
  def primaryKeyField: MappedLongIndex[MapperType] = id
  object id extends MappedLongIndex[MapperType](this.asInstanceOf[MapperType])
}

/**
 * A trait you can mix into a Mapper class that gives you
 * a createdat column
 */
trait CreatedTrait {
  self: BaseMapper =>

  import net.liftweb.util._

  /**
   * Override this method to index the createdAt field
   */
  protected def createdAtIndexed_? = false

  /**
   * The createdAt field.  You can change the behavior of this
   * field:
   * 
   * override lazy val createdAt = new MyCreatedAt(this) {
   *   override def dbColumnName = "i_eat_time"
   * }
   * 
*/ lazy val createdAt: MappedDateTime[MapperType] = new MyCreatedAt(this) protected class MyCreatedAt(obj: self.type) extends MappedDateTime[MapperType](obj.asInstanceOf[MapperType]) { override def defaultValue = Helpers.now override def dbIndexed_? = createdAtIndexed_? } } /** * A trait you can mix into a Mapper class that gives you * an updatedat column */ trait UpdatedTrait { self: BaseMapper => import net.liftweb.util._ /** * Override this method to index the updatedAt field */ protected def updatedAtIndexed_? = false /** * The updatedAt field. You can change the behavior of this * field: *
   * override lazy val updatedAt = new MyUpdatedAt(this) {
   *   override def dbColumnName = "i_eat_time_for_breakfast"
   * }
   * 
*/ lazy val updatedAt: MyUpdatedAt = new MyUpdatedAt(this) protected class MyUpdatedAt(obj: self.type) extends MappedDateTime(obj.asInstanceOf[MapperType]) with LifecycleCallbacks { override def beforeSave: Unit = {super.beforeSave; this.set(Helpers.now)} override def defaultValue: Date = Helpers.now override def dbIndexed_? : Boolean = updatedAtIndexed_? } } /** * Mix this trait into your Mapper instance to get createdAt and updatedAt fields. */ trait CreatedUpdated extends CreatedTrait with UpdatedTrait { self: BaseMapper => } trait KeyedMapper[KeyType, OwnerType<:KeyedMapper[KeyType, OwnerType]] extends Mapper[OwnerType] with BaseKeyedMapper { self: OwnerType => type TheKeyType = KeyType type KeyedMapperType = OwnerType def primaryKeyField: MappedField[KeyType, OwnerType] with IndexedField[KeyType] def getSingleton: KeyedMetaMapper[KeyType, OwnerType]; override def comparePrimaryKeys(other: OwnerType): Boolean = primaryKeyField.get == other.primaryKeyField.get def reload: OwnerType = getSingleton.find(By(primaryKeyField, primaryKeyField.get)) openOr this def asSafeJs(f: KeyObfuscator): JsExp = getSingleton.asSafeJs(this, f) override def hashCode(): Int = primaryKeyField.get.hashCode override def equals(other: Any): Boolean = { other match { case null => false case km: KeyedMapper[_, _] if this.getClass.isAssignableFrom(km.getClass) || km.getClass.isAssignableFrom(this.getClass) => this.primaryKeyField == km.primaryKeyField case k => super.equals(k) } } } /** * If this trait is mixed into a validation function, the validation for a field * will stop if this validation function returns an error */ trait StopValidationOnError[T] extends Function1[T, List[FieldError]] object StopValidationOnError { def apply[T](f: T => List[FieldError]): StopValidationOnError[T] = new StopValidationOnError[T] { def apply(in: T): List[FieldError] = f(in) } def apply[T](f: PartialFunction[T, List[FieldError]]): PartialFunction[T, List[FieldError]] with StopValidationOnError[T] = new PartialFunction[T, List[FieldError]] with StopValidationOnError[T] { def apply(in: T): List[FieldError] = f(in) def isDefinedAt(in: T): Boolean = f.isDefinedAt(in) } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy