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

DatabaseFactory.create() fails with java.lang.InstantiationError: io.ebean.bean.EntityBeanIntercept in multi-module gradle setup #2888

Closed
Incanus3 opened this issue Nov 12, 2022 · 6 comments
Labels
Milestone

Comments

@Incanus3
Copy link
Contributor

Expected behavior

create and return a Database instance

Actual behavior

DatabaseFactory.create() call fails with

Error trying to create the prototypeEntityBean for class cz.sentica.qwazar.ea.core.entities.EaObject
java.lang.IllegalStateException: Error trying to create the prototypeEntityBean for class cz.sentica.qwazar.ea.core.entities.EaObject
	at io.ebeaninternal.server.deploy.BeanDescriptor.createPrototypeEntityBean(BeanDescriptor.java:432)
	at io.ebeaninternal.server.deploy.BeanDescriptor.<init>(BeanDescriptor.java:247)
	at io.ebeaninternal.server.deploy.BeanDescriptorManager.registerDescriptor(BeanDescriptorManager.java:621)
	at io.ebeaninternal.server.deploy.BeanDescriptorManager.readEntityRelationships(BeanDescriptorManager.java:736)
	at io.ebeaninternal.server.deploy.BeanDescriptorManager.deploy(BeanDescriptorManager.java:296)
	at io.ebeaninternal.server.core.InternalConfiguration.<init>(InternalConfiguration.java:129)
	at io.ebeaninternal.server.core.DefaultContainer.createServer(DefaultContainer.java:104)
	at io.ebeaninternal.server.core.DefaultContainer.createServer(DefaultContainer.java:29)
	at io.ebean.DatabaseFactory.createInternal(DatabaseFactory.java:136)
	at io.ebean.DatabaseFactory.create(DatabaseFactory.java:85)
	at cz.sentica.qwazar.ea.db.ObjectConnectorRelationsTest.<init>(ObjectConnectorRelationsTest.kt:37)
	at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
	at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:77)
	at java.base/jdk.internal.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
	at java.base/java.lang.reflect.Constructor.newInstanceWithCaller(Constructor.java:499)
	at java.base/java.lang.reflect.Constructor.newInstance(Constructor.java:480)
	at org.junit.platform.commons.util.ReflectionUtils.newInstance(ReflectionUtils.java:513)
	at org.junit.jupiter.engine.execution.ConstructorInvocation.proceed(ConstructorInvocation.java:56)
	at org.junit.jupiter.engine.execution.InvocationInterceptorChain$ValidatingInvocation.proceed(InvocationInterceptorChain.java:131)
	at org.junit.jupiter.api.extension.InvocationInterceptor.interceptTestClassConstructor(InvocationInterceptor.java:72)
	at org.junit.jupiter.engine.execution.ExecutableInvoker.lambda$invoke$0(ExecutableInvoker.java:105)
	at org.junit.jupiter.engine.execution.InvocationInterceptorChain$InterceptedInvocation.proceed(InvocationInterceptorChain.java:106)
	at org.junit.jupiter.engine.execution.InvocationInterceptorChain.proceed(InvocationInterceptorChain.java:64)
	at org.junit.jupiter.engine.execution.InvocationInterceptorChain.chainAndInvoke(InvocationInterceptorChain.java:45)
	at org.junit.jupiter.engine.execution.InvocationInterceptorChain.invoke(InvocationInterceptorChain.java:37)
	at org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:104)
	at org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:77)
	at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.invokeTestClassConstructor(ClassBasedTestDescriptor.java:342)
	at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.instantiateTestClass(ClassBasedTestDescriptor.java:289)
	at org.junit.jupiter.engine.descriptor.ClassTestDescriptor.instantiateTestClass(ClassTestDescriptor.java:79)
	at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.instantiateAndPostProcessTestInstance(ClassBasedTestDescriptor.java:267)
	at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.lambda$testInstancesProvider$2(ClassBasedTestDescriptor.java:259)
	at java.base/java.util.Optional.orElseGet(Optional.java:364)
	at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.lambda$testInstancesProvider$3(ClassBasedTestDescriptor.java:258)
	at org.junit.jupiter.engine.execution.TestInstancesProvider.getTestInstances(TestInstancesProvider.java:31)
	at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda$prepare$0(TestMethodTestDescriptor.java:101)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.prepare(TestMethodTestDescriptor.java:100)
	at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.prepare(TestMethodTestDescriptor.java:65)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$prepare$1(NodeTestTask.java:111)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.prepare(NodeTestTask.java:111)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:79)
	at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)
	at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:38)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:143)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$7(NodeTestTask.java:129)
	at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:127)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:126)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:84)
	at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)
	at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:38)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:143)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$7(NodeTestTask.java:129)
	at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:127)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:126)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:84)
	at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.submit(SameThreadHierarchicalTestExecutorService.java:32)
	at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor.execute(HierarchicalTestExecutor.java:57)
	at org.junit.platform.engine.support.hierarchical.HierarchicalTestEngine.execute(HierarchicalTestEngine.java:51)
	at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:107)
	at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:88)
	at org.junit.platform.launcher.core.EngineExecutionOrchestrator.lambda$execute$0(EngineExecutionOrchestrator.java:54)
	at org.junit.platform.launcher.core.EngineExecutionOrchestrator.withInterceptedStreams(EngineExecutionOrchestrator.java:67)
	at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:52)
	at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:114)
	at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:86)
	at org.junit.platform.launcher.core.DefaultLauncherSession$DelegatingLauncher.execute(DefaultLauncherSession.java:86)
	at org.junit.platform.launcher.core.SessionPerRequestLauncher.execute(SessionPerRequestLauncher.java:53)
	at org.gradle.api.internal.tasks.testing.junitplatform.JUnitPlatformTestClassProcessor$CollectAllTestClassesExecutor.processAllTestClasses(JUnitPlatformTestClassProcessor.java:99)
	at org.gradle.api.internal.tasks.testing.junitplatform.JUnitPlatformTestClassProcessor$CollectAllTestClassesExecutor.access$000(JUnitPlatformTestClassProcessor.java:79)
	at org.gradle.api.internal.tasks.testing.junitplatform.JUnitPlatformTestClassProcessor.stop(JUnitPlatformTestClassProcessor.java:75)
	at org.gradle.api.internal.tasks.testing.SuiteTestClassProcessor.stop(SuiteTestClassProcessor.java:61)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.base/java.lang.reflect.Method.invoke(Method.java:568)
	at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:36)
	at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:24)
	at org.gradle.internal.dispatch.ContextClassLoaderDispatch.dispatch(ContextClassLoaderDispatch.java:33)
	at org.gradle.internal.dispatch.ProxyDispatchAdapter$DispatchingInvocationHandler.invoke(ProxyDispatchAdapter.java:94)
	at jdk.proxy1/jdk.proxy1.$Proxy2.stop(Unknown Source)
	at org.gradle.api.internal.tasks.testing.worker.TestWorker$3.run(TestWorker.java:193)
	at org.gradle.api.internal.tasks.testing.worker.TestWorker.executeAndMaintainThreadName(TestWorker.java:129)
	at org.gradle.api.internal.tasks.testing.worker.TestWorker.execute(TestWorker.java:100)
	at org.gradle.api.internal.tasks.testing.worker.TestWorker.execute(TestWorker.java:60)
	at org.gradle.process.internal.worker.child.ActionExecutionWorker.execute(ActionExecutionWorker.java:56)
	at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:133)
	at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:71)
	at worker.org.gradle.process.internal.worker.GradleWorkerMain.run(GradleWorkerMain.java:69)
	at worker.org.gradle.process.internal.worker.GradleWorkerMain.main(GradleWorkerMain.java:74)
Caused by: java.lang.reflect.InvocationTargetException
	at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
	at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:77)
	at java.base/jdk.internal.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
	at java.base/java.lang.reflect.Constructor.newInstanceWithCaller(Constructor.java:499)
	at java.base/java.lang.reflect.Constructor.newInstance(Constructor.java:480)
	at io.ebeaninternal.server.deploy.BeanDescriptor.createPrototypeEntityBean(BeanDescriptor.java:430)
	... 96 more
Caused by: java.lang.InstantiationError: io.ebean.bean.EntityBeanIntercept
	at cz.sentica.qwazar.ea.core.entities.EaObject.<init>(EaObject.kt:13)
	at cz.sentica.qwazar.ea.core.entities.EaObject.<init>(EaObject.kt:17)
	at cz.sentica.qwazar.ea.core.entities.EaObject.<init>(EaObject.kt)
	... 102 more
  • it isn't related to any specific entity class - on different runs reports different entities - probably just the first one it encounters
  • works up to ebean version 13.6.4, broken in 13.6.5 and still broken in 13.10.1
  • works fine in single-module setup
  • actually works in idea if I enable the IDE ebean plugin and disable the io.ebean gradle plugin, but than it of course doesn't work from console and fails with BeanNotEnhanced

Steps to reproduce

  • I've created (acually reused from another issue) a minimal repo to reproduce this: https:/Incanus3/ebean-test/tree/multi-module-intercept-error
  • there are two branches right now - master is still in single-module setup and works fine, the multi-module-intercept-error branch switches it to multi-module setup and there it's broken
  • at ea/db/src/test/kotlin/cz/sentica/qwazar/ea/db/ObjectConnectorRelationsTest.kt there's a test to reproduce this
  • I tried to do some debugging, but I only got a few levels deep into jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance() and hit a native method I can't step into
@rbygrave
Copy link
Member

actually works in idea if I enable the IDE ebean plugin and disable the io.ebean gradle plugin

Very likely that the issue is that the gradle plugin version is older than the ebean version.

The gradle plugin version should be equal to or later than the ebean version. If we are using ebean 13.10.1 we should be using that version of the gradle plugin or higher.

We can use the latest gradle plugin with older versions of ebean (it is backwards compatible).

@Incanus3
Copy link
Contributor Author

Incanus3 commented Nov 14, 2022

Hi, thanks for the reply, but I don't think so - as you can see here https:/Incanus3/ebean-test/blob/multi-module-intercept-error/build.gradle.kts on lines 7 and 22, I'm using the same version to define the plugin and the dependency itself. I've also tried to add explicit versions to submodules (although it shouldn't be needed, because it just inherits the plugin version from the parent module) and checked the output of both gradle dependencies and gradle buildEnvironment to see the versions that are actually installed and used and they are both 13.6.5, which is the first version that breaks this.

@Incanus3
Copy link
Contributor Author

Incanus3 commented Nov 14, 2022

I also checked this again with both versions set to 13.10.1 and the result is the same (I mean the new versions are correctly installed and used by gradle, but the instantiation still fails with the same problem).

@jnehlmeier
Copy link

jnehlmeier commented Nov 14, 2022

@Incanus3 Ebean has changed io.ebean.bean.EntityBeanIntercept from a class to an interface starting with 13.6.0 in 5ab0271

I have used your example project and added to the core project src/main/resources/ebean.mf containing synthetic: true. This tells ebean to keep synthetic methods visible in the class file during enhancement.

Then I have decompiled the class file and it indeed shows

public EaConnector(long id, long startObjectId, long endObjectId) {
      this._ebean_intercept = new EntityBeanIntercept(this);
      this._ebean_set_id(id);
      this._ebean_set_startObjectId(startObjectId);
      this._ebean_set_endObjectId(endObjectId);
}

with EntityBeanIntercept in Ebean 13.6.5 being an interface. That is why the exception is thrown.

I also verified ebean versions and Ebean Gradle Plugin and Ebean dependencies are indeed all the same version 13.6.5.

So I skimmed a bit through the code. The Ebean Gradle Plugin calls a class named Transformer by providing a classloader and some Ebean Agent properties. It only passes debug=<some debug level> and that's it. The Transformer class itself then creates an EnhanceContext which is responsible to provide an enhancementVersion. This version is an int and is used by Ebean to determine how to behave while enhancing class files. For example the EnhanceContext class has a method

  public String interceptNew() {
    return enhancementVersion >= 140 ? C_INTERCEPT_RW : C_INTERCEPT_I;
  }

which either returns "io/ebean/bean/InterceptReadWrite" or "io/ebean/bean/EntityBeanIntercept" depending on the version. I guess version 140 changed EntityBeanIntercept from class to interface and thus anything below 140 can use EntityBeanIntercept (since it was a class) and 140+ needs to use the read-write implementation of the EntityBeanIntercept interface (as it is not a class anymore).

However that enhancementVersion is always 0 in your multi-module example project because the Gradle Plugin does not provide the corresponding ebean agent property (version=<enhancement version>) and in that case the code tries to read the version from the jar file manifest. However there does not seem to be a jar manifest file that contain a property ebean-version which the code tries to read.

This situation is also logged to console if you add to your build.gradle.kts file

ebean {
  debugLevel = 5 
}

Then you see enhancement:0 the log line:

ebean-agent version:13.6.5 enhancement:0 resources:[META-INF/ebean-generated-info.mf, ebean.mf]

Now when using the main branch of your repository (the single module version) the log output is

ebean-agent version:13.6.5 enhancement:141 resources:[META-INF/ebean-version.mf, META-INF/ebean-generated-info.mf]

Suddenly there is an ebean-version.mf file showing up. This file comes from ebean-core:13.6.5 and contains ebean-version: 141 which the code can now read successfully.

So it seems like in your multi module build ebean-version.mf somehow gets lost. Looking at ./gradlew :ea:core:dependencies shows

compileClasspath - Compile classpath for compilation 'main' (target  (jvm)).
+--- io.ebean:ebean-api:13.6.5
|    +--- io.avaje:avaje-config:2.1
|    +--- io.avaje:avaje-lang:1.1
|    +--- io.ebean:persistence-api:2.2.5
|    +--- io.ebean:ebean-annotation:8.0
|    +--- io.ebean:ebean-types:2.2
|    \--- io.ebean:ebean-datasource-api:8.0
+--- io.ebean:ebean-querybean:13.6.5
|    \--- io.avaje:avaje-lang:1.1
\--- org.jetbrains.kotlin:kotlin-stdlib:1.7.21
     +--- org.jetbrains.kotlin:kotlin-stdlib-common:1.7.21
     \--- org.jetbrains:annotations:13.0

So you do not have any ebean-core in your dependencies. That is why the ebean agent can not figure out the enhancement version. Adding api("io.ebean:ebean-core:${project.ext["ebeanVersion"]}") to your core build.gradle.kts file fixes the issue.

@rbygrave If the Ebean Gradle Plugin can enhance classes with just ebean-api and ebean-querybean on classpath, then you should put ebean-version.mf into ebean-api instead of ebean-core. Or it must be part of ebean-agent and ebean-agent must always have the same release cycle as the rest of ebean.

@Incanus3
Copy link
Contributor Author

@jnehlmeier that's some great detective work, thank you so much. I can confirm that replacing ebean-api by ebean-core in the ea:core module fixes the issue.

I'm sorry I didn't think of setting the debugLevel to a higher value, but even if I did, I'm not sure I'd have noticed the ebean-agent log line as I had no idea what to look for.

We used the ebean-api (along with the querybean[-generator]) in the core module since it only contained the entities themselves, not any code actually using them (because we actually have two modules on top of these - one that works directly with the EA db and another one which calls an API that wraps it, and then just uses the same entities to transfer the results) and this was the minimal set of dependencies that worked. I don't really understand how the enhancement plugin works, so it didn't occur to me this could be a problem.

Thank you again, this is a great library with an even greater community around it. I can't stop being amazed how quickly all reported problems get resolved. Best of regards.

rbygrave added a commit that referenced this issue Nov 15, 2022
…ror: io.ebean.bean.EntityBeanIntercept in multi-module gradle setup
@rbygrave rbygrave added the bug label Nov 15, 2022
@rbygrave rbygrave added this to the 13.10.2 milestone Nov 15, 2022
@rbygrave
Copy link
Member

@rbygrave When the Ebean Gradle Plugin can enhance classes with just ebean-api and ebean-querybean on classpath, then you should put ebean-version.mf into ebean-api instead of ebean-core.

Yes. We should move ebean-version.mf into ebean-api.

ebean-agent must always have the same release cycle as the rest of ebean

ebean-agent is released with every version of ebean (even if it hasn't changed) but this is not sufficient in that it needs to be backwards compatible (support older versions of ebean).

Or it must be part of ebean-agent and ...

We don't take this option because we want ebean-agent to support older versions of ebean. In particular this means that people can install and use the latest IntelliJ Ebean plugin (which includes the latest ebean-agent) with older versions of ebean going back to version 11.x.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

3 participants