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

net.liftweb.mapper.OneToMany.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


private[mapper] object RecursiveType {
  val rec: { type R0 <: Mapper[R0] } = null
  type Rec = rec.R0
}
import net.liftweb.mapper.RecursiveType._

/**
 * Add this trait to a Mapper for managed one-to-many support
 * For example: class Contact extends LongKeyedMapper[Contact] with OneToMany[Long, Contact] { ... }
 * @tparam K the type of the primary key
 * @tparam T the mapper type
 * @author nafg
 */
trait OneToMany[K,T<:KeyedMapper[K, T]] extends KeyedMapper[K,T] { this: T =>

  private[mapper] lazy val oneToManyFields: List[MappedOneToManyBase[Rec]] = {
    new FieldFinder[MappedOneToManyBase[Rec]](
      getSingleton,
      net.liftweb.common.Logger(classOf[OneToMany[K,T]])
    ).accessorMethods map (_.invoke(this).asInstanceOf[MappedOneToManyBase[Rec]])
  }

  /**
   * An override for save to propagate the save to all children
   * of this parent.
   * Returns false as soon as the parent or a one-to-many field returns false.
   * If they are all successful returns true.
   */
  override def save: Boolean = {
    val ret = super.save &&
      oneToManyFields.forall(_.save)
    ret
  }

  /**
   * An override for delete_! to propagate the deletion
   * to all children of one-to-many fields implementing Cascade.
   * Returns false as soon as the parent or a one-to-many field returns false.
   * If they are all successful returns true.
   */
  override def delete_! : Boolean = DB.use(connectionIdentifier){ _ =>
    if(oneToManyFields.forall{(_: MappedOneToManyBase[_ <: Mapper[_]]) match {
      case f: Cascade[_] => f.delete_!
      case _ => true
    }
    })
      super.delete_!
    else {
      DB.rollback(connectionIdentifier)
      false
    }
  }


  /**
   * This implicit allows a MappedForeignKey to be used as foreignKey function.
   * Returns a function that takes a Mapper and looks up the actualField of field on the Mapper.
   */
  implicit def foreignKey[K, O<:Mapper[O], T<:KeyedMapper[K,T]](field: MappedForeignKey[K,O,T]): O=>MappedForeignKey[K,O,T] =
    field.actualField(_).asInstanceOf[MappedForeignKey[K,O,T]]

  /**
   * Simple OneToMany support for children from the same table
   */
  class MappedOneToMany[O <: Mapper[O]](meta: MetaMapper[O], foreign: MappedForeignKey[K,O,T], qp: QueryParam[O]*)
    extends MappedOneToManyBase[O](
      ()=>{
        val ret = meta.findAll(By(foreign, primaryKeyField.get) :: qp.toList : _*)
        for(child <- ret) {
          foreign.actualField(child).asInstanceOf[MappedForeignKey[K,O,T]].primeObj(net.liftweb.common.Full(OneToMany.this : T))
        }
        ret
      },
      foreign
    )

  /**
   * This is the base class to use for fields that represent one-to-many or parent-child relationships.
   * Maintains a list of children, tracking pending additions and deletions, and
   * keeping their foreign key pointed to this mapper.
   * Implements Buffer, so the children can be managed as one.
   * Most users will use MappedOneToMany, however to support children from multiple tables
   * it is necessary to use MappedOneToManyBase.
   * @param reloadFunc A function that returns a sequence of children from storage.
   * @param foreign A function that gets the MappedForeignKey on the child that refers to this parent
   */
  class MappedOneToManyBase[O <: Mapper[_]](val reloadFunc: () => Seq[O],
                                            val foreign: O => MappedForeignKey[K,_,T]) extends scala.collection.mutable.Buffer[O] {
    private var inited = false
    private var _delegate: List[O] = _
    /**
     * children that were added before the parent was ever saved
     */
    private var unlinked: List[O] = Nil
    protected def delegate: List[O] = {
      if(!inited) {
        refresh()
        inited = true
      }
      _delegate
    }
    protected def delegate_=(d: List[O]): Unit = _delegate = d

    /**
     * Takes ownership of e. Sets e's foreign key to our primary key
     */
    protected def own(e: O): O = {
      val f0 = foreign(e).asInstanceOf[Any]
      f0 match {
        case f: MappedLongForeignKey[O,T] with MappedForeignKey[K,_,T] =>
          f.apply(OneToMany.this)
        case f: MappedForeignKey[K,_,T] =>
          f.set(OneToMany.this.primaryKeyField.get)
      }
      if(!OneToMany.this.saved_?)
        unlinked ::= e
      e
    }
    /**
     * Relinquishes ownership of e. Resets e's foreign key to its default value.
     */
    protected def unown(e: O): O = {
      val f = foreign(e)
      f.set(f.defaultValue)
      unlinked = unlinked filter {e.ne}
      e
    }
    /**
     * Returns the backing List
     */
    def all: List[O] = delegate

    // 2.8: return this
    def +=(elem: O): MappedOneToManyBase.this.type = {
      delegate = delegate ++ List(own(elem))
      this
    }
    // 2.7
    //def readOnly = all
    def length: Int = delegate.length
    // 2.7
    //def elements = delegate.elements
    // 2.8
    def iterator: Iterator[O] = delegate.iterator

    def apply(n: Int): O = delegate(n)

    // 2.7
    /* def +:(elem: O) = {
      delegate ::= own(elem)
      this
    } */
    // 2.8
    def +=:(elem: O): MappedOneToManyBase.this.type = {
      delegate ::= own(elem)
      this
    }

    override def indexOf[B >: O](e: B): Int = delegate.indexWhere(e.asInstanceOf[AnyRef].eq)

    // 2.7
    // def insertAll(n: Int, iter: Iterable[O]) {
    // 2.8
    def insertAll(n: Int, iter: Traversable[O]) {
      val (before, after) = delegate.splitAt(n)
      iter foreach own
      delegate = before ++ iter ++ after
    }

    def update(n: Int, newelem: O): Unit = {
      unown(delegate(n))
      delegate = delegate.take(n) ++ List(own(newelem)) ++ delegate.drop(n+1)
    }

    def remove(n: Int): O = {
      val e = unown(delegate(n))
      delegate = delegate.filterNot(e.eq)
      e
    }

    def clear(): Unit = {
      while(delegate.nonEmpty)
        remove(0)
    }

    /**
     * Reloads the children from storage.
     * NOTE: This may leave children in an inconsistent state.
     * It is recommended to call save or clear() before calling refresh.
     */
    def refresh(): Unit = {
      delegate = reloadFunc().toList
      if(saved_?)
        unlinked = Nil
      else
        unlinked = _delegate
    }

    /**
     * Saves this "field," i.e., all the children it represents.
     * Returns false as soon as save on a child returns false.
     * Returns true if all children were saved successfully.
     */
    def save: Boolean = {
      unlinked foreach {u =>
        val f = foreign(u)
        if(f.obj.map(_ eq OneToMany.this) openOr true) // obj is Empty or this
        f.apply(OneToMany.this)
      }
      unlinked = Nil
      delegate = delegate.filter {e =>
        foreign(e).get == OneToMany.this.primaryKeyField.get ||
          foreign(e).obj.map(_ eq OneToMany.this).openOr(false) // obj is this but not Empty
      }
      delegate.forall(_.save)
    }

    override def toString: String = {
      val c = getClass.getSimpleName
      val l = c.lastIndexOf("$")
      c.substring(c.lastIndexOf("$",l-1)+1, l) + delegate.mkString("[",", ","]")
    }
  }

  /**
   * Adds behavior to delete orphaned fields before save.
   */
  trait Owned[O<:Mapper[_]] extends MappedOneToManyBase[O] {
    var removed: List[O] = Nil
    override def unown(e: O) = {
      removed = e :: removed
      super.unown(e)
    }
    override def own(e: O) = {
      removed = removed filter {e.ne}
      super.own(e)
    }
    override def save: Boolean = {
      val unowned = removed.filter{ e =>
        val f = foreign(e)
        f.get == f.defaultValue
      }
      unowned foreach {_.delete_!}
      super.save
    }
  }

  /**
   * Trait that indicates that the children represented
   * by this field should be deleted when the parent is deleted.
   */
  trait Cascade[O<:Mapper[_]] extends MappedOneToManyBase[O] {
    def delete_! : Boolean = {
      delegate.forall { e =>
        if(foreign(e).get ==
          OneToMany.this.primaryKeyField.get) {
          e.delete_!
        }
        else
          true // doesn't constitute a failure
      }
    }
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy