Skip to content

Conversation

@SailReal
Copy link
Member

@SailReal SailReal commented Jun 6, 2025

Under certain circumstances, where a large number of modifying processes take place in a transaction, e.g. 10 groups are deleted, 10 groups are created, these new groups are directly assigned to many users, it can happen that the syncer aborts with the following exception:

java.lang.IllegalStateException: org.hibernate.TransientObjectException: persistent instance references an unsaved transient instance of ‘org.cryptomator.hub.entities.Authority’ (save the transient instance before flushing)

Since the tansaction is canceled here, the same error occurs again during the next sync process.

The reason for this is that we mix managed (which already exist in the DB) and unmanaged (which we map from Keycloak directly into entities) Hibernate entities and from a certain complexity, the Hibernate session management no longer gets this cleanly assigned, especially if an object with the same ID exists as both managed and unmanaged.

Under certain circumstances, where a large number of modifying processes take place in a transaction, e.g. 10 groups are deleted, 10 groups are created, these new groups are directly assigned to many new users, it can happen that the syncer aborts with the following exception:

java.lang.IllegalStateException: org.hibernate.TransientObjectException: persistent instance references an unsaved transient instance of ‘org.cryptomator.hub.entities.Authority’ (save the transient instance before flushing)

Since the tansaction is canceled here, the same error occurs again during the next sync process.

The reason for this is that we mix managed (which already exist in the DB) and unmanaged (which we map from Keycloak directly into entities) Hibernate entities and from a certain complexity, the Hibernate session management no longer gets this cleanly assigned, especially if an object with the same ID exists as both managed and unmanaged.
@SailReal SailReal added this to the 1.4.2 milestone Jun 6, 2025
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jun 6, 2025

Walkthrough

This change refactors and restructures the synchronization of user and group data between a remote Keycloak server and a local database. The previous interface (RemoteUserProvider), synchronization component (RemoteUserPuller), and related test class are removed. In their place, new DTO records (KeycloakUserDto, KeycloakGroupDto), a provider (KeycloakAuthorityProvider), and a puller (KeycloakAuthorityPuller) are introduced, all focused on using DTOs instead of domain entities. The cascade behavior in the Group entity is updated from REMOVE to MERGE and PERSIST. The KeycloakClientProducer class is renamed and moved to a new package. Corresponding test classes are added or updated to reflect these changes, with all logic and naming aligned to the new DTO-based approach.

Suggested reviewers

  • overheadhunter

📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d08d58f and 9033d63.

📒 Files selected for processing (1)
  • backend/src/main/java/org/cryptomator/hub/keycloak/KeycloakAuthorityPuller.java (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • backend/src/main/java/org/cryptomator/hub/keycloak/KeycloakAuthorityPuller.java
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: Run Tests
✨ Finishing Touches
  • 📝 Generate Docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🧹 Nitpick comments (2)
backend/src/main/java/org/cryptomator/hub/keycloak/KeycloakAuthorityProvider.java (1)

82-82: Address the TODO comment for sub-group handling.

The comment indicates that sub-groups and their members need to be included recursively. This could be important for complete group synchronization.

Would you like me to generate the implementation for recursive sub-group collection or open an issue to track this enhancement?

backend/src/main/java/org/cryptomator/hub/keycloak/KeycloakAuthorityPuller.java (1)

25-25: Rename field to match the injected type.

The field name remoteUserProvider doesn't match the injected type KeycloakAuthorityProvider. Consider renaming for consistency.

-	KeycloakAuthorityProvider remoteUserProvider;
+	KeycloakAuthorityProvider keycloakAuthorityProvider;

Also update the references in lines 29-30:

-		var keycloakGroups = remoteUserProvider.groups().stream().collect(Collectors.toMap(KeycloakGroupDto::id, Function.identity()));
-		var keycloakUsers = remoteUserProvider.users().stream().collect(Collectors.toMap(KeycloakUserDto::id, Function.identity()));
+		var keycloakGroups = keycloakAuthorityProvider.groups().stream().collect(Collectors.toMap(KeycloakGroupDto::id, Function.identity()));
+		var keycloakUsers = keycloakAuthorityProvider.users().stream().collect(Collectors.toMap(KeycloakUserDto::id, Function.identity()));
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 026ef5c and d08d58f.

📒 Files selected for processing (11)
  • backend/src/main/java/org/cryptomator/hub/RemoteUserProvider.java (0 hunks)
  • backend/src/main/java/org/cryptomator/hub/RemoteUserPuller.java (0 hunks)
  • backend/src/main/java/org/cryptomator/hub/entities/Group.java (1 hunks)
  • backend/src/main/java/org/cryptomator/hub/keycloak/KeycloakAuthorityProvider.java (6 hunks)
  • backend/src/main/java/org/cryptomator/hub/keycloak/KeycloakAuthorityPuller.java (1 hunks)
  • backend/src/main/java/org/cryptomator/hub/keycloak/KeycloakClientProducer.java (2 hunks)
  • backend/src/main/java/org/cryptomator/hub/keycloak/KeycloakGroupDto.java (1 hunks)
  • backend/src/main/java/org/cryptomator/hub/keycloak/KeycloakUserDto.java (1 hunks)
  • backend/src/test/java/org/cryptomator/hub/RemoteUserPullerTest.java (0 hunks)
  • backend/src/test/java/org/cryptomator/hub/keycloak/KeycloakAuthorityPullerTest.java (1 hunks)
  • backend/src/test/java/org/cryptomator/hub/keycloak/KeycloakUserProviderTest.java (7 hunks)
💤 Files with no reviewable changes (3)
  • backend/src/main/java/org/cryptomator/hub/RemoteUserProvider.java
  • backend/src/test/java/org/cryptomator/hub/RemoteUserPullerTest.java
  • backend/src/main/java/org/cryptomator/hub/RemoteUserPuller.java
🔇 Additional comments (10)
backend/src/main/java/org/cryptomator/hub/keycloak/KeycloakUserDto.java (1)

1-3: LGTM! Clean DTO design aligns with PR objectives.

This record effectively separates data transfer concerns from entity management, which should help resolve the TransientObjectException by avoiding direct mixing of managed and unmanaged Hibernate entities during synchronization.

backend/src/main/java/org/cryptomator/hub/keycloak/KeycloakGroupDto.java (1)

1-5: LGTM! Consistent DTO pattern with proper member composition.

The use of Set<KeycloakUserDto> for members maintains the DTO approach consistently throughout the synchronization process, which aligns well with the goal of separating remote data representation from entity management.

backend/src/main/java/org/cryptomator/hub/keycloak/KeycloakClientProducer.java (2)

1-1: LGTM! Improved organization with better package structure.

The move to the org.cryptomator.hub.keycloak package logically groups Keycloak-related components together, improving maintainability.


12-12:

Details

✅ Verification successful

Verify that dependent classes have been updated for the rename.

The class rename from KeycloakProducer to KeycloakClientProducer may require updates to dependent classes that inject or reference this component.


🏁 Script executed:

#!/bin/bash
# Description: Search for references to the old class name to ensure all dependencies are updated.
# Expected: No references to "KeycloakProducer" should remain in the codebase.

echo "Searching for references to old class name 'KeycloakProducer'..."
rg "KeycloakProducer" --type java

echo -e "\nSearching for imports of the old package..."
rg "import.*hub\.KeycloakProducer" --type java

Length of output: 330


All references to KeycloakProducer removed

The rename to KeycloakClientProducer appears to be complete—no occurrences of the old class name were found in the codebase. No further updates are needed.

backend/src/main/java/org/cryptomator/hub/entities/Group.java (2)

22-22: Excellent cascade type change that addresses the core PR issue.

Changing from CascadeType.REMOVE to {CascadeType.MERGE, CascadeType.PERSIST} directly addresses the TransientObjectException by:

  • MERGE: Properly handles existing managed entities during updates
  • PERSIST: Safely persists new unmanaged entities
  • Removing REMOVE: Prevents cascading deletions that could interfere with synchronization

This change enables the new DTO-based synchronization to safely manage both existing and new group memberships.


22-27:

Details

✅ Verification successful

Verify that group deletion logic still works correctly.

With the removal of CascadeType.REMOVE, ensure that group deletion scenarios are handled properly in the application logic, as members will no longer be automatically removed when a group is deleted.


🏁 Script executed:

#!/bin/bash
# Description: Search for group deletion logic to verify it properly handles member cleanup.
# Expected: Find explicit member handling in group deletion code.

echo "Searching for group deletion methods..."
ast-grep --pattern 'delete($$$) {
  $$$
}'

echo -e "\nSearching for group removal/deletion logic..."
rg -A 10 -B 2 "delete.*[Gg]roup|remove.*[Gg]roup" --type java

echo -e "\nSearching for group repository delete methods..."
rg -A 5 "Group.*Repository.*delete|delete.*Group" --type java

Length of output: 27881


No additional cleanup needed for group deletions
Removing CascadeType.REMOVE simply stops JPA from deleting Authority entities when a Group is removed—JPA still issues the necessary DELETE on the group_membership join table whenever you call groupRepo.delete(group). The call to groupRepo.delete(databaseGroup) in KeycloakAuthorityPuller.syncDeletedGroups continues to clean up memberships correctly, and existing integration tests pass without regression.

backend/src/test/java/org/cryptomator/hub/keycloak/KeycloakUserProviderTest.java (1)

1-187: LGTM! Test refactoring correctly aligns with the DTO-based approach.

The test class has been properly updated to test the renamed KeycloakAuthorityProvider class and all assertions have been correctly modified to use DTO property accessors instead of JavaBean-style getters.

backend/src/main/java/org/cryptomator/hub/keycloak/KeycloakAuthorityProvider.java (1)

1-115: LGTM! Provider correctly refactored to use DTOs.

The class has been properly refactored to return DTO objects instead of entities, which aligns with the PR objective to prevent TransientObjectException by avoiding the mixing of managed and unmanaged entities.

backend/src/test/java/org/cryptomator/hub/keycloak/KeycloakAuthorityPullerTest.java (1)

1-325: Comprehensive test coverage for the new synchronization logic.

The test class provides thorough coverage of all synchronization scenarios including adding, deleting, and updating both users and groups. The parameterized tests effectively cover edge cases.

backend/src/main/java/org/cryptomator/hub/keycloak/KeycloakAuthorityPuller.java (1)

34-44: Good synchronization order!

The method correctly processes users before groups, which is essential since groups reference users. The transactional boundary ensures atomicity of the entire sync operation.

@SailReal SailReal merged commit f928af5 into develop Jun 6, 2025
8 checks passed
@SailReal SailReal deleted the feature/fix-syncer branch June 6, 2025 10:51
SailReal added a commit that referenced this pull request Jun 6, 2025
* Fix syncer

Under certain circumstances, where a large number of modifying processes take place in a transaction, e.g. 10 groups are deleted, 10 groups are created, these new groups are directly assigned to many new users, it can happen that the syncer aborts with the following exception:

java.lang.IllegalStateException: org.hibernate.TransientObjectException: persistent instance references an unsaved transient instance of ‘org.cryptomator.hub.entities.Authority’ (save the transient instance before flushing)

Since the tansaction is canceled here, the same error occurs again during the next sync process.

The reason for this is that we mix managed (which already exist in the DB) and unmanaged (which we map from Keycloak directly into entities) Hibernate entities and from a certain complexity, the Hibernate session management no longer gets this cleanly assigned, especially if an object with the same ID exists as both managed and unmanaged.

* Fix potential nullpointer if a new user was added to a group
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.

2 participants