[1.1] where Scala and play JPA don't meet

25 views
Skip to first unread message

Bart Schuller

unread,
Nov 2, 2009, 4:54:54 PM11/2/09
to play-framework
Hi,

This is a fairly technical mail about features that aren't written
yet. Feel free to ignore.

My quest to write a version of the tutorial using Scala brought me to
model objects and tests. Writing a simple entity in Scala is easy, but
I started with a Java entity and a Scala test. The trouble starts when
you want to write User.find(…) and find out that in Scala that doesn't
work. JPASupport.find(…) does work, but doesn't run, because the byte
code magic really wants the find method to belong to User.

What would work for Scala is to declare the User companion object and
make that extend something providing the methods. The problem is: that
something can't be the existing JPASupport class and so will mean a
significant restructuring of the play core. Worse, I don't think it
can be made compatible with Java as long as static methods are involved.

This wouldn't matter if every play application started from scratch,
but the CRUD module in particular knows very intimate details of the
current JPA support. So is there a future for Scala as a module?

A possible way forward that I can see is to define a core API that may
not be the nicest thing to work in from either language, but that is
at least compatible with both. Think plain JPA with something to
provide an entity manager. Core modules like CRUD would then be
rewritten using the new API. The current Java sugar for entities could
be added back in based on the API, but it wouldn't be used for Scala
code. Scala would use different means to create a nice to use API.


Now I could be wrong and see problems that aren't there, or we could
decide this is all too much work for a small project and focus our
energy on other ways to make Play! better. Either way, I'd like to
know what you think.

--
Bart.S...@gmail.com

phausel

unread,
Nov 2, 2009, 5:05:59 PM11/2/09
to play-framework
Hi Bart, I have not tried it but could not implicit conversions be
used to simulate find on models?

just a thought
Peter
> Bart.Schul...@gmail.com

Bart Schuller

unread,
Nov 2, 2009, 5:37:33 PM11/2/09
to play-fr...@googlegroups.com

On Nov 2, 2009, at 23:05, phausel wrote:

> Hi Bart, I have not tried it but could not implicit conversions be
> used to simulate find on models?

Oh yes, a nice API for Scala code would probably make tasteful use of
implicit conversions, as well as lots of traits to mix in. My mail was
more about the details of actually making it work.

> just a thought
> Peter

--
Bart.S...@gmail.com

phausel

unread,
Nov 2, 2009, 6:09:34 PM11/2/09
to play-framework
>My mail was  
> more about the details of actually making it work.

Hi Bart, that is what I was also referring to.

here you said:
"JPASupport.find(…) does work, but doesn't run, because the byte
code magic really wants the find method to belong to User. "

could we just add an implicit conversion to add that missing 'find
method' to the model class?
something along this line:

implicit def model2RichModel(model: Model):JPASupport.JPAQuery = new
RichModel(model);

class RichModel(m:Model) {
def find(query:String,params:java.lang.Object*)=JPASupport.find
(query,params:_*)
}


Peter

Bart Schuller

unread,
Nov 2, 2009, 6:27:38 PM11/2/09
to play-fr...@googlegroups.com

On Nov 3, 2009, at 0:09, phausel wrote:
> class RichModel(m:Model) {
> def find(query:String,params:java.lang.Object*)=JPASupport.find
> (query,params:_*)
> }

This will call:

public static JPAQuery find(String query, Object... params) {
throw new UnsupportedOperationException("Please annotate your
JPA model with @javax.persistence.Entity annotation.");
}

but what it really should call is added by JPAEnhancer to every model
class:

CtMethod find = CtMethod.make("public static
play.db.jpa.JPASupport.JPAQuery find(String query, Object[] params)
{ javax.persistence.Query q = em().createQuery
(play.db.jpa.JPQLDialect.instance.createFindByQuery(\"" + entityName +
"\", \"" + entityName + "\", query, params)); return new
play.db.jpa.JPASupport.JPAQuery
(play.db.jpa.JPQLDialect.instance.createFindByQuery(\"" + entityName +
"\", \"" + entityName + "\", query, params),
play.db.jpa.JPQLDialect.instance.bindParameters(q,params)); }",
ctClass);


--
Bart.S...@gmail.com

phausel

unread,
Nov 2, 2009, 6:32:22 PM11/2/09
to play-framework
Thanks! I will play with it tonight
> Bart.Schul...@gmail.com

Guillaume Bort

unread,
Nov 3, 2009, 5:41:11 AM11/3/09
to play-fr...@googlegroups.com
> The trouble starts when
you want to write User.find(…) and find out that in Scala that doesn't
work. JPASupport.find(…) does work, but doesn't run, because the byte
code magic really wants the find method to belong to User.

Yes I know. You can't use directly the helper defined on Java Model
objects. This is because scala create a User companion object and a
JPASupport companion object. But User does not extend JPASupport. In
scala an object cannot act as superclass for another object.

So we will need to provide another API for model in Scala. In fact
this is already the same problem with the Controller API.

Also I plan to refactor a little the Model API to be able to use CRUD,
Fixtures, etc... with other persistance engine (like Siena, ...). So I
think that the Scala-specific JPA API could work with CRUD as well.

Guillaume Bort

unread,
Nov 3, 2009, 5:43:07 AM11/3/09
to play-fr...@googlegroups.com
Does this implicit conversion work (Despite the fact that it will need
to call some non-existent code) ?

Bart Schuller

unread,
Nov 3, 2009, 6:53:49 AM11/3/09
to play-fr...@googlegroups.com
Sorry,

I didn't try it yet, I only reacted to JPASupport.find, but this
evening I'll see if I can make a piece of Scala with a User object
with no methods, yet where User.find will compile.

On Nov 3, 2009, at 11:43, Guillaume Bort wrote:

>
> Does this implicit conversion work (Despite the fact that it will need
> to call some non-existent code) ?
>
> On Tue, Nov 3, 2009 at 12:27 AM, Bart Schuller <Bart.S...@gmail.com
> > wrote:
>>
>>
>> On Nov 3, 2009, at 0:09, phausel wrote:
>>> class RichModel(m:Model) {
>>> def find(query:String,params:java.lang.Object*)=JPASupport.find
>>> (query,params:_*)
>>> }

--
Bart.S...@gmail.com

phausel

unread,
Nov 3, 2009, 9:28:17 AM11/3/09
to play-framework
Hi,

I believe the idea definitely should work,ie adding methods to
existing classes using implicits to simulate what the enhancer does
(the example was just a pseudo-code though). Peter
> > Bart.Schul...@gmail.com

Guillaume Bort

unread,
Nov 3, 2009, 10:08:38 AM11/3/09
to play-fr...@googlegroups.com
Well,

The problem is that the User companion object seen by scala, does not
extends JPASupport.
So the implicit method will not work. Perhaps if I add a Java
interface to JPASupport, it could be seen as a scala Trait ?

Bart Schuller

unread,
Nov 3, 2009, 10:48:25 AM11/3/09
to play-fr...@googlegroups.com

On Nov 3, 2009, at 16:08, Guillaume Bort wrote:

> The problem is that the User companion object seen by scala, does not
> extends JPASupport.
> So the implicit method will not work. Perhaps if I add a Java
> interface to JPASupport, it could be seen as a scala Trait ?

In Scala, it's very easy to add lots of methods to an object such as
User:

object User extends JPAMethods

where JPAMethods is an interface declaring normal methods, or a Scala
trait where the methods can carry implementations as well.

But for the case where User is a java class, this will not work. I'll
still try the implicit conversion way, because even though User is to
Scala an object with no methods, it should still be possible to give
it an explicit conversion, something like def any2Richmodel(any: AnyRef)


--
Bart.S...@gmail.com

Guillaume Bort

unread,
Nov 3, 2009, 10:51:36 AM11/3/09
to play-fr...@googlegroups.com
I tried it a little but can't make it work.

For the User class defined in Java, it seems that there is no real
scala companion object:

println(User) ==> value User not found.

But if the Java object define a static method, you can still call it using:

User.findByName("...")

So I think there is a hack somewhere ...

Bart Schuller

unread,
Nov 3, 2009, 3:13:13 PM11/3/09
to play-fr...@googlegroups.com

On Nov 3, 2009, at 16:51, Guillaume Bort wrote:

I tried it a little but can't make it work.

For the User class defined in Java, it seems that there is no real
scala companion object:

println(User) ==> value User not found.

That's my conclusion as well, so, no implicit conversions.

But what would work is mandating that if you use java models with scala, you write one scala file in the models directory containing a line for every model like this:

object Juser extends Findable
object JGroup extends Findable

Here's a test that shows the concept (I've changed the signature of find to return a model here):

class BasicTest extends UnitTest {
class Findable {
def find(how: String, vals: Any*) = {
println("finding "+how+" on "+this.getClass())
new Juser("b...@gmail.com", "secret", "Bob")
}
}
object Juser extends Findable

    @Test
    def createAndRetrieveUser() {
        new Juser("b...@gmail.com", "secret", "Bob").save()

        val bob = Juser.find("byEmail", "b...@gmail.com")
        // Test 
        assertNotNull(bob)
        assertEquals("Bob", bob.fullname)
    }
}


This prints: "finding byEmail on class BasicTest$Juser$" and also passes the test.


Bart Schuller

unread,
Nov 3, 2009, 3:50:40 PM11/3/09
to play-framework
On Nov 3, 2009, at 21:13, Bart Schuller wrote:

> But what would work is mandating that if you use java models with
> scala, you write one scala file in the models directory containing a
> line for every model like this:
>
> object Juser extends Findable


Of course, with all the custom classloading and compiling going on, it
would be possible to have play do this itself.

Still, that leaves writing the trait that implements JPASupport but as
regular methods.

--
Bart.S...@gmail.com

Guillaume Bort

unread,
Nov 3, 2009, 4:00:46 PM11/3/09
to play-fr...@googlegroups.com
Yes that's a good idea. That allows to work with existing Java object in a very simple way. 

Btw I think we could make these objects inherit Model directly and use implicits to redefine static methods. If the object is defined in scala, it should work.  

Bart Schuller

unread,
Nov 3, 2009, 4:47:01 PM11/3/09
to play-fr...@googlegroups.com

On Nov 3, 2009, at 22:00, Guillaume Bort wrote:

Yes that's a good idea. That allows to work with existing Java object in a very simple way. 

Btw I think we could make these objects inherit Model directly and use implicits to redefine static methods. If the object is defined in scala, it should work.  

That is just some more code to wind up where we started:

object Juser extends Model

class StaticAdapter(m: Model) {
def find(how: String, vals: Any*) = // what?
}
implicit def model2StaticAdapter(m: Model) = new StaticAdapter(m)

We're not one step closer to an implementation of find() here.

No, once we declare the objects to extend something, the implementation can live there as well and we don't need implicits, but it really needs to be written somewhere as normal methods first.


Bart Schuller

unread,
Nov 3, 2009, 5:26:52 PM11/3/09
to play-fr...@googlegroups.com

On Nov 3, 2009, at 22:47, Bart Schuller wrote:
No, once we declare the objects to extend something, the implementation can live there as well and we don't need implicits, but it really needs to be written somewhere as normal methods first.

Working code:

class BasicTest extends UnitTest {
class Findable(entityName: String) {
def find(query: String, params: AnyRef*) = {
val fbq = JPQLDialect.instance.createFindByQuery(entityName, entityName, query, params: _*)
val q = JPA.em().createQuery(fbq)
new JPASupport.JPAQuery(fbq, JPQLDialect.instance.bindParameters(q,params:_*))
}
}
object Juser extends Findable("Juser")

    @Test
    def createAndRetrieveUser() {
        new Juser("b...@gmail.com", "secret", "Bob").save()

        val bob: Juser = Juser.find("byEmail", "b...@gmail.com").first()
        // Test 
        assertNotNull(bob)
        assertEquals("Bob", bob.fullname)
    }
}

So it's just a matter of renaming Findable and fleshing it out. Reformatting and reading the generated code is instructive by the way, it made me conclude that fbq could be factored out here :-)

Guillaume Bort

unread,
Nov 4, 2009, 4:37:07 AM11/4/09
to play-fr...@googlegroups.com
Ok, fine.
I will start to move all this code out of the JPAEnhancer...

Bart Schuller

unread,
Nov 4, 2009, 5:55:58 PM11/4/09
to play-fr...@googlegroups.com

On Nov 4, 2009, at 10:37, Guillaume Bort wrote:

> Ok, fine.
> I will start to move all this code out of the JPAEnhancer...

That looks good. Here's where I got this evening:

Juser is a java model, User is a scala model:

val bob: Juser = find[Juser]("byEmail", "b...@gmail.com").first()

val harry: User = find[User]("byEmail", "ha...@gmail.com").first()

val tom: User = User.find("byEmail", "t...@gmail.com").first()

You'll notice that Juser.find is not in there. I tried, but it looks
like it really isn't possible.

Of course, find[Juser](q, v) is just one possible function-like
syntax, find("User", q, v) is even easier (and would also work as an
alternative java syntax with a static import).


Comments, opinions?

--
Bart.S...@gmail.com

Guillaume Bort

unread,
Nov 4, 2009, 6:27:43 PM11/4/09
to play-fr...@googlegroups.com
Seems great. Can you share the code somewhere in a branch ?

Bart Schuller

unread,
Nov 5, 2009, 3:03:15 AM11/5/09
to play-fr...@googlegroups.com

On Nov 5, 2009, at 0:27, Guillaume Bort wrote:

> Seems great. Can you share the code somewhere in a branch ?

I will, this evening (as you may have noticed, play time for me is
only evenings (CET) and weekends).

Another thing I'd like to try out is to simplify imports for standard
tasks. In Scala, it should be possible to say

import play.scala.Test._

as the only import statement for a test and have JUnit, the models,
the query functions and everything available. The same can be done for
controllers and for models themselves.
Reply all
Reply to author
Forward
0 new messages