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