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

Auditing triggers unintended persistent entity creation for collection like types (List, Set,...) #3056

Closed
mahdibohloul opened this issue Mar 2, 2024 · 3 comments
Assignees
Labels
type: bug A general bug

Comments

@mahdibohloul
Copy link

I have defined a Kotlin class that contains a field such as Map<Enum, HashSet<Something>> and I can save it in the database. Now when I use the @EnableReactiveMongoAuditing annotation, I encounter the following error:

java.lang.reflect.InaccessibleObjectException: Unable to make field private transient java.util.HashMap java.util.HashSet.map accessible: module java.base does not "opens java.util" to unnamed module @661972b0
	at java.base/java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java:354) ~[na:na]
	at java.base/java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java:297) ~[na:na]
	at java.base/java.lang.reflect.Field.checkCanSetAccessible(Field.java:178) ~[na:na]
	at java.base/java.lang.reflect.Field.setAccessible(Field.java:172) ~[na:na]
	at org.springframework.util.ReflectionUtils.makeAccessible(ReflectionUtils.java:804) ~[spring-core-6.1.4.jar:6.1.4]
	at org.springframework.data.mapping.context.AbstractMappingContext$PersistentPropertyCreator.doWith(AbstractMappingContext.java:540) ~[spring-data-commons-3.2.3.jar:3.2.3]
	at org.springframework.util.ReflectionUtils.doWithFields(ReflectionUtils.java:728) ~[spring-core-6.1.4.jar:6.1.4]
	at org.springframework.data.mapping.context.AbstractMappingContext.doAddPersistentEntity(AbstractMappingContext.java:426) ~[spring-data-commons-3.2.3.jar:3.2.3]
	at org.springframework.data.mapping.context.AbstractMappingContext.addPersistentEntity(AbstractMappingContext.java:383) ~[spring-data-commons-3.2.3.jar:3.2.3]
	at org.springframework.data.mapping.context.AbstractMappingContext.getPersistentEntity(AbstractMappingContext.java:279) ~[spring-data-commons-3.2.3.jar:3.2.3]
	at org.springframework.data.mapping.context.AbstractMappingContext.getPersistentEntity(AbstractMappingContext.java:293) ~[spring-data-commons-3.2.3.jar:3.2.3]
	at org.springframework.data.mongodb.core.mapping.MongoMappingContext.getPersistentEntity(MongoMappingContext.java:132) ~[spring-data-mongodb-4.2.3.jar:4.2.3]
	at org.springframework.data.mongodb.core.mapping.MongoMappingContext.getPersistentEntity(MongoMappingContext.java:41) ~[spring-data-mongodb-4.2.3.jar:4.2.3]
	at org.springframework.data.mapping.context.MappingContext.getRequiredPersistentEntity(MappingContext.java:147) ~[spring-data-commons-3.2.3.jar:3.2.3]
	at org.springframework.data.mapping.context.PersistentPropertyPathFactory.lambda$from$2(PersistentPropertyPathFactory.java:259) ~[spring-data-commons-3.2.3.jar:3.2.3]
	at org.springframework.data.mapping.model.BasicPersistentEntity.doWithProperties(BasicPersistentEntity.java:298) ~[spring-data-commons-3.2.3.jar:3.2.3]
	at org.springframework.data.mapping.context.PersistentPropertyPathFactory.from(PersistentPropertyPathFactory.java:264) ~[spring-data-commons-3.2.3.jar:3.2.3]
	at org.springframework.data.mapping.context.PersistentPropertyPathFactory.lambda$from$2(PersistentPropertyPathFactory.java:260) ~[spring-data-commons-3.2.3.jar:3.2.3]
	at org.springframework.data.mapping.model.BasicPersistentEntity.doWithProperties(BasicPersistentEntity.java:298) ~[spring-data-commons-3.2.3.jar:3.2.3]
	at org.springframework.data.mapping.context.PersistentPropertyPathFactory.from(PersistentPropertyPathFactory.java:264) ~[spring-data-commons-3.2.3.jar:3.2.3]
	at org.springframework.data.mapping.context.PersistentPropertyPathFactory.from(PersistentPropertyPathFactory.java:235) ~[spring-data-commons-3.2.3.jar:3.2.3]
	at org.springframework.data.mapping.context.PersistentPropertyPathFactory.from(PersistentPropertyPathFactory.java:166) ~[spring-data-commons-3.2.3.jar:3.2.3]
	at org.springframework.data.mapping.context.AbstractMappingContext.doFindPersistentPropertyPaths(AbstractMappingContext.java:333) ~[spring-data-commons-3.2.3.jar:3.2.3]
	at org.springframework.data.mapping.context.AbstractMappingContext.findPersistentPropertyPaths(AbstractMappingContext.java:317) ~[spring-data-commons-3.2.3.jar:3.2.3]
	at org.springframework.data.auditing.MappingAuditableBeanWrapperFactory$MappingAuditingMetadata.findPropertyPaths(MappingAuditableBeanWrapperFactory.java:150) ~[spring-data-commons-3.2.3.jar:3.2.3]
	at org.springframework.data.auditing.MappingAuditableBeanWrapperFactory$MappingAuditingMetadata.<init>(MappingAuditableBeanWrapperFactory.java:124) ~[spring-data-commons-3.2.3.jar:3.2.3]
	at org.springframework.data.auditing.MappingAuditableBeanWrapperFactory.lambda$getBeanWrapperFor$0(MappingAuditableBeanWrapperFactory.java:85) ~[spring-data-commons-3.2.3.jar:3.2.3]
	at java.base/java.util.concurrent.ConcurrentMap.computeIfAbsent(ConcurrentMap.java:330) ~[na:na]
	at org.springframework.data.auditing.MappingAuditableBeanWrapperFactory.lambda$getBeanWrapperFor$1(MappingAuditableBeanWrapperFactory.java:84) ~[spring-data-commons-3.2.3.jar:3.2.3]
	at org.springframework.data.mapping.context.PersistentEntities.lambda$mapOnContext$2(PersistentEntities.java:144) ~[spring-data-commons-3.2.3.jar:3.2.3]
	at java.base/java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:197) ~[na:na]
	at java.base/java.util.stream.ReferencePipeline$2$1.accept(ReferencePipeline.java:179) ~[na:na]
	at java.base/java.util.Spliterators$ArraySpliterator.tryAdvance(Spliterators.java:1002) ~[na:na]
	at java.base/java.util.stream.ReferencePipeline.forEachWithCancel(ReferencePipeline.java:129) ~[na:na]
	at java.base/java.util.stream.AbstractPipeline.copyIntoWithCancel(AbstractPipeline.java:527) ~[na:na]
	at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:513) ~[na:na]
	at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:499) ~[na:na]
	at java.base/java.util.stream.FindOps$FindOp.evaluateSequential(FindOps.java:150) ~[na:na]
	at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234) ~[na:na]
	at java.base/java.util.stream.ReferencePipeline.findFirst(ReferencePipeline.java:647) ~[na:na]
	at org.springframework.data.mapping.context.PersistentEntities.mapOnContext(PersistentEntities.java:145) ~[spring-data-commons-3.2.3.jar:3.2.3]
	at org.springframework.data.auditing.MappingAuditableBeanWrapperFactory.lambda$getBeanWrapperFor$3(MappingAuditableBeanWrapperFactory.java:82) ~[spring-data-commons-3.2.3.jar:3.2.3]
	at java.base/java.util.Optional.flatMap(Optional.java:289) ~[na:na]
	at org.springframework.data.auditing.MappingAuditableBeanWrapperFactory.getBeanWrapperFor(MappingAuditableBeanWrapperFactory.java:76) ~[spring-data-commons-3.2.3.jar:3.2.3]
	at org.springframework.data.auditing.AuditingHandlerSupport.isAuditable(AuditingHandlerSupport.java:101) ~[spring-data-commons-3.2.3.jar:3.2.3]
	at org.springframework.data.auditing.ReactiveIsNewAwareAuditingHandler.markAudited(ReactiveIsNewAwareAuditingHandler.java:71) ~[spring-data-commons-3.2.3.jar:3.2.3]
	at org.springframework.data.mongodb.core.mapping.event.ReactiveAuditingEntityCallback.onBeforeConvert(ReactiveAuditingEntityCallback.java:52) ~[spring-data-mongodb-4.2.3.jar:4.2.3]
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na]
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77) ~[na:na]
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]
	at java.base/java.lang.reflect.Method.invoke(Method.java:568) ~[na:na]
	at org.springframework.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:281) ~[spring-core-6.1.4.jar:6.1.4]
	at org.springframework.data.mapping.callback.EntityCallbackDiscoverer.lambda$computeCallbackInvokerFunction$1(EntityCallbackDiscoverer.java:258) ~[spring-data-commons-3.2.3.jar:3.2.3]
	at org.springframework.data.mapping.callback.DefaultReactiveEntityCallbacks$DefaultReactiveEntityCallbackInvoker.invokeCallback(DefaultReactiveEntityCallbacks.java:110) ~[spring-data-commons-3.2.3.jar:3.2.3]
	at org.springframework.data.mapping.callback.DefaultReactiveEntityCallbacks.lambda$callback$1(DefaultReactiveEntityCallbacks.java:91) ~[spring-data-commons-3.2.3.jar:3.2.3]
	at reactor.core.publisher.FluxFlatMap.trySubscribeScalarMap(FluxFlatMap.java:153) ~[reactor-core-3.6.3.jar:3.6.3]
	at reactor.core.publisher.MonoFlatMap.subscribeOrReturn(MonoFlatMap.java:53) ~[reactor-core-3.6.3.jar:3.6.3]
	at reactor.core.publisher.InternalMonoOperator.subscribe(InternalMonoOperator.java:63) ~[reactor-core-3.6.3.jar:3.6.3]
	at reactor.core.publisher.MonoFlatMap$FlatMapMain.onNext(MonoFlatMap.java:165) ~[reactor-core-3.6.3.jar:3.6.3]
	at reactor.core.publisher.FluxPeekFuseable$PeekFuseableSubscriber.onNext(FluxPeekFuseable.java:210) ~[reactor-core-3.6.3.jar:3.6.3]
	at reactor.core.publisher.Operators$ScalarSubscription.request(Operators.java:2571) ~[reactor-core-3.6.3.jar:3.6.3]
	at reactor.core.publisher.FluxPeekFuseable$PeekFuseableSubscriber.request(FluxPeekFuseable.java:144) ~[reactor-core-3.6.3.jar:3.6.3]
	at reactor.core.publisher.MonoFlatMap$FlatMapMain.request(MonoFlatMap.java:194) ~[reactor-core-3.6.3.jar:3.6.3]
	at reactor.core.publisher.FluxMapFuseable$MapFuseableSubscriber.request(FluxMapFuseable.java:171) ~[reactor-core-3.6.3.jar:3.6.3]
	at reactor.core.publisher.MonoFlatMap$FlatMapMain.request(MonoFlatMap.java:194) ~[reactor-core-3.6.3.jar:3.6.3]
	at reactor.core.publisher.MonoFlatMap$FlatMapMain.request(MonoFlatMap.java:194) ~[reactor-core-3.6.3.jar:3.6.3]
	at reactor.core.publisher.FluxUsingWhen$UsingWhenSubscriber.request(FluxUsingWhen.java:322) ~[reactor-core-3.6.3.jar:3.6.3]
	at reactor.core.publisher.FluxPeekFuseable$PeekConditionalSubscriber.request(FluxPeekFuseable.java:783) ~[reactor-core-3.6.3.jar:3.6.3]
	at reactor.core.publisher.MonoPeekTerminal$MonoTerminalPeekSubscriber.request(MonoPeekTerminal.java:139) ~[reactor-core-3.6.3.jar:3.6.3]
	at reactor.core.publisher.BlockingSingleSubscriber.onSubscribe(BlockingSingleSubscriber.java:53) ~[reactor-core-3.6.3.jar:3.6.3]
	at reactor.core.publisher.MonoPeekTerminal$MonoTerminalPeekSubscriber.onSubscribe(MonoPeekTerminal.java:152) ~[reactor-core-3.6.3.jar:3.6.3]
	at reactor.core.publisher.FluxPeekFuseable$PeekConditionalSubscriber.onSubscribe(FluxPeekFuseable.java:816) ~[reactor-core-3.6.3.jar:3.6.3]
	at reactor.core.publisher.FluxUsingWhen$UsingWhenSubscriber.onSubscribe(FluxUsingWhen.java:411) ~[reactor-core-3.6.3.jar:3.6.3]
	at reactor.core.publisher.MonoFlatMap$FlatMapMain.onSubscribe(MonoFlatMap.java:117) ~[reactor-core-3.6.3.jar:3.6.3]
	at reactor.core.publisher.MonoFlatMap$FlatMapMain.onSubscribe(MonoFlatMap.java:117) ~[reactor-core-3.6.3.jar:3.6.3]
	at reactor.core.publisher.FluxMapFuseable$MapFuseableSubscriber.onSubscribe(FluxMapFuseable.java:96) ~[reactor-core-3.6.3.jar:3.6.3]
	at reactor.core.publisher.MonoFlatMap$FlatMapMain.onSubscribe(MonoFlatMap.java:117) ~[reactor-core-3.6.3.jar:3.6.3]
	at reactor.core.publisher.FluxPeekFuseable$PeekFuseableSubscriber.onSubscribe(FluxPeekFuseable.java:178) ~[reactor-core-3.6.3.jar:3.6.3]
	at reactor.core.publisher.MonoJust.subscribe(MonoJust.java:55) ~[reactor-core-3.6.3.jar:3.6.3]
	at reactor.core.publisher.InternalMonoOperator.subscribe(InternalMonoOperator.java:76) ~[reactor-core-3.6.3.jar:3.6.3]
	at reactor.core.publisher.MonoUsingWhen.subscribe(MonoUsingWhen.java:87) ~[reactor-core-3.6.3.jar:3.6.3]
	at reactor.core.publisher.Mono.subscribe(Mono.java:4563) ~[reactor-core-3.6.3.jar:3.6.3]
	at reactor.core.publisher.Mono.block(Mono.java:1778) ~[reactor-core-3.6.3.jar:3.6.3]
	at cab.tapsi.springmongotest.ApplicationRunner.run(ApplicationRunner.kt:44) ~[main/:na]
	at org.springframework.boot.SpringApplication.lambda$callRunner$5(SpringApplication.java:790) ~[spring-boot-3.2.3.jar:3.2.3]
	at org.springframework.util.function.ThrowingConsumer$1.acceptWithException(ThrowingConsumer.java:83) ~[spring-core-6.1.4.jar:6.1.4]
	at org.springframework.util.function.ThrowingConsumer.accept(ThrowingConsumer.java:60) ~[spring-core-6.1.4.jar:6.1.4]
	at org.springframework.util.function.ThrowingConsumer$1.accept(ThrowingConsumer.java:88) ~[spring-core-6.1.4.jar:6.1.4]
	at org.springframework.boot.SpringApplication.callRunner(SpringApplication.java:798) ~[spring-boot-3.2.3.jar:3.2.3]
	at org.springframework.boot.SpringApplication.callRunner(SpringApplication.java:789) ~[spring-boot-3.2.3.jar:3.2.3]
	at org.springframework.boot.SpringApplication.lambda$callRunners$3(SpringApplication.java:774) ~[spring-boot-3.2.3.jar:3.2.3]
	at java.base/java.util.stream.ForEachOps$ForEachOp$OfRef.accept(ForEachOps.java:183) ~[na:na]
	at java.base/java.util.stream.SortedOps$SizedRefSortingSink.end(SortedOps.java:357) ~[na:na]
	at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:510) ~[na:na]
	at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:499) ~[na:na]
	at java.base/java.util.stream.ForEachOps$ForEachOp.evaluateSequential(ForEachOps.java:150) ~[na:na]
	at java.base/java.util.stream.ForEachOps$ForEachOp$OfRef.evaluateSequential(ForEachOps.java:173) ~[na:na]
	at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234) ~[na:na]
	at java.base/java.util.stream.ReferencePipeline.forEach(ReferencePipeline.java:596) ~[na:na]
	at org.springframework.boot.SpringApplication.callRunners(SpringApplication.java:774) ~[spring-boot-3.2.3.jar:3.2.3]
	at org.springframework.boot.SpringApplication.run(SpringApplication.java:341) ~[spring-boot-3.2.3.jar:3.2.3]
	at org.springframework.boot.SpringApplication.run(SpringApplication.java:1354) ~[spring-boot-3.2.3.jar:3.2.3]
	at org.springframework.boot.SpringApplication.run(SpringApplication.java:1343) ~[spring-boot-3.2.3.jar:3.2.3]
	at cab.tapsi.springmongotest.SpringMongoTestApplicationKt.main(SpringMongoTestApplication.kt:15) ~[main/:an]

The classes are defined as follows:

@Document(collection = "stores")
data class Store(
  @Id val id: String? = null,
  @Indexed(unique = true) val slug: String,
  @Indexed(unique = true) val ownerId: String,
  val name: String,
  val preferences: StorePreferences,
  @CreatedDate val createdTimestamp: Timestamp? = null,
  @LastModifiedDate val updatedTimestamp: Timestamp? = null,
)


data class StorePreferences(
  val workingTimeslots: Map<DayOfWeek, HashSet<TimeslotOffset>> = emptyMap(),
  val processingDurationInSeconds: Long = 0,
)

data class TimeslotOffset(
  val startOffset: Long,
  val endOffset: Long,
)

Also, in this link, I have created a project where you can reproduce the error. Also, you can see the correct way of working by removing the @EnableReactiveMongoAuditing annotation.

This problem arose after upgrading Spring Boot from version 2.5.X to version 3.2.3.
Java version:

openjdk version "17.0.9" 2023-10-17
OpenJDK Runtime Environment (build 17.0.9+9-Ubuntu-122.04)
OpenJDK 64-Bit Server VM (build 17.0.9+9-Ubuntu-122.04, mixed mode, sharing)
@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged label Mar 2, 2024
@mp911de mp911de added type: bug A general bug and removed status: waiting-for-triage An issue we've not yet triaged labels Mar 4, 2024
@christophstrobl
Copy link
Member

The problem we're seeing is actually created by having the PersistentPropertyPathFactory rely on the result of PersistentProperty#isEntity() when inspecting property candidates for path creation while the MappingContext itself may conclude differently. Further more, the MappingContext used in data-mongodb, overrides shouldCreatePersistentEntityFor with a custom flavour that on the one hand does recognise Map as non entity type, but happily returns true for collection like types.
So if we'd only apply a fix for the MappingContext in data-mongodb, it would still break as the code within commons would still check the property and then request an entity from the context that does not exits.
Going forward we'll fix this as part of a service release for data-mongodb covering the property and mapping context part. For the next minor the fix for the PersistentPropertyPathFactory should move to data-commons.

@christophstrobl christophstrobl changed the title Error in saving an entity that has a HashSet field when the @EnableReactiveMongoAuditing annotation is active Auditing triggers unintended persistent entity creation for collection like types (List, Set,...) Mar 5, 2024
@christophstrobl christophstrobl self-assigned this Mar 5, 2024
christophstrobl pushed a commit that referenced this issue Mar 8, 2024
…ies.

These types are expected to behave like maps and collections and should not carry properties.
The only exception are types implementing Streamable as Streamable can be used in domain types.

Resolves: #3056
Original Pull Request: #3059
@christophstrobl christophstrobl added this to the 3.2.4 (2023.1.4) milestone Mar 8, 2024
@igorg-il
Copy link

Hello
We encountered the same issue
Is there an expected release date for the fix?
thank you

@christophstrobl
Copy link
Member

@igorg-il please have a look at the release calendar.

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

No branches or pull requests

5 participants