Startup improvements#3928
Conversation
… checked/ugpraded yet
WalkthroughThis PR removes organization-scoped group and user management APIs, introduces per-site bootstrap state tracking with annotation-based operation gating, refactors the startup event flow using semaphore-based coordination between system and site upgrades, removes deprecated WebDAV and ElasticTranscoder features, and updates the database schema to version 5.0.0.18. ChangesOrganization Removal & Site Bootstrap State Gating
🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related issues
Possibly related PRs
Suggested reviewers
✨ Finishing Touches🧪 Generate unit tests (beta)
|
There was a problem hiding this comment.
Actionable comments posted: 4
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In
`@src/main/java/org/craftercms/studio/impl/v2/service/site/SitesServiceImpl.java`:
- Around line 129-132: The enablePublishing method is annotated with
`@RequireSiteBootstrapComplete` but its siteId parameter lacks the `@SiteId`
annotation so the bootstrap aspect cannot reliably resolve the target site;
update the method signature for enablePublishing to annotate the siteId
parameter with `@SiteId` in addition to the existing
`@ProtectedResourceId`(SITE_ID_RESOURCE_ID) so the bootstrap gating aspect can
correctly resolve and validate the site before calling
sitesServiceInternal.enablePublishing.
In
`@src/main/java/org/craftercms/studio/impl/v2/upgrade/StudioUpgradeManagerImpl.java`:
- Around line 139-141: The call to siteBootstrapStateProvider.markSiteAsReady is
executed unconditionally even though upgradeSiteConfiguration catches and
swallows errors; change the flow so the site is only marked ready when the
config upgrade actually succeeded by having upgradeSiteConfiguration report
success (e.g., return boolean or throw on failure) and update
StudioUpgradeManagerImpl to check that result before calling
siteBootstrapStateProvider.markSiteAsReady(context.getTarget()); alternatively
catch specific exceptions around upgradeSiteConfiguration here and skip marking
ready on failure, ensuring upgradeSiteConfiguration and/or the caller use clear
success/failure signaling rather than logging-only.
In
`@src/main/java/org/craftercms/studio/impl/v2/utils/spring/context/BootstrapManagerImpl.java`:
- Around line 87-94: The catch for InterruptedException in BootstrapManagerImpl
currently logs and restores the interrupt but then lets execution continue and
returns a StartSitesUpgradeEvent; change the catch to stop the flow by either
rethrowing a runtime exception or returning a no-op (do not produce
StartSitesUpgradeEvent). Concretely, inside the catch where
upgradeSemaphore.acquire() is handled, after Thread.currentThread().interrupt()
either throw a new IllegalStateException("Interrupted waiting for system
upgrade", e) (so callers won't start site upgrades) or return null/a dedicated
abort event instead of falling through to return new
StartSitesUpgradeEvent(this); update any callers if you choose to return null to
handle the abort case.
In `@src/main/resources/org/craftercms/studio/api/v2/dal/GroupDAO.xml`:
- Around line 53-71: The mapper IDs in GroupDAO.xml don't match the DAO method
names; rename the <select> id "getAllGroups" to "getAllGroupsForOrganization"
and "getAllGroupsTotal" to "getAllGroupsForOrganizationTotal" so MyBatis can
bind to the GroupDAO methods; keep the existing resultMap/resultType, parameter
bindings (keyword -> pattern bind), and SQL clauses unchanged, only update the
id attributes to exactly match the GroupDAO method names.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 5a58e55f-7793-42e2-afec-b0517e741d2c
📒 Files selected for processing (54)
src/main/api/studio-api.yamlsrc/main/java/org/craftercms/studio/api/v1/aws/elastictranscoder/ElasticTranscoder.javasrc/main/java/org/craftercms/studio/api/v1/constant/StudioConstants.javasrc/main/java/org/craftercms/studio/api/v1/service/webdav/WebDavService.javasrc/main/java/org/craftercms/studio/api/v2/annotation/RequireSiteBootstrapComplete.javasrc/main/java/org/craftercms/studio/api/v2/annotation/RequireSiteBootstrapCompleteAnnotationHandler.javasrc/main/java/org/craftercms/studio/api/v2/dal/AuditLog.javasrc/main/java/org/craftercms/studio/api/v2/dal/Group.javasrc/main/java/org/craftercms/studio/api/v2/dal/GroupDAO.javasrc/main/java/org/craftercms/studio/api/v2/dal/Organization.javasrc/main/java/org/craftercms/studio/api/v2/dal/QueryParameterNames.javasrc/main/java/org/craftercms/studio/api/v2/dal/Site.javasrc/main/java/org/craftercms/studio/api/v2/exception/OrganizationNotFoundException.javasrc/main/java/org/craftercms/studio/api/v2/exception/SiteBootstrapNotCompleteException.javasrc/main/java/org/craftercms/studio/api/v2/service/security/GroupService.javasrc/main/java/org/craftercms/studio/api/v2/service/security/UserService.javasrc/main/java/org/craftercms/studio/api/v2/utils/spring/context/BootstrapManager.javasrc/main/java/org/craftercms/studio/api/v2/utils/spring/context/SiteBootstrapStateProvider.javasrc/main/java/org/craftercms/studio/controller/rest/v2/ExceptionHandlers.javasrc/main/java/org/craftercms/studio/controller/rest/v2/GroupsController.javasrc/main/java/org/craftercms/studio/controller/rest/v2/UsersController.javasrc/main/java/org/craftercms/studio/impl/v1/aws/elastictranscoder/TranscoderProfileMapper.javasrc/main/java/org/craftercms/studio/impl/v1/service/content/DmPageNavigationOrderServiceImpl.javasrc/main/java/org/craftercms/studio/impl/v1/service/webdav/WebDavServiceImpl.javasrc/main/java/org/craftercms/studio/impl/v2/repository/GitStartupConfig.javasrc/main/java/org/craftercms/studio/impl/v2/repository/RepositoryStartupCleanup.javasrc/main/java/org/craftercms/studio/impl/v2/service/audit/internal/AuditServiceInternalImpl.javasrc/main/java/org/craftercms/studio/impl/v2/service/content/ContentServiceImpl.javasrc/main/java/org/craftercms/studio/impl/v2/service/security/GroupServiceImpl.javasrc/main/java/org/craftercms/studio/impl/v2/service/security/UserServiceImpl.javasrc/main/java/org/craftercms/studio/impl/v2/service/security/internal/GroupServiceInternalImpl.javasrc/main/java/org/craftercms/studio/impl/v2/service/security/internal/UserServiceInternalImpl.javasrc/main/java/org/craftercms/studio/impl/v2/service/site/SitesServiceImpl.javasrc/main/java/org/craftercms/studio/impl/v2/service/site/internal/SitesServiceInternalImpl.javasrc/main/java/org/craftercms/studio/impl/v2/upgrade/StudioUpgradeManagerImpl.javasrc/main/java/org/craftercms/studio/impl/v2/upgrade/operations/plugin/DescriptorV2UpgradeOperation.javasrc/main/java/org/craftercms/studio/impl/v2/upgrade/operations/site/AddSiteUuidOperation.javasrc/main/java/org/craftercms/studio/impl/v2/utils/spring/context/BootstrapManagerImpl.javasrc/main/java/org/craftercms/studio/impl/v2/utils/spring/context/SiteBootstrapStateProviderImpl.javasrc/main/java/org/craftercms/studio/impl/v2/utils/spring/event/StartSitesBootstrapEvent.javasrc/main/java/org/craftercms/studio/impl/v2/utils/spring/event/StartSitesUpgradeEvent.javasrc/main/java/org/craftercms/studio/impl/v2/utils/spring/event/StartSystemUpgradeEvent.javasrc/main/java/org/craftercms/studio/model/rest/ApiResponse.javasrc/main/resources/crafter/studio/database/createDDL.sqlsrc/main/resources/crafter/studio/database/upgrade/5.0.x/5.0.0.17-to-5.0.0.18.sqlsrc/main/resources/crafter/studio/security/common.xmlsrc/main/resources/crafter/studio/studio-config.yamlsrc/main/resources/crafter/studio/studio-services-context.xmlsrc/main/resources/crafter/studio/studio-upgrade-context.xmlsrc/main/resources/crafter/studio/studio-websocket-context.xmlsrc/main/resources/crafter/studio/upgrade/pipelines.yamlsrc/main/resources/org/craftercms/studio/api/v2/dal/AuditDAO.xmlsrc/main/resources/org/craftercms/studio/api/v2/dal/GroupDAO.xmlsrc/test/java/org/craftercms/studio/impl/v2/service/content/ContentServiceImplTest.java
💤 Files with no reviewable changes (11)
- src/main/java/org/craftercms/studio/api/v1/constant/StudioConstants.java
- src/main/java/org/craftercms/studio/api/v1/aws/elastictranscoder/ElasticTranscoder.java
- src/main/java/org/craftercms/studio/controller/rest/v2/ExceptionHandlers.java
- src/main/java/org/craftercms/studio/impl/v1/aws/elastictranscoder/TranscoderProfileMapper.java
- src/main/java/org/craftercms/studio/api/v1/service/webdav/WebDavService.java
- src/main/java/org/craftercms/studio/impl/v2/upgrade/operations/site/AddSiteUuidOperation.java
- src/main/java/org/craftercms/studio/api/v2/dal/Organization.java
- src/main/java/org/craftercms/studio/api/v2/exception/OrganizationNotFoundException.java
- src/main/java/org/craftercms/studio/impl/v2/upgrade/operations/plugin/DescriptorV2UpgradeOperation.java
- src/main/resources/crafter/studio/studio-upgrade-context.xml
- src/main/java/org/craftercms/studio/impl/v1/service/webdav/WebDavServiceImpl.java
| @RequireSiteBootstrapComplete | ||
| @HasPermission(type = DefaultPermission.class, action = PERMISSION_START_STOP_PUBLISHER) | ||
| public void enablePublishing(@ProtectedResourceId(SITE_ID_RESOURCE_ID) String siteId, boolean enabled) { | ||
| sitesServiceInternal.enablePublishing(siteId, enabled); |
There was a problem hiding this comment.
Add @SiteId to enablePublishing so bootstrap gating can resolve the target site.
This method is now guarded by @RequireSiteBootstrapComplete, but its siteId argument is not annotated with @SiteId. The bootstrap aspect resolves site IDs via @SiteId, so this can cause incorrect/null resolution and block valid calls.
💡 Proposed fix
- public void enablePublishing(`@ProtectedResourceId`(SITE_ID_RESOURCE_ID) String siteId, boolean enabled) {
+ public void enablePublishing(`@SiteId` `@ProtectedResourceId`(SITE_ID_RESOURCE_ID) String siteId, boolean enabled) {
sitesServiceInternal.enablePublishing(siteId, enabled);
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| @RequireSiteBootstrapComplete | |
| @HasPermission(type = DefaultPermission.class, action = PERMISSION_START_STOP_PUBLISHER) | |
| public void enablePublishing(@ProtectedResourceId(SITE_ID_RESOURCE_ID) String siteId, boolean enabled) { | |
| sitesServiceInternal.enablePublishing(siteId, enabled); | |
| `@RequireSiteBootstrapComplete` | |
| `@HasPermission`(type = DefaultPermission.class, action = PERMISSION_START_STOP_PUBLISHER) | |
| public void enablePublishing(`@SiteId` `@ProtectedResourceId`(SITE_ID_RESOURCE_ID) String siteId, boolean enabled) { | |
| sitesServiceInternal.enablePublishing(siteId, enabled); | |
| } |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In
`@src/main/java/org/craftercms/studio/impl/v2/service/site/SitesServiceImpl.java`
around lines 129 - 132, The enablePublishing method is annotated with
`@RequireSiteBootstrapComplete` but its siteId parameter lacks the `@SiteId`
annotation so the bootstrap aspect cannot reliably resolve the target site;
update the method signature for enablePublishing to annotate the siteId
parameter with `@SiteId` in addition to the existing
`@ProtectedResourceId`(SITE_ID_RESOURCE_ID) so the bootstrap gating aspect can
correctly resolve and validate the site before calling
sitesServiceInternal.enablePublishing.
| upgradeSiteConfiguration((StudioUpgradeContext) context); | ||
| siteBootstrapStateProvider.markSiteAsReady(context.getTarget()); | ||
| } |
There was a problem hiding this comment.
Avoid marking sites ready when config upgrade errors are swallowed.
Line 140 marks the site as ready unconditionally. Since upgradeSiteConfiguration(...) catches broad exceptions and only logs them, failed config upgrades can still transition a site to ready.
🛠️ Proposed fix
- } catch (Exception e) {
- logger.error("Failed to upgrade the configuration file '{}'", configPath, e);
+ } catch (Exception e) {
+ logger.error("Failed to upgrade the configuration file '{}'", configPath, e);
+ throw new UpgradeException("Failed to upgrade site configuration for site '" + site + "'", e);
} finally {
context.clearCurrentConfig();
}🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In
`@src/main/java/org/craftercms/studio/impl/v2/upgrade/StudioUpgradeManagerImpl.java`
around lines 139 - 141, The call to siteBootstrapStateProvider.markSiteAsReady
is executed unconditionally even though upgradeSiteConfiguration catches and
swallows errors; change the flow so the site is only marked ready when the
config upgrade actually succeeded by having upgradeSiteConfiguration report
success (e.g., return boolean or throw on failure) and update
StudioUpgradeManagerImpl to check that result before calling
siteBootstrapStateProvider.markSiteAsReady(context.getTarget()); alternatively
catch specific exceptions around upgradeSiteConfiguration here and skip marking
ready on failure, ensuring upgradeSiteConfiguration and/or the caller use clear
success/failure signaling rather than logging-only.
| try { | ||
| upgradeSemaphore.acquire(); | ||
| } catch (InterruptedException e) { | ||
| logger.warn("Interrupted while waiting for system upgrade to complete, starting sites upgrade anyway", e); | ||
| Thread.currentThread().interrupt(); // restore interrupt status | ||
| } | ||
| logger.info("Start upgrade ..."); | ||
| return new StartSitesUpgradeEvent(this); |
There was a problem hiding this comment.
Do not continue to sites upgrade after interruption.
If semaphore wait is interrupted, the code currently still returns StartSitesUpgradeEvent, which can violate bootstrap ordering and run site upgrades before system upgrade completion.
🛠️ Proposed fix
try {
upgradeSemaphore.acquire();
} catch (InterruptedException e) {
- logger.warn("Interrupted while waiting for system upgrade to complete, starting sites upgrade anyway", e);
- Thread.currentThread().interrupt(); // restore interrupt status
+ logger.warn("Interrupted while waiting for system upgrade to complete; skipping sites upgrade start", e);
+ Thread.currentThread().interrupt(); // restore interrupt status
+ return null;
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| try { | |
| upgradeSemaphore.acquire(); | |
| } catch (InterruptedException e) { | |
| logger.warn("Interrupted while waiting for system upgrade to complete, starting sites upgrade anyway", e); | |
| Thread.currentThread().interrupt(); // restore interrupt status | |
| } | |
| logger.info("Start upgrade ..."); | |
| return new StartSitesUpgradeEvent(this); | |
| try { | |
| upgradeSemaphore.acquire(); | |
| } catch (InterruptedException e) { | |
| logger.warn("Interrupted while waiting for system upgrade to complete; skipping sites upgrade start", e); | |
| Thread.currentThread().interrupt(); // restore interrupt status | |
| return null; | |
| } | |
| logger.info("Start upgrade ..."); | |
| return new StartSitesUpgradeEvent(this); |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In
`@src/main/java/org/craftercms/studio/impl/v2/utils/spring/context/BootstrapManagerImpl.java`
around lines 87 - 94, The catch for InterruptedException in BootstrapManagerImpl
currently logs and restores the interrupt but then lets execution continue and
returns a StartSitesUpgradeEvent; change the catch to stop the flow by either
rethrowing a runtime exception or returning a no-op (do not produce
StartSitesUpgradeEvent). Concretely, inside the catch where
upgradeSemaphore.acquire() is handled, after Thread.currentThread().interrupt()
either throw a new IllegalStateException("Interrupted waiting for system
upgrade", e) (so callers won't start site upgrades) or return null/a dedicated
abort event instead of falling through to return new
StartSitesUpgradeEvent(this); update any callers if you choose to return null to
handle the abort case.
| <select id="getAllGroups" resultMap="GroupMap"> | ||
| SELECT * FROM `group` | ||
| <if test="keyword != null" > | ||
| <bind name="pattern" value="'%' + keyword + '%'" /> | ||
| WHERE group_name like #{pattern} | ||
| </if> | ||
| <if test="sort != null and sort != '' "> | ||
| ORDER BY ${sort} | ||
| </if> | ||
| LIMIT #{offset}, #{limit} | ||
| </select> | ||
|
|
||
| <select id="getAllGroupsTotal" resultType="Integer" > | ||
| SELECT count(1) FROM `group` | ||
| <if test="keyword != null" > | ||
| <bind name="pattern" value="'%' + keyword + '%'" /> | ||
| WHERE group_name like #{pattern} | ||
| </if> | ||
| </select> |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Description: Compare DAO interface method names with mapper query IDs
echo "=== DAO interface method names ==="
rg -n 'List<Group> getAllGroups|int getAllGroupsTotal|getAllGroupsForOrganization' --type java src/main/java/org/craftercms/studio/api/v2/dal/GroupDAO.java
echo ""
echo "=== Mapper query IDs ==="
rg -n 'select id="getAllGroups|select id="getAllGroupsForOrganization' src/main/resources/org/craftercms/studio/api/v2/dal/GroupDAO.xmlRepository: craftercms/studio
Length of output: 385
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "=== Mapper statement ids for getAllGroups* ==="
rg -n 'id="getAllGroups(ForOrganization)?(Total)?"' src/main/resources/org/craftercms/studio/api/v2/dal/GroupDAO.xml
echo ""
echo "=== Any other mapper ids matching DAO methods ==="
rg -n 'getAllGroupsForOrganization(Total)?' src/main/resources/org/craftercms/studio/api/v2/dalRepository: craftercms/studio
Length of output: 272
Fix MyBatis mapper IDs to match GroupDAO method names
GroupDAO.java declares getAllGroupsForOrganization / getAllGroupsForOrganizationTotal, but GroupDAO.xml defines getAllGroups / getAllGroupsTotal. The statement id must match the DAO method name for MyBatis to bind it.
🐛 Proposed fix
- <select id="getAllGroups" resultMap="GroupMap">
+ <select id="getAllGroupsForOrganization" resultMap="GroupMap">
SELECT * FROM `group`
<if test="keyword != null" >
<bind name="pattern" value="'%' + keyword + '%'" />
WHERE group_name like #{pattern}
</if>
<if test="sort != null and sort != '' ">
ORDER BY ${sort}
</if>
LIMIT #{offset}, #{limit}
</select>
- <select id="getAllGroupsTotal" resultType="Integer" >
+ <select id="getAllGroupsForOrganizationTotal" resultType="Integer" >
SELECT count(1) FROM `group`
<if test="keyword != null" >
<bind name="pattern" value="'%' + keyword + '%'" />
WHERE group_name like #{pattern}
</if>
</select>📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| <select id="getAllGroups" resultMap="GroupMap"> | |
| SELECT * FROM `group` | |
| <if test="keyword != null" > | |
| <bind name="pattern" value="'%' + keyword + '%'" /> | |
| WHERE group_name like #{pattern} | |
| </if> | |
| <if test="sort != null and sort != '' "> | |
| ORDER BY ${sort} | |
| </if> | |
| LIMIT #{offset}, #{limit} | |
| </select> | |
| <select id="getAllGroupsTotal" resultType="Integer" > | |
| SELECT count(1) FROM `group` | |
| <if test="keyword != null" > | |
| <bind name="pattern" value="'%' + keyword + '%'" /> | |
| WHERE group_name like #{pattern} | |
| </if> | |
| </select> | |
| <select id="getAllGroupsForOrganization" resultMap="GroupMap"> | |
| SELECT * FROM `group` | |
| <if test="keyword != null" > | |
| <bind name="pattern" value="'%' + keyword + '%'" /> | |
| WHERE group_name like #{pattern} | |
| </if> | |
| <if test="sort != null and sort != '' "> | |
| ORDER BY ${sort} | |
| </if> | |
| LIMIT #{offset}, #{limit} | |
| </select> | |
| <select id="getAllGroupsForOrganizationTotal" resultType="Integer" > | |
| SELECT count(1) FROM `group` | |
| <if test="keyword != null" > | |
| <bind name="pattern" value="'%' + keyword + '%'" /> | |
| WHERE group_name like #{pattern} | |
| </if> | |
| </select> |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/main/resources/org/craftercms/studio/api/v2/dal/GroupDAO.xml` around
lines 53 - 71, The mapper IDs in GroupDAO.xml don't match the DAO method names;
rename the <select> id "getAllGroups" to "getAllGroupsForOrganization" and
"getAllGroupsTotal" to "getAllGroupsForOrganizationTotal" so MyBatis can bind to
the GroupDAO methods; keep the existing resultMap/resultType, parameter bindings
(keyword -> pattern bind), and SQL clauses unchanged, only update the id
attributes to exactly match the GroupDAO method names.
Startup improvements
craftercms/craftercms#8652
Summary by CodeRabbit
New Features
Bug Fixes
Chores