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

ClassGeneratingPropertyAccessorFactory needs custom ClassLoader for defineClass() [DATACMNS-1422] #1855

Closed
spring-projects-issues opened this issue Nov 20, 2018 · 5 comments
Assignees
Labels
in: mapping type: bug

Comments

@spring-projects-issues
Copy link

spring-projects-issues commented Nov 20, 2018

Daniel Raap opened DATACMNS-1422 and commented

The ClassGeneratingPropertyAccessorFactory fails to generate classes in an OSGi environment, where the ClassLoader for the model project, the store implementation and Spring Data is different. Here is the error we are facing:

java.lang.RuntimeException: java.lang.IllegalStateException: org.springframework.cglib.core.CodeGenerationException: java.lang.NoClassDefFoundError-->org/springframework/data/mapping/PersistentPropertyAccessor
	at org.springframework.data.mapping.model.ClassGeneratingPropertyAccessorFactory.createAccessorClass(ClassGeneratingPropertyAccessorFactory.java:197)
	at org.springframework.data.mapping.model.ClassGeneratingPropertyAccessorFactory.potentiallyCreateAndRegisterPersistentPropertyAccessorClass(ClassGeneratingPropertyAccessorFactory.java:181)
	at org.springframework.data.mapping.model.ClassGeneratingPropertyAccessorFactory.getPropertyAccessor(ClassGeneratingPropertyAccessorFactory.java:91)
	at org.springframework.data.mapping.model.BasicPersistentEntity.getPropertyAccessor(BasicPersistentEntity.java:455)
	at com.example.storeimpl.CustomEntityConverter.read(Unknown Source)
[...]
Caused by: java.lang.IllegalStateException: org.springframework.cglib.core.CodeGenerationException: java.lang.NoClassDefFoundError-->org/springframework/data/mapping/PersistentPropertyAccessor
	at org.springframework.data.mapping.model.ClassGeneratingPropertyAccessorFactory$PropertyAccessorClassGenerator.generateCustomAccessorClass(ClassGeneratingPropertyAccessorFactory.java:323)
	at org.springframework.data.mapping.model.ClassGeneratingPropertyAccessorFactory.createAccessorClass(ClassGeneratingPropertyAccessorFactory.java:195)
	... 61 common frames omitted
Caused by: org.springframework.cglib.core.CodeGenerationException: java.lang.NoClassDefFoundError-->org/springframework/data/mapping/PersistentPropertyAccessor
	at org.springframework.cglib.core.ReflectUtils.defineClass(ReflectUtils.java:526)
	at org.springframework.data.mapping.model.ClassGeneratingPropertyAccessorFactory$PropertyAccessorClassGenerator.generateCustomAccessorClass(ClassGeneratingPropertyAccessorFactory.java:321)
	... 62 common frames omitted
Caused by: java.lang.NoClassDefFoundError: org/springframework/data/mapping/PersistentPropertyAccessor
	at java.lang.ClassLoader.defineClass1(Native Method)
	at java.lang.ClassLoader.defineClass(Unknown Source)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
	at java.lang.reflect.Method.invoke(Unknown Source)
	at org.springframework.cglib.core.ReflectUtils.defineClass(ReflectUtils.java:523)
	... 63 common frames omitted
Caused by: java.lang.ClassNotFoundException: org.springframework.data.mapping.PersistentPropertyAccessor cannot be found by custom-model_1.2.3
	at org.eclipse.osgi.internal.loader.BundleLoader.findClassInternal(BundleLoader.java:511)
	at org.eclipse.osgi.internal.loader.BundleLoader.findClass(BundleLoader.java:422)
	at org.eclipse.osgi.internal.loader.BundleLoader.findClass(BundleLoader.java:414)
	at org.eclipse.osgi.internal.loader.ModuleClassLoader.loadClass(ModuleClassLoader.java:153)
	at java.lang.ClassLoader.loadClass(Unknown Source)
	... 70 common frames omitted

The problem seems to be that PropertyAccessorClassGenerator.generateBytecode() adds the interface PersistentPropertyAccessor which lives in Spring Data. In generateCustomAccessorClass() the ClassLoader of the model entity is used. Therefore the ClassLoader of the custom model project needs access to all classes which are added by the factory (especially the package org.springframework.data.mapping).

To resolve this problem, a child ClassLoader of the entity should be used that is able to access both projects: Spring Data and the custom entity model. Else this has to fail because different classes are mixed which cannot be accessed by a single ClassLoader


Affects: 2.1.2 (Lovelace SR2)

Attachments:

Referenced from: pull request #324

Backported to: 2.1.3 (Lovelace SR3)

@spring-projects-issues
Copy link
Author

spring-projects-issues commented Nov 21, 2018

Mark Paluch commented

I'm not sure this will work. As mentioned, we would require a different class loader that then requires a separate protection domain. This has implications for SecurityManager use and brings a limitation regarding package protected visibility. Types that are package-protected can only be accessed by classes residing the same package (accessing class must be loaded in the same class loader) as ClassGeneratingPropertyAccessorFactory isn't using reflection.

We face the same issue with class-generated entity instantiators as we generate another set of classes that implements org.springframework.data.convert.ClassGeneratingEntityInstantiator.ObjectInstantiator.  

 

Can you provide a minimal project to reproduce the issue so we can investigate on available options?

@spring-projects-issues
Copy link
Author

spring-projects-issues commented Nov 22, 2018

Daniel Raap commented

Can you provide a minimal project to reproduce the issue so we can investigate on available options?
I added an example where our use case is copied almost 1:1. So it is not minimal, but I hope small enough. It is kept simple, so no annotations are used. Because we access Spring Data from outside of a Spring Context this example is also very light on Spring usage.

[^bug-DATACMNS-1422.zip] contains a runnable application. I added scripts to simply start the application. There are three cases:

  • start-java directly calls the main method of Main and everything runs with a single class loader.
  • start-osgi uses the Felix framework distribution as platform. Here the Activator executes the example and provokes the execption. (Ignore "An illegal reflective access operation has occurred" if using Java 9+)
  • start-osgi-with-workaround does the same as start-osgi, but sets a property to prevent the usage of ClassGeneratingPropertyAccessorFactory. The workaround is implemented in SimpleCsvPersistentEntity.setPersistentPropertyAccessorFactory().

The dependencies are also contained in the folder bundle. The org.springframework.*.jar files were created by us. They are OSGi bundles that were build with the Maven-Plugin org.reficio:p2-maven-plugin.

[^example-parent.zip] contains the source, which can be build with Maven. The POMs will generate a MANIFEST.MF suitable for OSGi.

I removed the OSGi console of Felix from the bundles to reduce the size and because they are not necessary for the example. But for debugging the exported and imported packages, they may be helpful to inspect the state. Simple copy them from the vanilla Felix distribution.

Thanks in advance

@spring-projects-issues
Copy link
Author

spring-projects-issues commented Nov 23, 2018

Mark Paluch commented

Thanks. OSGi bears some complexity to get things running. We can guard our code by checking whether our types are accessible through the entity's ClassLoader and then fall back to reflection-based entity instantiation/property accessors

@spring-projects-issues
Copy link
Author

spring-projects-issues commented Nov 23, 2018

Mark Paluch commented

I pushed a change to our feature branch for this ticket and we have a branch build that has produced an artifact with version 2.2.0.DATACMNS-1422-SNAPSHOT. Care to upgrade and see whether the change fixes your issue?

Make sure to include our snapshot repository:

<dependencies>
  <dependency>
    <groupId>org.springframework.data</groupId>
    <artifactId>spring-data-commons</artifactId>
    <version>2.2.0.DATACMNS-1422-SNAPSHOT</version>
  </dependency>
</dependencies>

<repositories>
  <repository>
    <id>spring-libs-snapshot</id>
    <url>https://repo.spring.io/libs-snapshot</url>
    <snapshots>
      <enabled>true</enabled>
    </snapshots>
  </repository>
</repositories>

@spring-projects-issues
Copy link
Author

spring-projects-issues commented Nov 23, 2018

Daniel Raap commented

Thx for the quick fixing!

We can guard our code by checking whether our types are accessible through the entity's ClassLoader and then fall back to reflection-based entity instantiation/property accessors.
Sounds good.

Care to upgrade and see whether the change fixes your issue?
I simply replaced the previous JAR with the new snapshot and replaced the MANIFEST.MF. My example now just works!

@spring-projects-issues spring-projects-issues added type: bug in: mapping labels Dec 30, 2020
@spring-projects-issues spring-projects-issues added this to the 2.2 M1 (Moore) milestone Dec 30, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
in: mapping type: bug
Projects
None yet
Development

No branches or pull requests

2 participants