Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[2.1.0] JPA: Parameter value [*] was not matching type [java.util.Map] #851

Merged
merged 1 commit into from Jun 27, 2013

Conversation

jroper
Copy link
Member

@jroper jroper commented Jun 26, 2013

When fetching a list of models and getting it by a '.setParameter()', instead of getting the list something throws an exception about a not matching type. Here is the code that i used in the sample project:

 @Transactional
    public static Result index() {
        Company company = Company.findById(1l);
        List<Computer> data = JPA.em()
                .createQuery("from Computer where company = :company")
                .setParameter("company", company)
                .getResultList();

        return GO_HOME;

    }

This is the exception that gets thrown.

play.api.Application$$anon$1: Execution exception[[IllegalArgumentException: Parameter value [models.Company@f321da3] was not matching type [java.util.Map]]]
    at play.api.Application$class.handleError(Application.scala:289) ~[play_2.10-2.1.0.jar:2.1.0]
    at play.api.DefaultApplication.handleError(Application.scala:383) [play_2.10-2.1.0.jar:2.1.0]
    at play.core.server.netty.PlayDefaultUpstreamHandler$$anon$2$$anonfun$handle$1.apply(PlayDefaultUpstreamHandler.scala:132) [play_2.10-2.1.0.jar:2.1.0]
    at play.core.server.netty.PlayDefaultUpstreamHandler$$anon$2$$anonfun$handle$1.apply(PlayDefaultUpstreamHandler.scala:128) [play_2.10-2.1.0.jar:2.1.0]
    at play.api.libs.concurrent.PlayPromise$$anonfun$extend1$1.apply(Promise.scala:113) [play_2.10-2.1.0.jar:2.1.0]
    at play.api.libs.concurrent.PlayPromise$$anonfun$extend1$1.apply(Promise.scala:113) [play_2.10-2.1.0.jar:2.1.0]
java.lang.IllegalArgumentException: Parameter value [models.Company@f321da3] was not matching type [java.util.Map]
    at org.hibernate.ejb.AbstractQueryImpl.registerParameterBinding(AbstractQueryImpl.java:360) ~[hibernate-entitymanager-3.6.9.Final.jar:3.6.9.Final]
    at org.hibernate.ejb.QueryImpl.setParameter(QueryImpl.java:364) ~[hibernate-entitymanager-3.6.9.Final.jar:3.6.9.Final]
    at org.hibernate.ejb.QueryImpl.setParameter(QueryImpl.java:72) ~[hibernate-entitymanager-3.6.9.Final.jar:3.6.9.Final]
    at controllers.Application.index(Application.java:37) ~[na:na]
    at Routes$$anonfun$routes$1$$anonfun$applyOrElse$1$$anonfun$apply$1.apply(routes_routing.scala:73) ~[na:na]
    at Routes$$anonfun$routes$1$$anonfun$applyOrElse$1$$anonfun$apply$1.apply(routes_routing.scala:73) ~[na:na]

@Gissues:{"order":33.333333333333286,"status":"backlog"}

@benlaughlin
Copy link

Hey guys, any progress on this one? This is a blocker for upgrading from 2.0.4 for me.

@slagod
Copy link

slagod commented Apr 11, 2013

I just moved to Play from SpringMVC and I encountered exactly the same issue.

There is workaround:

Instead of passing entities as a parameters, you can pass their identifiers, e.g.:

Company company = Company.findById(1l);
        List<Computer> data = JPA.em()
                .createQuery("from Computer where company.id = :company")
                .setParameter("company", company.id)
                .getResultList();

Of course this is not ideal and I would like to see this issue fixed. Anybody?

@benlaughlin
Copy link

Still exists in 2.1.1

@ferdy-lw
Copy link

This error happens when specifying the result class of a TypedQuery as well:

List<Computer> data = JPA.em()
                .createQuery("from Computer", Computer.class)
                .getResultList();

This has something to do with the classloaders, as the error results from Hibernate trying to determine the specified result class is assignable from the query result (same when setting the parameter in the query - it makes sure the class is correct).

Interestingly (or annoyingly) this does not happen in running play test! The test cases pass, but play run/debug fails. The classloaders/paths will obviously be different between test and run, but I would have thought the models path would be the same in both instances. [This is in 2.1.1]

More details ->

This is in the EntityManager createQuery(). If the return type is not null it verifies the result class:

else if ( hqlQuery.getReturnTypes().length == 1 ) {
                    // if we have only a single return expression, its java type should match with the requested type
                    if ( !resultClass.isAssignableFrom( hqlQuery.getReturnTypes()[0].getReturnedClass() ) ) {
                        throw new IllegalArgumentException(
                                "Type specified for TypedQuery [" +
                                        resultClass.getName() +
                                        "] is incompatible with query return type [" +
                                        hqlQuery.getReturnTypes()[0].getReturnedClass() + "]"
                        );
                    }
                }

The getReturnedClass() ends up down here in hibernates EntityType, which will ultimately do a classForName

    private Class determineAssociatedEntityClass() {
        try {
            return ReflectHelper.classForName( getAssociatedEntityName() );
        }
        catch ( ClassNotFoundException cnfe ) {
            return java.util.Map.class;
        }
    }

@nchiring
Copy link

Has anyone got a workaround for this?

@jroper
Copy link
Member

jroper commented Apr 28, 2013

If you wrap calls to hibernate in:

ClassLoader old = Thread.currentThread().getContextClassLoader();
try {
  Thread.currentThread().setContextClassLoader(Play.application().classloader());
  // Your hibernate code here
} finally {
  Thread.currentThread().setContextClassLoader(old);
}

Does this solve it?

@nchiring
Copy link

James,
That works! Thank you.
However, I didn't want to wrap my hibernate/jpa code with this try {..}finally{..} pattern everywhere. So, I came up with the following class(dynamic proxy to the rescue !!), namely, MyJPA.java.

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import javax.persistence.EntityManager;
import play.Play;
import play.db.jpa.JPA;

public class MyJPA {

public static EntityManager em(){
    return (EntityManager) Proxy.newProxyInstance( Play.application().classloader(),
                                                   new Class[] { EntityManager.class }, 
                                                   new ProxyEM(JPA.em()) 
                                                  );
}

private static class ProxyEM implements InvocationHandler
{
  EntityManager original;

  public ProxyEM(EntityManager em){ 
      this.original = em; 
  }
  public Object invoke(Object proxy, Method m, Object[] args) throws Throwable
  {
    try {
          ClassLoader old = Thread.currentThread().getContextClassLoader();
          try {
             Thread.currentThread().setContextClassLoader(Play.application().classloader());
             return m.invoke(original, args);
          } finally {
            Thread.currentThread().setContextClassLoader(old);
          }
    } catch (InvocationTargetException e) {
        throw e.getCause();
    } catch (Exception e) {
        throw e;
    }
  }
}

}

Now, I can use MyJPA.em() instead of play.db.jpa.JPA.em() . I shall switch back from MyJPA.em() to play.db.jpa.JPA.em() when this bug is fixed.

@benlaughlin
Copy link

Hey nchiring, handy work-around!

Does anyone know how we get visibility on this bug? I'm worried it isn't even assigned to a milestone yet. Other than having to refactor a complete code base with a work-around above, I would have thought this is blocker for anyone using JPA in anything beyond 2.0.4.

Thanks in advance!

@jroper
Copy link
Member

jroper commented Apr 29, 2013

@benlaughlin You have visibility, and I get notified of everything that happens on the issue tracker.

@benlaughlin
Copy link

Thanks jroper!

@thomas-p-wilson
Copy link

I'm also having the same problem that ferdy-lw described above, wherein a classloader-related issue is encountered when specify the result class of a TypedQuery. The issue exists in master (f231601) as well as the java-future branch in richdougherty's fork as of 24cb6e6c.

@richdougherty
Copy link
Member

@thomas-p-wilson, thanks for checking the java-future branch. Out of interest, does the problem go away if you hack the HttpExecutionContext's execute method to always use the Play ClassLoader?

thread.setContextClassLoader(Play.application().classloader())

@thomas-p-wilson
Copy link

Hey Rich, sorry for the late reply. I'm new to the hacking of the Play Framework internals, so forgive me if this seems a dull question, but is your question a re-iteration of jroper's comment (comment 17145471) from 22 days ago? If so, then I'm pretty sure the answer is yes, but that was Friday. I'll check tomorrow again to make sure.

The issue I'm having is that I can't really be wrapping every single data access call with that. I'm totally willing to dive in and try to figure out a solution, but I'm new to hacking the core so I might need some guidance.

@richdougherty
Copy link
Member

What I mean is that, in my branch (now merged into master), I introduced a new class called HttpExecutionContext that should automatically propagate the current context ClassLoader when using Java F.Promises. I hoped propagating the current context ClassLoader would also fix this issue, but according to your comment it doesn't seem to be working.

So I was wondering if you wouldn't mind editing HttpExecutionContext in master so that it always uses Play.application().classloader() instead of using one it picks up automatically, then test whether that resolves this issue. This can be achieved quite easily by editing the file and looking for the line where the contextClassLoader is set and changing it to:

thread.setContextClassLoader(Play.application().classloader())

This isn't a real fix, but it would provide some helpful information that would lead to a fix.

@thomas-p-wilson
Copy link

Roger that. Thanks for the clarification. I'll do that first thing in the morning.

@thomas-p-wilson
Copy link

The modification did not work as expected. I am still receiving the same issue with TypedQuery that I was before. I'm going to keep messing around with it. At the very least I'll be a little more familiar with the internals after this. As a side-note I'm using Play for Java, as opposed to Scala, lest it make any difference.

@thomas-p-wilson
Copy link

A quick inspection of the context classloader reveals SBT/Play shared ClassLoader, with: WrappedArray(file:[...]), using parent: Common ClassLoader: file:/usr/share/playframework/framework/../repository/cache/com.h2database/h2/jars/h2-1.3.168.jar There are a bunch of jars listed in the WrappedArray, but none are related to my project.

@richdougherty
Copy link
Member

The fix should have ensured that any code running as a function supplied to an F.Promise would run with the right context ClassLoader set. But it probably won't handle other code. Where was your code running?

@thomas-p-wilson
Copy link

My code is just running in a normal controller. I haven't been able to test
any of our promise-based code yet because I can't get through our
controllers.

On Tue, Jun 4, 2013 at 12:31 AM, Rich Dougherty notifications@github.comwrote:

The fix should have ensured that any code running as a function supplied
to an F.Promise would run with the right context ClassLoader set. But it
probably won't handle other code. Where was your code running?


Reply to this email directly or view it on GitHubhttps://github.com//issues/851#issuecomment-18888630
.

@jroper
Copy link
Member

jroper commented Jun 26, 2013

The attached pull request fixes this for the 2.1.x branch.

As for master, the test should be copied to master, and if it fails, it should be fixed, but I suspect it may already be fixed on master.

@cloudbees-pull-request-builder

play2-master-PRs #277 FAILURE
Looks like there's a problem with this pull request

@jroper
Copy link
Member

jroper commented Jun 26, 2013

PR validator failed because this PR is against 2.1.x branch, and scalariform hasn't been run against 2.1.x branch yet.

@jroper jroper closed this Jun 27, 2013
@jroper jroper reopened this Jun 27, 2013
@jroper jroper closed this Jun 27, 2013
@jroper jroper reopened this Jun 27, 2013
@cloudbees-pull-request-builder

play2-master-PRs #280 FAILURE
Looks like there's a problem with this pull request

jroper added a commit that referenced this pull request Jun 27, 2013
[2.1.0] JPA: Parameter value [*] was not matching type [java.util.Map]
@jroper jroper merged commit 9162546 into playframework:2.1.x Jun 27, 2013
@benlaughlin
Copy link

Nice work guys, thanks very much for fixing this one!

@jroper jroper deleted the 851-java-classloader branch March 5, 2015 00:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

8 participants