Issue #2200: ClassCastException in Tuple.get

Type: Bug ReoprtVersion: 2.7.4Priority: NormalStatus: FixedReplies: 8
#1

Hello ObjectDB team,

I am getting a ClassCastException with following stack trace:

Caused by: java.lang.ClassCastException: com.objectdb.jpa.criteria.JoinImpl cannot be cast to com.objectdb.o.TPE
 at com.objectdb.o.TPL.get(TPL.java:91)
 at pl.hobsoft.lohare.server.BusinessService.lambda$4(BusinessService.java:732)
 at pl.hobsoft.lohare.server.BusinessService$$Lambda$6/297307723.apply(Unknown Source)
 at com.google.common.collect.Lists$TransformingRandomAccessList$1.transform(Lists.java:651)
 at com.google.common.collect.TransformedIterator.next(TransformedIterator.java:48)
 at java.util.AbstractCollection.toArray(AbstractCollection.java:196)
 at pl.hobsoft.lohare.server.BusinessService.findFinishedDemands(BusinessService.java:736)
 at pl.hobsoft.lohare.server.jsp.views.UserAccountView.findDemands(UserAccountView.java:78)
 at pl.hobsoft.lohare.server.jsp.views.UserAccountView.init(UserAccountView.java:62)
 ... 50 more

 

This time I am not saying it's a bug ;) however with just ClassCastException and without any further explanation (especially without knowledge what com.objectdb.o.TPE is and what TPE.get expects) it's difficult for me to pin the problem down. Is it possible to add type check before the cast and throw more meaningful exception in such case?

 

If you are interested, my code:

 

  EntityManager em = createEm();
  try {
   
   ProvidingPerson storedProvidingPerson = load(em, providingPerson);
   
   CriteriaBuilder cb = em.getCriteriaBuilder();
   
   CriteriaQuery<Tuple> cq = cb.createTupleQuery();

   Root<DemandAnswer> rootDemandAnswer = cq.from(DemandAnswer.class);
   Join<DemandAnswer, Demand> joinDemand = rootDemandAnswer.join("demand");
   Join<Object, Object> joinDemandingPerson = joinDemand.join("demandingPerson");
   Join<Object, Object> joinProvidingPerson = rootDemandAnswer.join("providingPerson");
   joinDemand.join("execution");
   joinDemand.fetch("services");
   joinDemandingPerson.fetch("account");
   cq.multiselect(joinDemand, rootDemandAnswer).distinct(true);
   
   cq.where(
     cb.equal(joinProvidingPerson, storedProvidingPerson)
     );
   
   TypedQuery<Tuple> query = em.createQuery(cq);
   List<Tuple> tuples = query.getResultList();
   
   List<DemandDetails> candidateDemands = Lists.transform(tuples, d -> {    
    //Demand demand = (Demand) d.get(0);
    //DemandAnswer answer = (DemandAnswer) d.get(1);
    Demand demand = d.get(joinDemand);
    DemandAnswer answer = d.get(rootDemandAnswer);
    return new DemandDetails(demand, answer);
   });
   DemandDetails[] demands = candidateDemands.toArray(new DemandDetails[candidateDemands.size()]);
   
   return demands;
   
  } finally {
   em.close();
  }

Commented out calls to Tuple.get(int) work as expected, array Tuple.b contains expected values. Also note that call to Lists.transform is evaluated lazily, that's why stacktrace looks the way it does. Lazy evaluation of Guava methods is sometimes a problem when working with ObjectDB (and other libraries as well), but I do not think it's the cause of above error.

Do you have any suggestions what I am doing wrong?

 

PS. I am really sorry for the formatting, but I cannot get this to work;

#2

Accessing tuple elements by index and by alias works:

cq.multiselect(joinDemand.alias("d"), rootDemandAnswer.alias("a")).distinct(true);

//...

Demand demand = (Demand)tuple.get("d");

DemandAnswer answer = (DemandAnswer)tuple.get(1);

 

So it seems that Tuple.get chokes on Join, Root and probably other TupleElements that are not a Path - am I right?

#3

Just to let you know, in most places I managed to replace multiselect and tuple queries with constructor select and calls to CriteriaBuilder.construct. There is one query which I would like to leave as a tuple query, so I replaced calls to Tuple.get(root) and Tuple.get(join) with Tuple.get(alias, SelectedEntityType.class) but I'd really love to see it fixed, so Tuple.get would work with any TupleElement, be it Path, Join, Root, or whatever. It would make ObjectDB more conformant to JPA specs, too! ;) Or, if it's not going to be fixed for some reason, it could be at least improved by throwing something better and more explanatory than ClassCastException, and mentioning the limitation in docs.

 

Cheers!

#4

It would help if you could isolate the issue and provide a minimal runnable standalone program, in the format as specified in the posting instructions, which demonstrates the exception.

ObjectDB Support
#5

I am sorry, I assumed that issue is trivial to reproduce and that's why I did not attach the sample code. You can find it in attachment, and please note how all calls to Tuple.get(TupleElement) fail with ClassCastException.

Exception in thread "main" java.lang.ClassCastException:
        com.objectdb.jpa.criteria.PathImpl cannot be cast to com.objectdb.o.TPE
    at com.objectdb.o.TPL.get(TPL.java:91)
    at Main.main(Main.java:43)

Cheers!

 

PS. it seems that there is a problem with post editor on MS Edge, as it seems to be set to 'track changes' mode by default and lags terribly.

 

#6

Thank you for this report and for the test case.

Please try ObjectDB version 2.7.4 that should fix the issue.

ObjectDB Support
#7

Things are better (sometimes work), but not perfect (sometimes work incorrectly). Please see attachment for a test case.

For some reason, when calling Tuple.get(TupleElement), invalid element of a tuple is selected. Note how both references e3 and untyped (lines 47 and 48) point to the instance of EntityChild, while untyped is expected to refer to instance of EntityClass. Thus, getting tuple element by its Join results in ClassCastException:

Exception in thread "main" java.lang.ClassCastException: 
        Main$EntityChild cannot be cast to Main$EntityClass
    at Main.main(Main.java:49)

While debugging I noticed that unexpectedly for me both root and joinParent have the same value of field m_variableName="$1", while query string in a log file references selections rather correctly (as $1 and $2), so maybe that's why Tuple.get gets confused. Having observed that, I tried to 'override' these default names with aliases and achieved following result:

1. After aliasing only root, following exception occurred (uncomment line 35 to see):

Exception in thread "main" java.lang.IllegalArgumentException: Unknown alias: $1
    at com.objectdb.o.TPL.get(TPL.java:115)
    at com.objectdb.o.TPL.get(TPL.java:129)
    at com.objectdb.o.TPL.get(TPL.java:99)
    at Main.main(Main.java:48)

2. After aliasing joinParent, problem disappeared (uncomment line 38 to see).

 

Thanks for your work and hope it helps!

 

#8

Thank you for the test case. Please try build 2.7.4_01.

ObjectDB Support
#9

My tests pass now.

Thanks!

Reply