alvinalexander.com | career | drupal | java | mac | mysql | perl | scala | uml | unix  

Lift Framework example source code file (MetaRecord.scala)

This example Lift Framework source code file (MetaRecord.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

baserecord, baserecord, box, box, field, full, list, list, map, method, nil, reflection, req, string, string

The Lift Framework MetaRecord.scala source code

/*
 * 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 java.lang.reflect.Modifier
import net.liftweb._
import util._
import common._
import scala.collection.mutable.{ListBuffer}
import scala.xml._
import net.liftweb.http.js.{JsExp, JE, JsObj}
import net.liftweb.http.{SHtml, Req, LiftResponse, LiftRules}
import net.liftweb.json.{JsonParser, Printer}
import net.liftweb.json.JsonAST._
import net.liftweb.record.FieldHelpers.expectedA
import java.lang.reflect.Method
import field._
import Box._
import JE._
import Helpers._

/**
 * Holds meta information and operations on a record
 */
trait MetaRecord[BaseRecord <: Record[BaseRecord]] {
  self: BaseRecord =>

  private var fieldList: List[FieldHolder] = Nil
  private var fieldMap: Map[String, FieldHolder] = Map.empty

  private var lifecycleCallbacks: List[(String, Method)] = Nil

  /**
   * Set this to use your own form template when rendering a Record to a form.
   *
   * This template is any given XHtml that contains three nodes acting as placeholders such as:
   *
   * <pre>
   *
   * <lift:field_label name="firstName"/> - the label for firstName field will be rendered here
   * <lift:field name="firstName"/> - the firstName field will be rendered here (typically an input field)
   * <lift:field_msg name="firstName"/> - the <lift:msg> will be rendered here hafing the id given by
   *                                             uniqueFieldId of the firstName field.
   *
   *
   * Example.
   *
   * Having:
   *
   * class MyRecord extends Record[MyRecord] {
   *
   * 	def meta = MyRecordMeta
   *
   * 	object firstName extends StringField(this, "John")
   *
   * }
   *
   * object MyRecordMeta extends MyRecord with MetaRecord[MyRecord] {
   *  override def mutable_? = false
   * }
   *
   * ...
   *
   * val rec = MyRecordMeta.createRecord.firstName("McLoud")
   *
   * val template =
   * <div>
   * 	<div>
   * 		<div><lift:field_label name="firstName"/></div>
   * 		<div><lift:field name="firstName"/></div>
   * 		<div><lift:field_msg name="firstName"/></div>
   * 	</div>
   * </div>
   *
   * MyRecordMeta.formTemplate = Full(template)
   * rec.toForm((r:MyRecord) => println(r));
   *
   * </pre>
   *
   */
  var formTemplate: Box[NodeSeq] = Empty

  protected val rootClass = this.getClass.getSuperclass

  private def isLifecycle(m: Method) = classOf[LifecycleCallbacks].isAssignableFrom(m.getReturnType)

  private def isField(m: Method) = {
    val ret = !m.isSynthetic && classOf[Field[_, _]].isAssignableFrom(m.getReturnType)
    ret
  }

  def introspect(rec: BaseRecord, methods: Array[Method])(f: (Method, Field[_, BaseRecord]) => Any): Unit = {

    // find all the potential fields
    val potentialFields = methods.toList.filter(isField)

    // any fields with duplicate names get put into a List
    val map: Map[String, List[Method]] = potentialFields.foldLeft[Map[String, List[Method]]](Map()){
      case (map, method) => val name = method.getName
      map + (name -> (method :: map.getOrElse(name, Nil)))
    }

    // sort each list based on having the most specific type and use that method
    val realMeth = map.values.map(_.sortWith {
      case (a, b) => !a.getReturnType.isAssignableFrom(b.getReturnType)
    }).map(_.head)

    for (v <- realMeth) {
      v.invoke(rec) match {
        case mf: Field[_, BaseRecord] if !mf.ignoreField_? =>
          mf.setName_!(v.getName)
          f(v, mf)
        case _ =>
      }
    }

  }

  this.runSafe {
    val tArray = new ListBuffer[FieldHolder]

    val methods = rootClass.getMethods

    lifecycleCallbacks = (for (v <- methods
                              if v.getName != "meta" && isLifecycle(v)) yield (v.getName, v)).toList

    introspect(this, methods) {
      case (v, mf) => tArray += FieldHolder(mf.name, v, mf)
    }

    fieldList = {
      val ordered = fieldOrder.flatMap(f => tArray.find(_.metaField == f))
      ordered ++ (tArray -- ordered)
    }

    fieldMap = Map() ++ fieldList.map(i => (i.name, i))
  }

  /**
   * Specifies if this Record is mutable or not
   */
  def mutable_? = true

  /**
   * Creates a new record
   */
  def createRecord: BaseRecord = {
    val rec = instantiateRecord
    rec runSafe {
      fieldList.foreach(fh => fh.field(rec).setName_!(fh.name))
    }
    rec
  }

  /** Make a new record instance. This method can be overridden to provide caching behavior or what have you. */
  protected def instantiateRecord: BaseRecord = rootClass.newInstance.asInstanceOf[BaseRecord]

  /**
   * Creates a new record setting the value of the fields from the original object but
   * apply the new value for the specific field
   *
   * @param - original the initial record
   * @param - field the new mutated field
   * @param - the new value of the field
   */
  def createWithMutableField[FieldType](original: BaseRecord,
                                        field: Field[FieldType, BaseRecord],
                                        newValue: Box[FieldType]): BaseRecord = {
    val rec = createRecord

    for (fh <- fieldList) {
      val recField = fh.field(rec)
      if (fh.name == field.name)
        recField.asInstanceOf[Field[FieldType, BaseRecord]].setBox(newValue)
      else
        recField.setFromAny(fh.field(original).valueBox)
    }

    rec
  }

  /**
   * Returns the HTML representation of inst Record.
   *
   * @param inst - th designated Record
   * @return a NodeSeq
   */
  def toXHtml(inst: BaseRecord): NodeSeq = fieldList.flatMap(_.field(inst).toXHtml ++ Text("\n"))


  /**
   * Validates the inst Record by calling validators for each field
   *
   * @pram inst - the Record tobe validated
   * @return a List of FieldError. If this list is empty you can assume that record was validated successfully
   */
  def validate(inst: BaseRecord): List[FieldError] = {
    foreachCallback(inst, _.beforeValidation)
    try{
	    fieldList.flatMap(_.field(inst).validate)
    } finally {
      foreachCallback(inst, _.afterValidation)
    }
  }

  /**
   * Returns the JSON representation of <i>inst record
   *
   * @param inst: BaseRecord
   * @return JsObj
   */
  def asJSON(inst: BaseRecord): JsObj = {
    val tups = fieldList.map{ fh =>
      val field = fh.field(inst)
      field.name -> field.asJs
    }
    JsObj(tups:_*)
  }

  /**
   * Retuns the JSON representation of <i>inst record, converts asJValue to JsObj
   *
   * @return a JsObj
   */
  def asJsExp(inst: BaseRecord): JsExp = new JsExp {
    lazy val toJsCmd = Printer.compact(render(asJValue(inst)))
  }

  /**
   * Create a record with fields populated with values from the JSON construct
   *
   * @param json - The stringified JSON object
   * @return Box[BaseRecord]
   */
  def fromJSON(json: String): Box[BaseRecord] = {
    val inst = createRecord
    setFieldsFromJSON(inst, json) map (_ => inst)
  }

  /**
   * Populate the fields of the record instance with values from the JSON construct
   *
   * @param inst - The record to populate
   * @param json - The stringified JSON object
   * @return - Full(()) on success, other on failure
   */
  def setFieldsFromJSON(inst: BaseRecord, json: String): Box[Unit] =
    JSONParser.parse(json) match {
      case Full(nvp : Map[_, _]) =>
        for ((k, v) <- nvp;
             field <- inst.fieldByName(k.toString)) yield field.setFromAny(v)
        Full(inst)
      case Full(_) => Empty
      case failure => failure.asA[Unit]
    }

  /** Encode a record instance into a JValue */
  def asJValue(rec: BaseRecord): JObject = {
    JObject(fields(rec).map(f => JField(f.name, f.asJValue)))
  }

  /** Create a record by decoding a JValue which must be a JObject */
  def fromJValue(jvalue: JValue): Box[BaseRecord] = {
    val inst = createRecord
    setFieldsFromJValue(inst, jvalue) map (_ => inst)
  }

  /** Attempt to decode a JValue, which must be a JObject, into a record instance */
  def setFieldsFromJValue(rec: BaseRecord, jvalue: JValue): Box[Unit] = {
    def fromJFields(jfields: List[JField]): Box[Unit] = {
      for {
        jfield <- jfields
        field <- rec.fieldByName(jfield.name)
      } field.setFromJValue(jfield.value)

      Full(())
    }

    jvalue match {
      case JObject(jfields) => fromJFields(jfields)
      case other => expectedA("JObject", other)
    }
  }

  /**
   * Create a record with fields populated with values from the JSON construct
   *
   * @param json - The stringified JSON object
   * @return Box[BaseRecord]
   */
  def fromJsonString(json: String): Box[BaseRecord] = {
    val inst = createRecord
    setFieldsFromJsonString(inst, json) map (_ => inst)
  }

  /**
   * Set from a Json String using the lift-json parser
   */
  def setFieldsFromJsonString(inst: BaseRecord, json: String): Box[Unit] =
    setFieldsFromJValue(inst, JsonParser.parse(json))

  def foreachCallback(inst: BaseRecord, f: LifecycleCallbacks => Any) {
    lifecycleCallbacks.foreach(m => f(m._2.invoke(inst).asInstanceOf[LifecycleCallbacks]))
  }

  /**
   * Returns the XHTML representation of inst Record. If formTemplate is set,
   * this template will be used otherwise a default template is considered.
   *
   * @param inst - the record to be rendered
   * @return the XHTML content as a NodeSeq
   */
  def toForm(inst: BaseRecord): NodeSeq = {
    formTemplate match {
      case Full(template) => toForm(inst, template)
      case _ => fieldList.flatMap(_.field(inst).toForm.openOr(NodeSeq.Empty) ++ Text("\n"))
    }
  }

  /**
   * Returns the XHTML representation of inst Record. You must provide the Node template
   * to represent this record in the proprietary layout.
   *
   * @param inst - the record to be rendered
   * @param template - The markup template forthe form. See also the formTemplate variable
   * @return the XHTML content as a NodeSeq
   */
  def toForm(inst: BaseRecord, template: NodeSeq): NodeSeq = {
    template match {
      case e @ <lift:field_label>{_*} => e.attribute("name") match {
          case Some(name) => fieldByName(name.toString, inst).map(_.label).openOr(NodeSeq.Empty)
          case _ => NodeSeq.Empty
        }

      case e @ <lift:field>{_*} => e.attribute("name") match {
          case Some(name) => fieldByName(name.toString, inst).flatMap(_.toForm).openOr(NodeSeq.Empty)
          case _ => NodeSeq.Empty
        }

      case e @ <lift:field_msg>{_*} => e.attribute("name") match {
          case Some(name) => fieldByName(name.toString, inst).map(_.uniqueFieldId match {
                case Full(id) => <lift:msg id={id}/>
                case _ => NodeSeq.Empty
              }).openOr(NodeSeq.Empty)
          case _ => NodeSeq.Empty
        }

      case Elem(namespace, label, attrs, scp, ns @ _*) =>
        Elem(namespace, label, attrs, scp, toForm(inst, ns.flatMap(n => toForm(inst, n))):_* )

      case s : Seq[_] => s.flatMap(e => e match {
            case Elem(namespace, label, attrs, scp, ns @ _*) =>
              Elem(namespace, label, attrs, scp, toForm(inst, ns.flatMap(n => toForm(inst, n))):_* )
            case x => x
          })

    }
  }

  /**
   * 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(fieldName: String, inst: BaseRecord): Box[Field[_, BaseRecord]] = {
    Box(fieldMap.get(fieldName).map(_.field(inst)))
  }

  /**
   * Prepend a DispatchPF function to LiftRules.dispatch. If the partial function id defined for a give Req
   * it will construct a new Record based on the HTTP query string parameters
   * and will pass this Record to the function returned by func parameter.
   *
   * @param func - a PartialFunction for associating a request with a user provided function and the proper Record
   */
  def prependDispatch(func: PartialFunction[Req, BaseRecord => Box[LiftResponse]])= {
    LiftRules.dispatch.prepend (makeFunc(func))
  }

  /**
   * Append a DispatchPF function to LiftRules.dispatch. If the partial function id defined for a give Req
   * it will construct a new Record based on the HTTP query string parameters
   * and will pass this Record to the function returned by func parameter.
   *
   * @param func - a PartialFunction for associating a request with a user provided function and the proper Record
   */
  def appendDispatch(func: PartialFunction[Req, BaseRecord => Box[LiftResponse]])= {
    LiftRules.dispatch.append (makeFunc(func))
  }


  private def makeFunc(func: PartialFunction[Req, BaseRecord => Box[LiftResponse]]) = new PartialFunction[Req, () => Box[LiftResponse]] {

      def isDefinedAt(r: Req): Boolean = func.isDefinedAt(r)

      def apply(r: Req): () => Box[LiftResponse] = {
        val rec = fromReq(r)
        () => func(r)(rec)
      }
    }

  /**
   * Create a record with fields populated with values from the request
   *
   * @param req - The Req to read from
   * @return the created record
   */
  def fromReq(r: Req): BaseRecord = {
    val inst = createRecord
    setFieldsFromReq(inst, r)
    inst
  }

  /**
   * Populate the fields of the record with values from the request
   *
   * @param inst - The record to populate
   * @param req - The Req to read from
   */
  def setFieldsFromReq(inst: BaseRecord, req: Req) {
    for(fh <- fieldList){
      fh.field(inst).setFromAny(req.param(fh.name))
    }
  }

  /**
   * Defined the order of the fields in this record
   *
   * @return a List of Field
   */
  def fieldOrder: List[Field[_, BaseRecord]] = Nil

  /**
   * Renamed from fields() due to a clash with fields() in Record. Use this method
   * to obtain a list of fields defined in the meta companion objects. Possibly a
   * breaking change? (added 14th August 2009, Tim Perrett)
   *
   * @see Record
   */
  def metaFields() : List[Field[_, BaseRecord]] = fieldList.map(_.metaField)

  /**
   * Obtain the fields for a particlar Record or subclass instance by passing
   * the instance itself.
   * (added 14th August 2009, Tim Perrett)
   */
  def fields(rec: BaseRecord) : List[Field[_, BaseRecord]] = fieldList.map(_.field(rec))

  case class FieldHolder(name: String, method: Method, metaField: Field[_, BaseRecord]) {
    def field(inst: BaseRecord): Field[_, BaseRecord] = method.invoke(inst).asInstanceOf[Field[_, BaseRecord]]
  }
}

Other Lift Framework examples (source code examples)

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

... 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.