Skip to content

Conversation

@rbygrave
Copy link
Member

@rbygrave rbygrave commented Mar 3, 2025

  • Add Support for List.of() & Collections.emptyList()
  • Add Support for Set.of() & Collections.emptySet()
  • Add Support for Map.of() & Collections.emptyMap()

For example:

@OneToMany(cascade = CascadeType.PERSIST)
List<Contact> contacts = List.of();

- Add Support for List.of() & Collections.emptyList()
- Add Support for Set.of() & Collections.emptySet()
- Add Support for Map.of() & Collections.emptyMap()


// these methods work because the underlying collection is a BeanCollection
bean.listOf().add(new Contact("junk"));
Copy link
Contributor

@rPraml rPraml Mar 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My expectation is, that this should fail.
When initializing with List.of etc. the collection is immutable.
Can we transfer this information to the BeanCollection? (if agent detects immutable initialization, the replaced collection is also immutable from the perspective of the user - not for lazy loading)

BTW. There are other ways to initialize immutable lists:

  • Collections.EMTY_LIST
  • Collections.unmodifiableList(new ArrsyList())
  • Arrays.asList(...)

I think, the agent should fail or at least warn, if an unknown initialization is detectef

Copy link
Member Author

@rbygrave rbygrave Mar 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My expectation is, that this should fail.

That is reasonable. My expectation was that people wouldn't use List.of() at all ...

Can we transfer this information to the BeanCollection?

The initialisation for OneToMany and ManyToMany is removed. So for example, if we detect new ArrayList<>() initialisation for a OneToMany - that bytecode is removed so it is as if it wasn't there at all.

So this PR was done such that initialisation via List.of() ... was detected and removed as if it wasn't there at all.

That is, the only reasons why we'd choose to initialise OneToMany collections in such a way is so that (A) the fields can be final and (B) it gives the impression to people reading the code that the collection will not be null.
... noting the for (B) this is only an impression because ebean will make sure the collection is not null regardless [well except for the new immutable read only stuff that isn't in yet and will throw an exception ... so also there no application code will see a null OneToMany collection].


So that all said, I've pushed another PR just now that detects when initialisation code for OneToMany or ManyToMany isn't supported and throws a new EnhancementException [and for maven we will fail the build at enhancement due when this is detected].

I think, the agent should fail or at least warn, if an unknown initialisation is detect

Yes. I think I've just managed to do that with the latest commit pushed here plus a change to the maven plugin to detect and fail the build for that case.

The outstanding question I think is, should we support List.of() and Collections.emptyList() [where support means that this bytecode is detected and removed just like new ArrayList<>() is].

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is reasonable. My expectation was that people wouldn't use List.of() at all ...

I would never have thought of that ;)

We would need the following use cases:

  • null / new ArrayList() for normal lists
  • a distinction between unsorted sets and sorted sets/maps (we initialize them with LinkedHashSet/LinkedHashMap)

The outstanding question I think is, should we support List.of() and Collections.emptyList()

if we decide to do, I would expect, that the BeanList/BeanSet etc. is also readonly. So BeanList needs a readOnly flag and the agent must call the new constructor new BeanList(underlyingList, true)

I can imagine use cases, where I want to protect the modification of that list by someone else.

but this is more theoretical, so it would be also OK to throw an error in the enhancer

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

a distinction between unsorted sets and sorted sets/maps

I have no intention of ebean supporting unsorted sets/maps. I don't think that is a need / requirement.

I would expect, that the BeanList/BeanSet etc. is also readonly.

I don't think we can easily do that at this point. I don't want to try to get that clever unless we have to.

My suspicion is that I don't think people are thinking deeply about this. I suspect the use of List.of() was more just a bit of an accident.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewing the JPA spec, the initialisation of OneToMany and ManyToMany is vague:

"Collection-valued persistent fields and properties must be defined in terms of one of the following collection-valued interfaces regardless of whether the entity class otherwise adheres to the JavaBeans method conventions noted above and whether field or property access is used: java.util.Collection, java.util.Set, java.util.List [3], java.util.Map. The collection implementation type may be used by the application to initialize fields or properties before the entity is made persistent."

Noting that stack overflow has - https://stackoverflow.com/questions/20715143/to-initialize-or-not-initialize-jpa-relationship-mappings

At this stage, it feels like the safest option would be to not support List.of() and Collections.emptyList() etc as long as we can explicitly fail the enhancement. Noting that there might have "holes" in this detection like an application that ONLY using javaagent style enhancement could miss an error going to system out [but the entity would not be enhanced so it would not be useable as an entity - it's just the error might get misleading].

…sation is detected

maven plugin will also be updated to fail the build when this exception is thrown
…isation for OneToMany etc

The thinking is that many folks are not reading the JPA spec, and there are some folks "suggesting" to initialise OneToMany collections with Collections.emptyList() ... even though it isn't as per JPA spec. Hmmm.
Copy link
Contributor

@rPraml rPraml left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks good to me now. We should not add too much complexity

@rbygrave
Copy link
Member Author

rbygrave commented Mar 4, 2025

Ok cool - I think we can roll with this.

@rbygrave rbygrave merged commit 12cff17 into master Mar 8, 2025
2 checks passed
@rbygrave rbygrave deleted the feature/detect-oneToMany-init-empty branch March 8, 2025 20:03
rbygrave added a commit that referenced this pull request Mar 8, 2025
Previously didn't account for Collections.EMPTY_LIST (which is field bytecode rather than method bytecode). Also account for initialising as an unsupported List, Set, Map implementation like ebeans own BeanList. The expected implementations are ArrayList, LinkedHashSet, LinkedHashMap.
@rbygrave
Copy link
Member Author

rbygrave commented Mar 8, 2025

Also needs PR #221 to capture some non valid cases

rbygrave added a commit that referenced this pull request Mar 8, 2025
For #220, detect unexpected OneToMany initialisation cases
rbygrave added a commit that referenced this pull request Mar 8, 2025
With 220 the ebean-agent is now conservative and only allows certain initialisation for OneToMany. This fixes the 220 change to allow OneToMany to be set via constructor. [PUTFIELD on a OneToMany was proceeded by an ALOAD]
rbygrave added a commit that referenced this pull request Mar 8, 2025
For #220, explicitly allow OneToMany to be set via constructor
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants