I made the beginnings of a lift app for GAE. It's for a volunteer
medical assistance organization, to manage requests that they get.
Every request has a nature, e.g., transportation, hospital visitation,
etc. Each nature has a OneToMany association with a
NatureLocationType, which means that when you enter the request you
have a different set of location fields depending on the nature. One
screen lets you manage the natures. On the same screen that you edit
the name of the nature you can add, remove, and edit location types
for that nature. I'm new to lift, GAE, and JPA, although familiar with
Scala. So far I'm only working with the dev server.
I started with the lift JPA library demo's (snippet) code
Anyway, after a while I got it pretty functional. (After discovering
that after each request, when it processes the input from the previous
request the same entity object is still in the JVM, but it has become
detached and there's no way to reattach it other than re-looking it up
via its id---why does the entity manager have a shorter lifespan than
the entity?)
However, every now and then I get an error that the object is dirty
but no field are, and sometimes an error that a request completed
without closing the (which?) transaction.
Could someone look at the source code and help me? Thanks, it's really
appreciated.



Here's the source for the snippet class (models below; note that
EntityManager is my name for what the library demo calls Model: object
EntityManager extends LocalEMF("transactions-optional") with
RequestVarEM):

package liftgae1.snippet

import javax.persistence.{EntityExistsException, PersistenceException}

import scala.collection.jcl.Conversions.convertList
import scala.xml.{NodeSeq, Text}

import net.liftweb.http.{RequestVar, SHtml, S, StatefulSnippet}
import S._
import net.liftweb.util.{Helpers, Log}
import Helpers._

import lrbcol.model.{EntityManager, Nature, NatureLocationType}

class Natures /*extends StatefulSnippet*/ {
  /*
  val dispatch: DispatchIt = {
    case "list" => list
    case "newOrEdit" => newOrEdit
    case "edit" => edit
  }*/

  // Set up a requestVar to track the nature object for edits and adds
  private object natureVar extends RequestVar[Nature](new Nature)
  def nature: Nature = natureVar.is
  def nature_=(nature: Nature) = natureVar(nature)
  def hasNature = nature.id != null
  def reloadNature = if(hasNature) try {nature = Nature.lookup
(nature.id).get} catch {case e => println(e)}


  def list(xhtml: NodeSeq): NodeSeq = {
    println("In list")
    Nature.all foreach println
    NatureLocationType.all foreach {l=>println(l + " for " +
l.nature)}
    Nature.all.flatMap { n => println("Binding with " + n + ",
contained = " + EntityManager.contains(n))
      bind("nature", xhtml,
           "name" ->            Text(n.name),
           "locations" ->       Text(n.natureLocationTypes map {_.name}
mkString ","),
           "edit" ->            SHtml.link("edit",
                                 () => nature = n,
                                 Text(?("Edit"))
                                                ),
           "remove" ->          SHtml.link("list",
                                 () => {nature = n; reloadNature;
EntityManager.removeAndFlush(nature)},
                                 Text(?("Remove"))
                                                )
      )
        }
  }


  def newOrEdit(xhtml: NodeSeq) =
    hasNature match {
      case false =>     chooseTemplate("if", "new", xhtml)
      case _ => chooseTemplate("if", "edit", xhtml)
    }


  def edit(xhtml: NodeSeq): NodeSeq = {
    def doSave() {
      println("In doSave with %s" format(nature))
      if (nature.name.isEmpty) {
        error("emptyNature", "The nature's name cannot be blank")
      } else try {
        EntityManager.mergeAndFlush(nature)
        redirectTo("list")
      } catch {
        case ee: EntityExistsException =>
          error("That nature already exists.")
        case pe: PersistenceException =>
          error("Error adding nature"); Log.error("Nature add failed",
pe)
      }
    }

    def insertLocation() {
      val l = new NatureLocationType
      nature.natureLocationTypes += l
      l.nature = nature
      EntityManager.mergeAndFlush(if(hasNature) l else nature)
    }

    def removeLocation(l: NatureLocationType) {
      try {
        EntityManager.removeAndFlush(l)
      } catch {
                case e => e.printStackTrace
      }
    }


    println("In edit with nature %s." format(nature))

    def bindLocation(loc: NatureLocationType) = {
      var l = loc
      bind("location", chooseTemplate("each", "location", xhtml),
      "id" -> SHtml.hidden(()=> l = NatureLocationType.lookup
(loc.id)),
      "name" -> SHtml.text(l.name, l.name = _),
      "allowStreet" -> SHtml.checkbox(l.allowStreet, l.allowStreet =
_),
      "allowHospital" -> SHtml.checkbox(l.allowHospital,
l.allowHospital = _),
      "allowDoctor" -> SHtml.checkbox(l.allowDoctor, l.allowDoctor =
_),
      "allowEquipment" -> SHtml.checkbox(l.allowEquipment,
l.allowEquipment = _),
      "removeBtn" -> SHtml.submit(?("Remove"), ()=>removeLocation(l))
      )
    }

    val locations = NatureLocationType.all.filter
(_.nature.id==nature.id)

    val currentNature = nature
    // Hold a val here so that the "id" closure holds it when we re-
enter this method
    bind("nature", xhtml,
         "id" -> SHtml.hidden(() => {
           nature = currentNature
           reloadNature
         }),
         "name" -> SHtml.text(nature.name, nature.name = _),
         "locations" -> locations.flatMap(bindLocation),
         "submit" -> SHtml.submit(?("Save"), doSave),
         "newLocation" -> SHtml.submit(?("New location"),
insertLocation)
    )
  }
}


////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
And here's the models source file:
package lrbcol.model

import javax.persistence._
import com.google.appengine.api.datastore.Key

import scala.collection.jcl.Conversions._

import Util._


@Entity
class Nature extends NotNull {
  @Id
  @GeneratedValue(){val strategy = GenerationType.IDENTITY}
  var id: Key = _

  @Column{val nullable = false}
  var name: String = ""

  @OneToMany{val mappedBy = "nature", val targetEntity = classOf
[NatureLocationType], val cascade=Array(CascadeType.ALL), val
fetch=FetchType.EAGER}
  var natureLocationTypes: java.util.List[NatureLocationType] with
NotNull =
    new java.util.ArrayList[NatureLocationType] with NotNull

  override def toString = (if(EntityManager.contains(this)) "" else
"un") + "contained %s %s (%s) [%s]".format(super.toString, id, name,
natureLocationTypes.mkString(", "))
}


@Entity
class NatureLocationType extends NotNull {
  @Id
  @GeneratedValue(){val strategy = GenerationType.IDENTITY}
  var id: Key = _


  @Column{val nullable = false}
  var name : String = ""

  @Column{val nullable = false}
  var allowStreet: Boolean = false

  @Column{val nullable = false}
  var allowHospital: Boolean = false

  @Column{val nullable = false}
  var allowDoctor: Boolean = false

  @Column{val nullable = false}
  var allowEquipment: Boolean = false

  @Column{val nullable = false}
  @ManyToOne
  var nature: Nature = new Nature

  override def toString = super.toString + " " + name
}


object Nature {
  def lookup(id: Key) =
    EntityManager.createQuery[Nature]("select from lrbcol.model.Nature
n where n.id = :id").
        setParameter("id", id).findOne

  def all: Seq[Nature] with NotNull =
    EntityManager.createNamedQuery[Nature]
("findAllNatures").getResultList
}

object NatureLocationType {
  def lookup(nature: Nature): Seq[NatureLocationType] with NotNull =
    EntityManager.
      createNamedQuery[NatureLocationType]("findLocationsByNature").
      setParameter("id", nature.id).
      getResultList

  def lookup(id: Key) = EntityManager.
    createQuery[NatureLocationType]("select from
lrbcol.model.NatureLocationType where id=:id").
    setParameter("id", id).
    getSingleResult

  def all = EntityManager.createQuery[NatureLocationType]("select from
lrbcol.model.NatureLocationType").getResultList
}


object Util {
  implicit def seqToNotNull[T](seq: Seq[T]): Seq[T] with NotNull =
    new SeqProxy[T] with NotNull {
          def self = seq
        }

  class Nzable[T](orig: T) {
    def nz(alt: T with NotNull): T = if(orig == null) alt else orig
  }
  implicit def nullAlt[T](orig: T): Nzable[T] = new Nzable(orig)

  def notNull[T](expr:T)(empty: =>T with NotNull, notNull: (T=>T with
NotNull) with NotNull): T with NotNull = {
    expr match {
      case null => empty
      case x: T with NotNull => x
      case x => notNull(x)
    }
  }
}

--~--~---------~--~----~------------~-------~--~----~
You received this message because you are subscribed to the Google Groups 
"Lift" group.
To post to this group, send email to liftweb@googlegroups.com
To unsubscribe from this group, send email to 
liftweb+unsubscr...@googlegroups.com
For more options, visit this group at 
http://groups.google.com/group/liftweb?hl=en
-~----------~----~----~----~------~----~------~--~---

Reply via email to