Reflection bug with DMI

Surprised nobody hit this before, but SmartGWT 2.4 gets upset if you have a parent class with an abstract method signature, and that signature contains generics. It is definitely the method signature that is the issue, not the class signature (I did a bit of experimenting).

For anybody that does hit this, the kludgy workaround for the code below was to make fetch() concrete in the parent class, and invoke an abstract handleFetch method. Then the concrete method in the subclass was renamed to handleFetch.

Now for some details. I’m using DMI via Spring, and it worked fine until a recent bit of refactoring. For example with a DMI object UserDmi, and an abstract base class DmiBase as shown below:

public abstract class DmiBase<PK extends Serializable, E extends Entity<PK>> {
public abstract DSResponse fetch(DSRequest dsRequest, E criteria);
…
}

public class UserDmi extends DmiBase<String, User> {
@Override
public DSResponse fetch(DSRequest dsRequest, User criteria) {
log.info(“processing DMI fetch operation on {}”, criteria);
…
}
…
}

In the browser this just gets you a popup dialog with:

sun.reflect.generics.reflectiveObjects.TypeVariableImpl

cannot be cast to java.lang.Class

It looks like the isomorphic reflection class doesn’t handle some of the java generics implementation, apparently in this case TypeVariable. It doesn’t need to, if it just spots that it is dealing with an abstract method signature on the parent, instead of the overridden concrete method on the child. That should be obvious from the Modifier flags on the method.

The log is below. Notice that none of my code is yet in play, only SmartGWT’s attempt to do reflection on it.

=== 2011-05-26 14:15:55,521 [l0-6] INFO ServerObject - DMI on Spring bean: userDmi
=== 2011-05-26 14:15:55,521 [l0-6] WARN RequestContext - dsRequest.execute() failed:
java.lang.ClassCastException: sun.reflect.generics.reflectiveObjects.TypeVariableImpl cannot be cast to java.lang.Class
at com.isomorphic.base.VersionSafeChecker.getGenericParameterTypes(VersionSafeChecker.java:118)
at com.isomorphic.base.Reflection.adaptArgsAndInvoke(Reflection.java:782)
at com.isomorphic.datasource.DataSourceDMI.execute(DataSourceDMI.java:586)
at com.isomorphic.datasource.DataSourceDMI.execute(DataSourceDMI.java:64)
at com.isomorphic.datasource.DSRequest.execute(DSRequest.java:1440)
at com.isomorphic.servlet.IDACall.handleDSRequest(IDACall.java:173)
at com.isomorphic.servlet.IDACall.processRPCTransaction(IDACall.java:130)
at com.isomorphic.servlet.IDACall.processRequest(IDACall.java:95)
at com.isomorphic.servlet.IDACall.doPost(IDACall.java:54)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:637)
at com.isomorphic.servlet.BaseServlet.service(BaseServlet.java:152)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:717)
at org.mortbay.jetty.servlet.ServletHolder.handle(ServletHolder.java:487)
at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1097)
at com.isomorphic.servlet.CompressionFilter.doFilter(CompressionFilter.java:259)
at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1088)
at org.mortbay.jetty.servlet.ServletHandler.handle(ServletHandler.java:360)
at org.mortbay.jetty.security.SecurityHandler.handle(SecurityHandler.java:216)
at org.mortbay.jetty.servlet.SessionHandler.handle(SessionHandler.java:181)
at org.mortbay.jetty.handler.ContextHandler.handle(ContextHandler.java:729)
at org.mortbay.jetty.webapp.WebAppContext.handle(WebAppContext.java:405)
at org.mortbay.jetty.handler.HandlerWrapper.handle(HandlerWrapper.java:152)
at org.mortbay.jetty.handler.RequestLogHandler.handle(RequestLogHandler.java:49)
at org.mortbay.jetty.handler.HandlerWrapper.handle(HandlerWrapper.java:152)
at org.mortbay.jetty.Server.handle(Server.java:324)
at org.mortbay.jetty.HttpConnection.handleRequest(HttpConnection.java:505)
at org.mortbay.jetty.HttpConnection$RequestHandler.content(HttpConnection.java:843)
at org.mortbay.jetty.HttpParser.parseNext(HttpParser.java:647)
at org.mortbay.jetty.HttpParser.parseAvailable(HttpParser.java:211)
at org.mortbay.jetty.HttpConnection.handle(HttpConnection.java:380)
at org.mortbay.io.nio.SelectChannelEndPoint.run(SelectChannelEndPoint.java:395)
at org.mortbay.thread.QueuedThreadPool$PoolThread.run(QueuedThreadPool.java:488)

One other comment. The behaviour hasn’t always been exactly as I describe above. One time the error was a complaint that DMI didn’t support multiple methods with the same name. Not sure what triggered that, since I’ve never had two concrete methods of the same name, but just thought I’d mention that in case it helps you investigate the bug.

Thanks for the report, it’ll be looked into, however, you seem to be constructing a re-usable base class and subclasses around DMI. Usually, if you’ve reached this level of sophistication and re-use in your code, it makes more sense to switch to a Custom DataSource. This allows DMI to be used only for DataSource-specific overrides of generic behaviors.

Thanks, I’ll investigate custom data sources (as time permits).

One correction to something I said before, regarding the workaround. What I described did appear to work once, but seems to be unstable - I think class content is resulting in unpredictable orders of method examination by your reflection class. I ended up having to move the fetch() method to the child class and have no declaration of it in the parent. The common implementation logic was moved to a handleFetch method in the parent class, which SmartGWT would have no reason to notice. Then things worked consistently.

Hmm, there’s nothing non-deterministic in the way the reflection system works. Is it possible these are systems of hot deploy of Java classes without a servlet engine restart?

Yes, there would have been times with a hot deploy. Not all the times, but of the various attempts no doubt a few of them were.

I checked into the custom data source angle, and I think there is probably something worth me pursing. From the method signatures on BasicDataSource I think it could be a good approach… but it is educated guesswork. It appears like you’ve probably put a lot of good thought into those base classes, but you don’t include them in your javadoc. As a suggestion for the future, it would be nice to expand the javadoc to include the com.isomorphic materials.

They’re doc’d, it’s separate, there’s a link in the QuickStart Guide.