Skip to content

Conversation

@adrianfish
Copy link
Contributor

@adrianfish adrianfish commented Jan 6, 2026

https://sakaiproject.atlassian.net/browse/SAK-51956

The idea behind this change is to replace the MOTD type notifications with a single db entry rather than one for each system user.

This change also creates a SchedulingService which provides a facade onto a ScheduledExecutorService and updates all the uses of scheduled executors across Sakai to use it.

Summary by CodeRabbit

  • New Features
    • Message of the Day (MOTD) notifications with broadcast delivery and configurable expiration
  • Improvements
    • Automatic expiration and periodic cleanup of old notifications
    • Centralized background scheduling for more reliable timed tasks
    • Improved notification retrieval, filtering and display; MOTD items now show a close button when appropriate
  • Configuration
    • New properties: announcement.motd.notification.ttlhours (default 24), schedulingservice.poolsize (default 4)

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 6, 2026

Walkthrough

Adds broadcast and TTL support to notifications, introduces UserNotificationTransferBean, changes UserNotificationData shape, schedules TTL-driven expiry via a new SchedulingService and implementation, migrates components from local ScheduledExecutorService to SchedulingService, and updates many handlers/tests to the new APIs.

Changes

Cohort / File(s) Summary
Core notification model & API
kernel/api/src/main/java/org/sakaiproject/messaging/api/UserNotificationData.java, kernel/api/src/main/java/org/sakaiproject/messaging/api/UserNotificationTransferBean.java, kernel/api/src/main/java/org/sakaiproject/messaging/api/model/UserNotification.java, kernel/api/src/main/java/org/sakaiproject/messaging/api/repository/UserNotificationRepository.java, kernel/api/src/main/java/org/sakaiproject/messaging/api/UserMessagingService.java
Added broadcast (boolean) and ttl (Duration) to UserNotificationData; added UserNotificationTransferBean DTO; made broadcast persistent and added endDate to UserNotification; repository gains findByBroadcast/deleteExpiredNotifications; getNotifications() now returns List.
Messaging implementation
kernel/kernel-impl/.../UserMessagingServiceImpl.java, kernel/kernel-impl/.../repository/UserNotificationRepositoryImpl.java
Switched internal flows to use UserNotificationData/UserNotificationTransferBean; added scheduled expiry deletion task; added broadcast push path; adjusted repository queries and transaction annotations.
MOTD / Announcement handler
announcement/announcement-api/api/src/java/org/sakaiproject/announcement/api/AnnouncementsUserNotificationHandler.java, config/configuration/bundles/src/bundle/org/sakaiproject/config/bundle/default.sakai.properties
Added MOTD-specific broadcast path producing TTL-bound notifications; introduced motdTTLHours configurable via announcement.motd.notification.ttlhours (default comment added); refactored guard and cleanup logic.
UserNotificationData constructor churn
multiple handlers and tests, e.g. assignment/api/.../AddAssignmentUserNotificationHandler.java, assignment/api/.../GradeAssignmentUserNotificationHandler.java, commons/api/.../CommonsCommentUserNotificationHandler.java, lessonbuilder/.../AddLessonsCommentUserNotificationHandlerImpl.java, msgcntr/messageforums-api/.../PrivateMessageUserNotificationHandler.java, samigo/.../TestsAndQuizzesUserNotificationHandler.java, ...
Numerous call sites updated to new UserNotificationData arity: previous 6-arg constructor usages now pass two additional parameters (commonly false, null).
Scheduling service (new)
kernel/api/src/main/java/org/sakaiproject/scheduling/api/SchedulingService.java, kernel/kernel-impl/src/main/java/org/sakaiproject/scheduling/impl/SchedulingServiceImpl.java, config/.../default.sakai.properties
Introduced SchedulingService interface and SchedulingServiceImpl that manages a configurable ScheduledExecutorService (config schedulingservice.poolsize) and exposes schedule/scheduleWithFixedDelay methods.
Consumers migrated to SchedulingService
kernel/kernel-impl/src/main/java/org/sakaiproject/authz/impl/DbAuthzGroupService.java, kernel/kernel-impl/src/main/java/org/sakaiproject/config/impl/StoredConfigService.java, kernel/kernel-impl/src/main/java/org/sakaiproject/content/impl/FileConversionServiceImpl.java, kernel/kernel-impl/src/main/java/org/sakaiproject/event/impl/ClusterEventTracking.java, plus/impl/src/main/java/org/sakaiproject/plus/impl/PlusServiceImpl.java, microsoft-integration/impl/src/main/java/org/sakaiproject/microsoft/impl/MicrosoftCommonServiceImpl.java
Replaced local ScheduledExecutorService usage with injected SchedulingService; removed local scheduler init/shutdown and added accessors/setters or autowiring.
Spring wiring
kernel/kernel-impl/src/main/webapp/WEB-INF/scheduling-components.xml, kernel/kernel-impl/src/main/webapp/WEB-INF/authz-components.xml, kernel/kernel-impl/src/main/webapp/WEB-INF/configstore-components.xml, kernel/kernel-impl/src/main/webapp/WEB-INF/event-components.xml, kernel/kernel-impl/src/main/webapp/WEB-INF/components.xml, plus/impl/src/main/webapp/WEB-INF/components.xml
Added scheduling-components.xml bean for SchedulingServiceImpl; added lookup-methods / bean properties to expose SchedulingService to existing beans; removed some destroy-method bean lifecycle entries.
Tests & test configs
kernel/kernel-impl/src/test/..., plus/impl/src/test/..., kernel/kernel-impl/src/test/java/org/sakaiproject/content/impl/test/FileConversionServiceTestConfiguration.java, kernel/kernel-impl/src/test/java/org/sakaiproject/messaging/impl/UserMessagingServiceTestConfiguration.java, kernel/kernel-impl/src/test/java/org/sakaiproject/event/impl/test/LearningResourceTestConfiguration.java, .../UserMessagingServiceTests.java
Added/mock SchedulingService beans in test configurations; updated tests to new UserNotificationData constructor and added schedulingService accessors in test bases.
Web API & frontend adjustments
webapi/src/main/java/org/sakaiproject/webapi/controllers/NotificationsController.java, webapi/src/main/java/org/sakaiproject/webapi/controllers/SitesController.java, webcomponents/tool/src/main/frontend/packages/sakai-notifications/src/SakaiNotifications.js
Controllers updated to use UserNotificationTransferBean and direct field access; frontend fetch removed credentials include and adjusted MOTD close-button rendering.

Possibly related PRs

Suggested reviewers

  • ern
  • ottenhoff
  • st-manu
🚥 Pre-merge checks | ✅ 1 | ❌ 2
❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 19.54% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Title check ❓ Inconclusive The PR title is partially related to the changeset. It emphasizes broadcast notification functionality and MOTD (Message of the Day), which is present in the code, but the title lacks clarity and contains a typo ('motifications' instead of 'notifications'), making it unclear and somewhat vague about the actual scope of changes. Revise the title to be more descriptive and accurate. Consider: 'Add broadcast notifications and centralize scheduling service' or 'SAK-51956: Implement broadcast MOTD notifications and SchedulingService abstraction'. This better reflects the significant changes including the new SchedulingService and comprehensive refactoring of notification handling.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ 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

Comment @coderabbitai help to get the list of available commands and usage tips.

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: 6

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
webcomponents/tool/src/main/frontend/packages/sakai-notifications/src/SakaiNotifications.js (1)

87-90: Add credentials: "include" to maintain consistency with other endpoint requests.

The GET request to fetch initial notifications was changed to omit credentials: "include", but other operations in this file (POST requests at lines 245 and 263 to the same /api/users/me/notifications/* endpoints) explicitly include it. For authenticated endpoints, explicitly including credentials is the standard pattern and should be consistent across all requests to the same endpoint.

Code context
    fetch(this.url, {
      cache: "no-cache",
      headers: { "Content-Type": "application/json" },
    })

Other requests in the file include credentials:

    fetch(url, { method: "POST", credentials: "include" })
kernel/kernel-impl/src/main/java/org/sakaiproject/content/impl/FileConversionServiceImpl.java (1)

114-206: Capture and cancel the scheduled task in the destroy() method.

The scheduled task started at line 114 is never captured or cancelled. When FileConversionServiceImpl is destroyed, the task continues running on the shared SchedulingService, causing:

  • Accumulation of multiple scheduled tasks if the bean is reinitialized
  • Resource and memory leaks
  • Unnecessary CPU usage

Store the ScheduledFuture<?> scheduledTask returned by scheduleWithFixedDelay() and cancel it in the destroy() method.

Proposed fix

Add a field to store the scheduled task:

+    private ScheduledFuture<?> scheduledTask;

Capture the return value at line 114:

-        schedulingService.scheduleWithFixedDelay(() -> {
+        scheduledTask = schedulingService.scheduleWithFixedDelay(() -> {

Cancel it in destroy():

     public void destroy() {
+        if (scheduledTask != null) {
+            scheduledTask.cancel(false);
+        }
         workers.shutdownNow();
     }
microsoft-integration/impl/src/main/java/org/sakaiproject/microsoft/impl/MicrosoftCommonServiceImpl.java (1)

946-994: Critical: Remove unused ScheduledExecutorService to prevent resource leak.

Line 947 creates a local ScheduledExecutorService that is never used. The scheduling calls on lines 981 and 993 correctly use the injected schedulingService instead. However, the unused executor is never shut down, creating a resource leak.

🔎 Proposed fix
 	@Override
 	public void createTeamFromGroupAsync(String groupId) throws MicrosoftCredentialsException {
-		ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);
 		
 		AtomicInteger counter = new AtomicInteger(1);
 		final int MAX_RETRY = 5;
🤖 Fix all issues with AI Agents
In
@announcement/announcement-api/api/src/java/org/sakaiproject/announcement/api/AnnouncementsUserNotificationHandler.java:
- Around line 170-177: The delete CriteriaQuery uses the wrong Root: replace
references to eventQueryTable with eventDeleteQueryTable in the predicate for
eventDeleteQuery so the DELETE filters use the delete query root; specifically
update the where(...) call used with eventDeleteQuery to use
eventDeleteQueryTable.get("event") for comparisons to
AnnouncementService.SECURE_ANNC_ADD, AnnouncementService.EVENT_AVAILABLE_ANNC
and AnnouncementService.EVENT_ANNC_UPDATE_AVAILABILITY and ensure
queryBuilder.equal(eventDeleteQueryTable.get("ref"), eventResource) is used
before executing session.createQuery(eventDeleteQuery).executeUpdate().
- Around line 203-205: The code in AnnouncementsUserNotificationHandler loads
all users via userDirectoryService.getUsers() when SiteService.ADMIN_SITE_ID and
eventResource contains "motd" (setting usersToNotify), which will OOM on large
installs; instead decide whether MOTD availability changes should use the same
broadcast path as EVENT_MOTD_NEW (avoid per-user notifications) or, if per-user
processing is required for EVENT_AVAILABLE_ANNC /
EVENT_ANNC_UPDATE_AVAILABILITY, replace the eager
userDirectoryService.getUsers() call with a paged/iterator-based retrieval that
streams user IDs (or a query for only active users) so you never materialize the
entire user list into memory; update the usersToNotify assignment and relevant
logic to use the chosen broadcast mechanism or the memory-safe iterator
approach.

In
@kernel/api/src/main/java/org/sakaiproject/messaging/api/UserMessagingService.java:
- Line 24: Remove the unused import UserNotificationData from the top of the
UserMessagingService interface; locate the line importing
org.sakaiproject.messaging.api.UserNotificationData in the UserMessagingService
file and delete that import statement so only referenced types remain.

In
@kernel/api/src/main/java/org/sakaiproject/scheduling/api/SchedulingService.java:
- Around line 21-24: The Javadoc in SchedulingService.java contains a typo in
the property name: remove the stray double-quote from the string
`schedulingservice.poolsize"` so the documentation reads
`schedulingservice.poolsize`; update the Javadoc comment above the
SchedulingService interface/class to correct that property name.

In
@kernel/kernel-impl/src/main/java/org/sakaiproject/messaging/impl/UserMessagingServiceImpl.java:
- Around line 579-594: pushToAllUsers currently calls
userDirectoryService.getUsers() which loads all users into memory; change it to
paginate using getUsers(int first, int last) to avoid OOMs. Modify
pushToAllUsers to loop in batches (e.g., pageSize constant), request users with
getUsers(first, first+pageSize-1), process each returned user by building the
UserNotificationTransferBean (reuse existing logic: set from, to = u.getId(),
event, ref, title), call decorateNotification and push for each, and continue
until no more users are returned; keep executor.execute(...) wrapper and reuse
decorateNotification and push methods. Ensure pageSize is reasonable and handle
the case where getUsers returns an empty list to terminate the loop.
- Around line 488-498: The getNotifications method can return duplicates when a
record has broadcast=true and toUser equals the current user; update the Stream
pipeline that builds beans (which starts with
userNotificationRepository.findByToUser(userId) and
userNotificationRepository.findByBroadcast(true), maps via
UserNotificationTransferBean::of and decorateNotification) to call .distinct()
before .collect() so duplicate UserNotificationTransferBean instances are
removed; ensure UserNotificationTransferBean equality works for distinct() to
operate correctly.
🧹 Nitpick comments (12)
webapi/src/main/java/org/sakaiproject/webapi/controllers/NotificationsController.java (1)

48-88: Consider logging full exceptions instead of just e.toString()

In the three catch blocks you call log.error("... {}", e.toString());, which loses the stack trace. Passing the exception as the last argument would make debugging much easier:

Proposed logging improvement (optional)
-        } catch (Exception e) {
-            log.error("Failed to clear notification: {}", e.toString());
+        } catch (Exception e) {
+            log.error("Failed to clear notification {}", id, e);
         }
@@
-        } catch (Exception e) {
-            log.error("Failed to clear all notifications: {}", e.toString());
+        } catch (Exception e) {
+            log.error("Failed to clear all notifications", e);
         }
@@
-        } catch (Exception e) {
-            log.error("Failed to mark all notifications as viewed: {}", e.toString());
+        } catch (Exception e) {
+            log.error("Failed to mark all notifications as viewed", e);
         }
kernel/api/src/main/java/org/sakaiproject/messaging/api/UserNotificationTransferBean.java (1)

22-42: Consider using Lombok annotations for consistency.

The class imports Lombok annotations (@AllArgsConstructor, @NoArgsConstructor, @Data) but doesn't use them. The related UserNotificationData class uses these annotations with private fields. Using public fields here is inconsistent and may lead to maintenance issues.

🔎 Proposed refactor to align with UserNotificationData pattern
 package org.sakaiproject.messaging.api;
 
 import org.sakaiproject.messaging.api.model.UserNotification;
 
 import java.time.Instant;
 
 import lombok.AllArgsConstructor;
 import lombok.NoArgsConstructor;
 import lombok.Data;
 
+@AllArgsConstructor
+@NoArgsConstructor
+@Data
 public class UserNotificationTransferBean {
 
-    public String from;
-    public String to;
-    public String event;
-    public String ref;
-    public String title;
-    public String siteId;
-    public String url;
-    public Instant eventDate;
-    public boolean viewed;
-    public String tool;
-    public boolean broadcast;
-
-    public String fromDisplayName;
-    public String siteTitle;
-    public String formattedEventDate;
+    private String from;
+    private String to;
+    private String event;
+    private String ref;
+    private String title;
+    private String siteId;
+    private String url;
+    private Instant eventDate;
+    private boolean viewed;
+    private String tool;
+    private boolean broadcast;
+
+    private String fromDisplayName;
+    private String siteTitle;
+    private String formattedEventDate;
 
     public static UserNotificationTransferBean of(UserNotification un) {
 
         UserNotificationTransferBean bean = new UserNotificationTransferBean();
         bean.from = un.getFromUser();
         bean.to = un.getToUser();
         bean.event = un.getEvent();
         bean.ref = un.getRef();
         bean.title = un.getTitle();
         bean.siteId = un.getSiteId();
         bean.url = un.getUrl();
         bean.eventDate = un.getEventDate();
         bean.viewed = un.getViewed();
         bean.tool = un.getTool();
         bean.broadcast = un.getBroadcast();
         return bean;
     }
 }

Note: If you use the Lombok @Data annotation, you can also simplify the factory method by using setters instead of direct field access.

kernel/kernel-impl/src/main/java/org/sakaiproject/messaging/impl/repository/UserNotificationRepositoryImpl.java (2)

52-63: Consider injecting a Clock for testability.

The method uses Instant.now() directly, which makes unit testing difficult since you cannot control the current time in tests.

💡 Consider using an injected Clock

Inject a Clock instance and use Instant.now(clock):

At class level:

private Clock clock = Clock.systemUTC();

public void setClock(Clock clock) {
    this.clock = clock;
}

Then in the method:

-        Instant now = Instant.now();
+        Instant now = Instant.now(clock);

This allows tests to inject a fixed clock for deterministic testing.


65-77: Consider injecting a Clock for testability.

Similar to findByBroadcast, using Instant.now() directly limits testability. Consider injecting a Clock instance.

See the recommendation in the findByBroadcast method above for implementation details.

kernel/api/src/main/java/org/sakaiproject/messaging/api/UserNotificationData.java (1)

20-26: Consider implications of changing from @Getter to @Data.

The switch from @Getter to @Data makes this class mutable by generating setters for all fields. If UserNotificationData is passed between components, unintended mutations could introduce bugs.

However, if this is purely a data transfer object where mutability is acceptable, this change is fine.

If immutability is desired, consider using @Value instead:

-@AllArgsConstructor
-@NoArgsConstructor
-@Data
+@Value
+@Builder
+@With

This would make the class immutable while still allowing flexible construction via the builder pattern. If you need a no-args constructor for frameworks, you can keep @NoArgsConstructor(access = AccessLevel.PRIVATE) with @AllArgsConstructor.

kernel/kernel-impl/src/main/java/org/sakaiproject/scheduling/impl/SchedulingServiceImpl.java (3)

34-38: Consider configuring daemon threads for the executor.

The thread pool threads are not explicitly configured as daemon threads. Non-daemon threads can prevent JVM shutdown if tasks are still running.

💡 Use a custom ThreadFactory to create daemon threads
 public void init() {
 
     int poolSize = serverConfigurationService.getInt("schedulingservice.poolsize", 4);
-    executor = Executors.newScheduledThreadPool(poolSize);
+    executor = Executors.newScheduledThreadPool(poolSize, r -> {
+        Thread t = new Thread(r);
+        t.setDaemon(true);
+        t.setName("SchedulingService-" + t.getId());
+        return t;
+    });
 }

40-43: Consider graceful shutdown instead of abrupt termination.

shutdownNow() immediately stops all executing tasks and may leave work incomplete. Consider a graceful shutdown approach that allows tasks to complete.

💡 Implement graceful shutdown
 public void destroy() {
 
-    executor.shutdownNow();
+    executor.shutdown();
+    try {
+        if (!executor.awaitTermination(30, TimeUnit.SECONDS)) {
+            executor.shutdownNow();
+            if (!executor.awaitTermination(10, TimeUnit.SECONDS)) {
+                log.warn("Executor did not terminate");
+            }
+        }
+    } catch (InterruptedException ie) {
+        executor.shutdownNow();
+        Thread.currentThread().interrupt();
+    }
 }

This gives tasks 30 seconds to complete normally before forcing shutdown.


45-58: Add defensive checks for executor state.

The methods don't check if the executor is null or has been shut down before delegating calls, which could lead to NullPointerException or RejectedExecutionException.

💡 Add executor state validation
 @Override
 public ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit) {
 
+    if (executor == null || executor.isShutdown()) {
+        throw new IllegalStateException("SchedulingService is not initialized or has been shut down");
+    }
     return executor.schedule(command, delay, unit);
 }
 
 @Override
 public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command,
                                             long initialDelay,
                                             long delay,
                                             TimeUnit unit) {
 
+    if (executor == null || executor.isShutdown()) {
+        throw new IllegalStateException("SchedulingService is not initialized or has been shut down");
+    }
     return executor.scheduleWithFixedDelay(command, initialDelay, delay, unit);
 }
samigo/samigo-services/src/java/org/sakaiproject/tool/assessment/TestsAndQuizzesUserNotificationHandler.java (2)

195-196: Use SLF4J parameterized logging instead of string concatenation.

As per coding guidelines, log messages should use parameterized placeholders rather than string concatenation.

Proposed fix
-            }catch (Exception e2){
-                log.error("failed to delete TestAndQuizzes userNotification alerts: " + e2.getMessage());
+            } catch (Exception e2) {
+                log.error("failed to delete TestAndQuizzes userNotification alerts: {}", e2.getMessage());

236-237: Use SLF4J parameterized logging instead of string concatenation.

Same issue as above - use parameterized logging.

Proposed fix
-            }catch (Exception e3){
-                log.error("failed to delete TestAndQuizzes UserNotificationData data " + e3.getMessage());
+            } catch (Exception e3) {
+                log.error("failed to delete TestAndQuizzes UserNotificationData data: {}", e3.getMessage());
kernel/kernel-impl/src/main/java/org/sakaiproject/config/impl/StoredConfigService.java (1)

85-88: Duplicate setters: Lombok-generated and manual setters coexist.

The @Setter annotations on lines 86-88 will generate setter methods via Lombok, but manual setter methods for the same fields still exist at lines 678-688. This creates redundant code and potential confusion.

Proposed fix - remove manual setters
-    public void setServerConfigurationService(ServerConfigurationService serverConfigurationService) {
-        this.serverConfigurationService = serverConfigurationService;
-    }
-
-    public void setDao(HibernateConfigItemDao dao) {
-        this.dao = dao;
-    }
-
-    public void setTextEncryptor(PBEStringEncryptor textEncryptor) {
-        this.textEncryptor = textEncryptor;
-    }
kernel/kernel-impl/src/main/java/org/sakaiproject/messaging/impl/UserMessagingServiceImpl.java (1)

137-137: Unused field detected.

The deletingExpiredNotifications field is declared but never referenced anywhere in the code. This appears to be dead code.

Proposed fix
-    private boolean deletingExpiredNotifications = false;
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0cdfff1 and 2f1f6bf.

📒 Files selected for processing (39)
  • announcement/announcement-api/api/src/java/org/sakaiproject/announcement/api/AnnouncementsUserNotificationHandler.java
  • assignment/api/src/java/org/sakaiproject/assignment/api/AddAssignmentUserNotificationHandler.java
  • assignment/api/src/java/org/sakaiproject/assignment/api/GradeAssignmentUserNotificationHandler.java
  • commons/api/src/java/org/sakaiproject/commons/api/CommonsCommentUserNotificationHandler.java
  • config/configuration/bundles/src/bundle/org/sakaiproject/config/bundle/default.sakai.properties
  • kernel/api/src/main/java/org/sakaiproject/messaging/api/UserMessagingService.java
  • kernel/api/src/main/java/org/sakaiproject/messaging/api/UserNotificationData.java
  • kernel/api/src/main/java/org/sakaiproject/messaging/api/UserNotificationTransferBean.java
  • kernel/api/src/main/java/org/sakaiproject/messaging/api/model/UserNotification.java
  • kernel/api/src/main/java/org/sakaiproject/messaging/api/repository/UserNotificationRepository.java
  • kernel/api/src/main/java/org/sakaiproject/scheduling/api/SchedulingService.java
  • kernel/kernel-impl/src/main/java/org/sakaiproject/authz/impl/AuthzGroupServiceTest.java
  • kernel/kernel-impl/src/main/java/org/sakaiproject/authz/impl/DbAuthzGroupService.java
  • kernel/kernel-impl/src/main/java/org/sakaiproject/config/impl/StoredConfigService.java
  • kernel/kernel-impl/src/main/java/org/sakaiproject/content/impl/FileConversionServiceImpl.java
  • kernel/kernel-impl/src/main/java/org/sakaiproject/event/impl/ClusterEventTracking.java
  • kernel/kernel-impl/src/main/java/org/sakaiproject/event/impl/EventTrackingTest.java
  • kernel/kernel-impl/src/main/java/org/sakaiproject/messaging/impl/UserMessagingServiceImpl.java
  • kernel/kernel-impl/src/main/java/org/sakaiproject/messaging/impl/repository/UserNotificationRepositoryImpl.java
  • kernel/kernel-impl/src/main/java/org/sakaiproject/scheduling/impl/SchedulingServiceImpl.java
  • kernel/kernel-impl/src/main/webapp/WEB-INF/authz-components.xml
  • kernel/kernel-impl/src/main/webapp/WEB-INF/components.xml
  • kernel/kernel-impl/src/main/webapp/WEB-INF/configstore-components.xml
  • kernel/kernel-impl/src/main/webapp/WEB-INF/event-components.xml
  • kernel/kernel-impl/src/main/webapp/WEB-INF/scheduling-components.xml
  • kernel/kernel-impl/src/test/java/org/sakaiproject/content/impl/test/FileConversionServiceTestConfiguration.java
  • kernel/kernel-impl/src/test/java/org/sakaiproject/event/impl/test/LearningResourceTestConfiguration.java
  • kernel/kernel-impl/src/test/java/org/sakaiproject/messaging/impl/UserMessagingServiceTestConfiguration.java
  • kernel/kernel-impl/src/test/java/org/sakaiproject/messaging/impl/UserMessagingServiceTests.java
  • lessonbuilder/components/src/java/org/sakaiproject/lessonbuildertool/impl/AddLessonsCommentUserNotificationHandlerImpl.java
  • microsoft-integration/impl/src/main/java/org/sakaiproject/microsoft/impl/MicrosoftCommonServiceImpl.java
  • msgcntr/messageforums-api/src/java/org/sakaiproject/api/app/messageforums/PrivateMessageUserNotificationHandler.java
  • plus/impl/src/main/java/org/sakaiproject/plus/impl/PlusServiceImpl.java
  • plus/impl/src/main/webapp/WEB-INF/components.xml
  • plus/impl/src/test/org/sakaiproject/plus/impl/PlusTestConfiguration.java
  • samigo/samigo-services/src/java/org/sakaiproject/tool/assessment/TestsAndQuizzesUserNotificationHandler.java
  • webapi/src/main/java/org/sakaiproject/webapi/controllers/NotificationsController.java
  • webapi/src/main/java/org/sakaiproject/webapi/controllers/SitesController.java
  • webcomponents/tool/src/main/frontend/packages/sakai-notifications/src/SakaiNotifications.js
🧰 Additional context used
📓 Path-based instructions (10)
**/*.{html,jsp,vm,xhtml,xml}

📄 CodeRabbit inference engine (.cursor/rules/frontend.mdc)

**/*.{html,jsp,vm,xhtml,xml}: Use Bootstrap 5.2 as the preferred UI framework for styling in Sakai frontends
Leverage Bootstrap 5 components for consistent UI/UX

Files:

  • kernel/kernel-impl/src/main/webapp/WEB-INF/components.xml
  • kernel/kernel-impl/src/main/webapp/WEB-INF/scheduling-components.xml
  • kernel/kernel-impl/src/main/webapp/WEB-INF/configstore-components.xml
  • kernel/kernel-impl/src/main/webapp/WEB-INF/event-components.xml
  • plus/impl/src/main/webapp/WEB-INF/components.xml
  • kernel/kernel-impl/src/main/webapp/WEB-INF/authz-components.xml
**/*.{html,jsp,vm,xhtml,xml,css,scss}

📄 CodeRabbit inference engine (.cursor/rules/frontend.mdc)

Ensure all UI components work across different screen sizes (Responsive Design)

Files:

  • kernel/kernel-impl/src/main/webapp/WEB-INF/components.xml
  • kernel/kernel-impl/src/main/webapp/WEB-INF/scheduling-components.xml
  • kernel/kernel-impl/src/main/webapp/WEB-INF/configstore-components.xml
  • kernel/kernel-impl/src/main/webapp/WEB-INF/event-components.xml
  • plus/impl/src/main/webapp/WEB-INF/components.xml
  • kernel/kernel-impl/src/main/webapp/WEB-INF/authz-components.xml
**/*.{js,html,jsp,vm,xhtml,xml}

📄 CodeRabbit inference engine (.cursor/rules/frontend.mdc)

Use the web components in the webcomponents/ directory when possible in Sakai frontends

Files:

  • kernel/kernel-impl/src/main/webapp/WEB-INF/components.xml
  • webcomponents/tool/src/main/frontend/packages/sakai-notifications/src/SakaiNotifications.js
  • kernel/kernel-impl/src/main/webapp/WEB-INF/scheduling-components.xml
  • kernel/kernel-impl/src/main/webapp/WEB-INF/configstore-components.xml
  • kernel/kernel-impl/src/main/webapp/WEB-INF/event-components.xml
  • plus/impl/src/main/webapp/WEB-INF/components.xml
  • kernel/kernel-impl/src/main/webapp/WEB-INF/authz-components.xml
**/*.{html,jsp,jspx,xml,ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

Prefer kebab-case for values of HTML class and id attributes

Files:

  • kernel/kernel-impl/src/main/webapp/WEB-INF/components.xml
  • webcomponents/tool/src/main/frontend/packages/sakai-notifications/src/SakaiNotifications.js
  • kernel/kernel-impl/src/main/webapp/WEB-INF/scheduling-components.xml
  • kernel/kernel-impl/src/main/webapp/WEB-INF/configstore-components.xml
  • kernel/kernel-impl/src/main/webapp/WEB-INF/event-components.xml
  • plus/impl/src/main/webapp/WEB-INF/components.xml
  • kernel/kernel-impl/src/main/webapp/WEB-INF/authz-components.xml
**/*.java

📄 CodeRabbit inference engine (.cursor/rules/logging-rule.mdc)

**/*.java: Use SLF4J parameterized logging (logger.info("Value is: {}", value)) instead of string concatenation (logger.info("Value is: " + value))
Log messages and code comments should be in English. Log messages should never be translated.

**/*.java: Java: Never use local variable type inference (var). Always declare explicit types. Yes: Map<String, Integer> counts = new HashMap<>(); No: var counts = new HashMap<String, Integer>();
When proposing Java code, spell out full types in local variable declarations, for loops, and try-with-resources
When editing Java, prefer clarity over brevity; avoid introducing language features that aren't widely used in the repo
Treat any PR or suggestion containing Java var as non-compliant. Recommend replacing with explicit types before merge

**/*.java: Use Java 17 for trunk development (Java 11 was used for Sakai 22 and Sakai 23)
Do not use local variable type inference (var) in Java code. Always declare explicit types (e.g., List<String> names = new ArrayList<>(); not var names = new ArrayList<String>();). Enforced by Checkstyle rule during mvn validate

Files:

  • kernel/api/src/main/java/org/sakaiproject/messaging/api/repository/UserNotificationRepository.java
  • kernel/kernel-impl/src/main/java/org/sakaiproject/content/impl/FileConversionServiceImpl.java
  • lessonbuilder/components/src/java/org/sakaiproject/lessonbuildertool/impl/AddLessonsCommentUserNotificationHandlerImpl.java
  • kernel/kernel-impl/src/main/java/org/sakaiproject/authz/impl/AuthzGroupServiceTest.java
  • kernel/kernel-impl/src/main/java/org/sakaiproject/messaging/impl/repository/UserNotificationRepositoryImpl.java
  • kernel/api/src/main/java/org/sakaiproject/scheduling/api/SchedulingService.java
  • microsoft-integration/impl/src/main/java/org/sakaiproject/microsoft/impl/MicrosoftCommonServiceImpl.java
  • webapi/src/main/java/org/sakaiproject/webapi/controllers/SitesController.java
  • kernel/kernel-impl/src/test/java/org/sakaiproject/messaging/impl/UserMessagingServiceTestConfiguration.java
  • assignment/api/src/java/org/sakaiproject/assignment/api/AddAssignmentUserNotificationHandler.java
  • assignment/api/src/java/org/sakaiproject/assignment/api/GradeAssignmentUserNotificationHandler.java
  • kernel/kernel-impl/src/main/java/org/sakaiproject/event/impl/EventTrackingTest.java
  • kernel/kernel-impl/src/test/java/org/sakaiproject/messaging/impl/UserMessagingServiceTests.java
  • plus/impl/src/test/org/sakaiproject/plus/impl/PlusTestConfiguration.java
  • plus/impl/src/main/java/org/sakaiproject/plus/impl/PlusServiceImpl.java
  • webapi/src/main/java/org/sakaiproject/webapi/controllers/NotificationsController.java
  • kernel/kernel-impl/src/test/java/org/sakaiproject/content/impl/test/FileConversionServiceTestConfiguration.java
  • kernel/kernel-impl/src/main/java/org/sakaiproject/messaging/impl/UserMessagingServiceImpl.java
  • commons/api/src/java/org/sakaiproject/commons/api/CommonsCommentUserNotificationHandler.java
  • kernel/kernel-impl/src/test/java/org/sakaiproject/event/impl/test/LearningResourceTestConfiguration.java
  • kernel/api/src/main/java/org/sakaiproject/messaging/api/UserMessagingService.java
  • kernel/api/src/main/java/org/sakaiproject/messaging/api/model/UserNotification.java
  • samigo/samigo-services/src/java/org/sakaiproject/tool/assessment/TestsAndQuizzesUserNotificationHandler.java
  • kernel/kernel-impl/src/main/java/org/sakaiproject/scheduling/impl/SchedulingServiceImpl.java
  • kernel/kernel-impl/src/main/java/org/sakaiproject/config/impl/StoredConfigService.java
  • kernel/api/src/main/java/org/sakaiproject/messaging/api/UserNotificationTransferBean.java
  • kernel/api/src/main/java/org/sakaiproject/messaging/api/UserNotificationData.java
  • kernel/kernel-impl/src/main/java/org/sakaiproject/event/impl/ClusterEventTracking.java
  • msgcntr/messageforums-api/src/java/org/sakaiproject/api/app/messageforums/PrivateMessageUserNotificationHandler.java
  • announcement/announcement-api/api/src/java/org/sakaiproject/announcement/api/AnnouncementsUserNotificationHandler.java
  • kernel/kernel-impl/src/main/java/org/sakaiproject/authz/impl/DbAuthzGroupService.java
**/*.js

📄 CodeRabbit inference engine (.cursor/rules/frontend.mdc)

**/*.js: Use clean, standard modern JavaScript in Sakai frontends
Update jQuery code to modern JavaScript when making changes, if the changes are minimal
Prefer ES6+ features (arrow functions, template literals, destructuring, etc.) in JavaScript
Write modular, reusable JavaScript components in Sakai frontends
Minimize use of global variables and functions (Avoid Global Scope) in JavaScript

Files:

  • webcomponents/tool/src/main/frontend/packages/sakai-notifications/src/SakaiNotifications.js
webcomponents/**/*.js

📄 CodeRabbit inference engine (.cursor/rules/webcomponents-rule.mdc)

webcomponents/**/*.js: Use double quotes instead of single quotes in Sakai Webcomponents
Do not use trailing spaces in Sakai Webcomponents code
Use the i18n property for internationalization; never use an OR switch to also provide English text
Do not attempt to manipulate dates and times based on the browser's timezone; use the Sakai portal object which includes the user's preferred timezone
Prefix internal component state properties with an underscore (e.g., _points) and expose getters if external read access is required; use Lit { state: true } properties and prefer reactive declarative template bindings over imperative DOM access

Files:

  • webcomponents/tool/src/main/frontend/packages/sakai-notifications/src/SakaiNotifications.js
**/*.{js,ts,jsx,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{js,ts,jsx,tsx}: Target evergreen browsers; assume ES2022+ features and browser APIs like fetch keepalive are present; avoid legacy branches, UA sniffing, or fallbacks unless a specific evergreen gap is documented
Replace jQuery with modern DOM APIs when touching code; new work should not add jQuery dependencies
Compose Lit components, ES modules, and encapsulated helpers; keep state local and explicit with modular code
Prefer module scope or class fields; expose intentional APIs instead of incidental globals; avoid global side channels

Files:

  • webcomponents/tool/src/main/frontend/packages/sakai-notifications/src/SakaiNotifications.js
**/*/*.{js,ts}

📄 CodeRabbit inference engine (AGENTS.md)

Internal reactive state in Lit components should stay prefixed with _ and is only surfaced through getters/setters when required

Files:

  • webcomponents/tool/src/main/frontend/packages/sakai-notifications/src/SakaiNotifications.js
{**/UserMessagingServiceImpl.java,**/sakai-service-worker.js}

📄 CodeRabbit inference engine (AGENTS.md)

Use Web Push with VAPID via UserMessagingServiceImpl.java; rely on the service worker in sakai-service-worker.js

Files:

  • kernel/kernel-impl/src/main/java/org/sakaiproject/messaging/impl/UserMessagingServiceImpl.java
🧠 Learnings (15)
📓 Common learnings
Learnt from: CR
Repo: sakaiproject/sakai PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-24T19:12:27.663Z
Learning: Applies to **/sakai-notifications.properties : Reconcile subscriptions proactively and keep localization strings in `sakai-notifications.properties`
Learnt from: CR
Repo: sakaiproject/sakai PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-24T19:12:20.537Z
Learning: Applies to **/*.{java,properties} : Reconcile push notification subscriptions proactively and keep localization strings in `sakai-notifications.properties`
Learnt from: CR
Repo: sakaiproject/sakai PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-10-07T16:11:33.008Z
Learning: Applies to **/sakai-notifications.properties : Use sakai-notifications.properties for internationalized PWA installation messages
📚 Learning: 2025-11-24T19:12:27.663Z
Learnt from: CR
Repo: sakaiproject/sakai PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-24T19:12:27.663Z
Learning: Applies to **/sakai-notifications.properties : Reconcile subscriptions proactively and keep localization strings in `sakai-notifications.properties`

Applied to files:

  • webcomponents/tool/src/main/frontend/packages/sakai-notifications/src/SakaiNotifications.js
  • kernel/kernel-impl/src/main/java/org/sakaiproject/messaging/impl/repository/UserNotificationRepositoryImpl.java
  • webapi/src/main/java/org/sakaiproject/webapi/controllers/SitesController.java
  • kernel/kernel-impl/src/main/java/org/sakaiproject/messaging/impl/UserMessagingServiceImpl.java
  • config/configuration/bundles/src/bundle/org/sakaiproject/config/bundle/default.sakai.properties
  • announcement/announcement-api/api/src/java/org/sakaiproject/announcement/api/AnnouncementsUserNotificationHandler.java
📚 Learning: 2025-10-07T16:11:33.008Z
Learnt from: CR
Repo: sakaiproject/sakai PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-10-07T16:11:33.008Z
Learning: Applies to webcomponents/tool/src/main/frontend/**/*.js : Define custom elements for Sakai-specific functionality when building Web Components

Applied to files:

  • webcomponents/tool/src/main/frontend/packages/sakai-notifications/src/SakaiNotifications.js
📚 Learning: 2025-11-24T19:12:20.537Z
Learnt from: CR
Repo: sakaiproject/sakai PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-24T19:12:20.537Z
Learning: Applies to **/*.{java,properties} : Reconcile push notification subscriptions proactively and keep localization strings in `sakai-notifications.properties`

Applied to files:

  • webcomponents/tool/src/main/frontend/packages/sakai-notifications/src/SakaiNotifications.js
  • kernel/kernel-impl/src/main/java/org/sakaiproject/messaging/impl/repository/UserNotificationRepositoryImpl.java
  • webapi/src/main/java/org/sakaiproject/webapi/controllers/SitesController.java
  • kernel/kernel-impl/src/main/java/org/sakaiproject/messaging/impl/UserMessagingServiceImpl.java
  • config/configuration/bundles/src/bundle/org/sakaiproject/config/bundle/default.sakai.properties
  • announcement/announcement-api/api/src/java/org/sakaiproject/announcement/api/AnnouncementsUserNotificationHandler.java
📚 Learning: 2025-09-16T04:03:17.598Z
Learnt from: kunaljaykam
Repo: sakaiproject/sakai PR: 13974
File: syllabus/syllabus-impl/src/webapp/WEB-INF/components.xml:80-90
Timestamp: 2025-09-16T04:03:17.598Z
Learning: In Sakai's search infrastructure, content producers like SyllabusContentProducer don't require explicit destroy-method configuration in Spring XML. The search framework handles cleanup automatically, and adding destroy-method when no destroy() method exists would cause configuration errors.

Applied to files:

  • kernel/kernel-impl/src/main/webapp/WEB-INF/configstore-components.xml
  • plus/impl/src/main/webapp/WEB-INF/components.xml
📚 Learning: 2025-10-07T16:11:33.008Z
Learnt from: CR
Repo: sakaiproject/sakai PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-10-07T16:11:33.008Z
Learning: Applies to **/sakai-notifications.properties : Use sakai-notifications.properties for internationalized PWA installation messages

Applied to files:

  • webapi/src/main/java/org/sakaiproject/webapi/controllers/SitesController.java
  • kernel/kernel-impl/src/main/java/org/sakaiproject/messaging/impl/UserMessagingServiceImpl.java
  • config/configuration/bundles/src/bundle/org/sakaiproject/config/bundle/default.sakai.properties
📚 Learning: 2025-11-24T19:12:27.663Z
Learnt from: CR
Repo: sakaiproject/sakai PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-24T19:12:27.663Z
Learning: Applies to {**/UserMessagingServiceImpl.java,**/sakai-service-worker.js} : Use Web Push with VAPID via `UserMessagingServiceImpl.java`; rely on the service worker in `sakai-service-worker.js`

Applied to files:

  • webapi/src/main/java/org/sakaiproject/webapi/controllers/SitesController.java
  • kernel/kernel-impl/src/test/java/org/sakaiproject/messaging/impl/UserMessagingServiceTests.java
  • kernel/kernel-impl/src/main/java/org/sakaiproject/messaging/impl/UserMessagingServiceImpl.java
  • kernel/api/src/main/java/org/sakaiproject/messaging/api/UserMessagingService.java
📚 Learning: 2025-09-25T13:34:06.742Z
Learnt from: susowrc
Repo: sakaiproject/sakai PR: 14105
File: assignment/tool/src/java/org/sakaiproject/assignment/entityproviders/AssignmentEntityProvider.java:1736-1745
Timestamp: 2025-09-25T13:34:06.742Z
Learning: In Sakai, each site can only have one tool placement for a given common tool ID (e.g., "sakai.assignment.grades" for the Assignment tool). The method getToolForCommonId() returns a single ToolConfiguration object, not a collection, confirming this one-to-one relationship between tools and sites.

Applied to files:

  • webapi/src/main/java/org/sakaiproject/webapi/controllers/SitesController.java
📚 Learning: 2025-10-10T15:13:08.361Z
Learnt from: ern
Repo: sakaiproject/sakai PR: 14152
File: reset-pass/account-validator-impl/src/test/java/org/sakaiproject/accountvalidator/test/AccountValidationTestConfiguration.java:69-72
Timestamp: 2025-10-10T15:13:08.361Z
Learning: In the Sakai project, test-specific mock configurations should remain in the test class itself as part of the test logic. Only mock configurations that are common across multiple test classes should be centralized in test configuration classes.

Applied to files:

  • kernel/kernel-impl/src/test/java/org/sakaiproject/messaging/impl/UserMessagingServiceTestConfiguration.java
  • plus/impl/src/test/org/sakaiproject/plus/impl/PlusTestConfiguration.java
  • kernel/kernel-impl/src/test/java/org/sakaiproject/event/impl/test/LearningResourceTestConfiguration.java
📚 Learning: 2025-11-24T19:12:27.663Z
Learnt from: CR
Repo: sakaiproject/sakai PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-24T19:12:27.663Z
Learning: Use the Kernel's service location mechanisms to access core services such as User Management, Email Service, Authorization, Content Hosting, and Session Management

Applied to files:

  • kernel/kernel-impl/src/main/webapp/WEB-INF/event-components.xml
  • kernel/kernel-impl/src/main/webapp/WEB-INF/authz-components.xml
📚 Learning: 2025-11-24T19:12:20.537Z
Learnt from: CR
Repo: sakaiproject/sakai PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-24T19:12:20.537Z
Learning: Applies to **/UserMessagingServiceImpl.java : Use Web Push with VAPID via `UserMessagingServiceImpl.java` and the service worker in `sakai-service-worker.js`

Applied to files:

  • kernel/kernel-impl/src/test/java/org/sakaiproject/messaging/impl/UserMessagingServiceTests.java
  • kernel/kernel-impl/src/main/java/org/sakaiproject/messaging/impl/UserMessagingServiceImpl.java
📚 Learning: 2025-10-07T16:11:33.008Z
Learnt from: CR
Repo: sakaiproject/sakai PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-10-07T16:11:33.008Z
Learning: Applies to **/UserMessagingServiceImpl.java : Implement backend Web Push (VAPID) in UserMessagingServiceImpl.java

Applied to files:

  • kernel/kernel-impl/src/test/java/org/sakaiproject/messaging/impl/UserMessagingServiceTests.java
  • webapi/src/main/java/org/sakaiproject/webapi/controllers/NotificationsController.java
  • kernel/kernel-impl/src/main/java/org/sakaiproject/messaging/impl/UserMessagingServiceImpl.java
  • kernel/api/src/main/java/org/sakaiproject/messaging/api/UserMessagingService.java
📚 Learning: 2025-10-07T16:11:33.008Z
Learnt from: CR
Repo: sakaiproject/sakai PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-10-07T16:11:33.008Z
Learning: Applies to **/sakai-service-worker.js : Handle background push in sakai-service-worker.js

Applied to files:

  • kernel/kernel-impl/src/test/java/org/sakaiproject/messaging/impl/UserMessagingServiceTests.java
📚 Learning: 2025-10-07T16:11:33.008Z
Learnt from: CR
Repo: sakaiproject/sakai PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-10-07T16:11:33.008Z
Learning: Handle push token subscription updates and expirations properly

Applied to files:

  • kernel/kernel-impl/src/main/java/org/sakaiproject/messaging/impl/UserMessagingServiceImpl.java
📚 Learning: 2025-10-07T15:11:27.298Z
Learnt from: ottenhoff
Repo: sakaiproject/sakai PR: 0
File: :0-0
Timestamp: 2025-10-07T15:11:27.298Z
Learning: In samigo’s Total Scores view (samigo/samigo-app/src/webapp/jsf/evaluation/totalScores.jsp), mailto links were hidden after commit dee05746 (PR #12312, SAK-49674) added a render check requiring email.fromEmailAddress to be non-empty; PR #14154 (SAK-52058) restores visibility by checking only description.email.

Applied to files:

  • samigo/samigo-services/src/java/org/sakaiproject/tool/assessment/TestsAndQuizzesUserNotificationHandler.java
🧬 Code graph analysis (7)
kernel/kernel-impl/src/main/java/org/sakaiproject/messaging/impl/repository/UserNotificationRepositoryImpl.java (1)
reset-pass/account-validator-impl/src/main/java/org/sakaiproject/accountvalidator/impl/repository/ValidationAccountRepositoryImpl.java (1)
  • Transactional (38-91)
webapi/src/main/java/org/sakaiproject/webapi/controllers/SitesController.java (1)
kernel/api/src/main/java/org/sakaiproject/messaging/api/UserNotificationTransferBean.java (1)
  • UserNotificationTransferBean (26-60)
assignment/api/src/java/org/sakaiproject/assignment/api/AddAssignmentUserNotificationHandler.java (1)
assignment/api/src/java/org/sakaiproject/assignment/api/AssignmentConstants.java (1)
  • AssignmentConstants (31-395)
assignment/api/src/java/org/sakaiproject/assignment/api/GradeAssignmentUserNotificationHandler.java (2)
webcomponents/tool/src/main/frontend/packages/sakai-notifications/src/utils.js (1)
  • url (3-3)
assignment/api/src/java/org/sakaiproject/assignment/api/AssignmentConstants.java (1)
  • AssignmentConstants (31-395)
webapi/src/main/java/org/sakaiproject/webapi/controllers/NotificationsController.java (1)
kernel/api/src/main/java/org/sakaiproject/messaging/api/UserNotificationTransferBean.java (1)
  • UserNotificationTransferBean (26-60)
kernel/kernel-impl/src/main/java/org/sakaiproject/messaging/impl/UserMessagingServiceImpl.java (1)
kernel/api/src/main/java/org/sakaiproject/messaging/api/UserNotificationTransferBean.java (1)
  • UserNotificationTransferBean (26-60)
kernel/api/src/main/java/org/sakaiproject/messaging/api/UserNotificationTransferBean.java (1)
kernel/api/src/main/java/org/sakaiproject/messaging/api/UserNotificationData.java (1)
  • AllArgsConstructor (24-37)
⏰ Context from checks skipped due to timeout of 900000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: sakai-deploy

https://sakaiproject.atlassian.net/browse/SAK-51956

The idea behind this change is to replace the MOTD type notifications
with a single db entry rather than one for each system user.

This change also creates a SchedulingService which provides a facade
onto a ScheduledExecutorService and updates all the uses of scheduled
executors across Sakai to use it.
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: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
microsoft-integration/impl/src/main/java/org/sakaiproject/microsoft/impl/MicrosoftCommonServiceImpl.java (1)

946-994: Remove unused executor variable.

Line 947 declares ScheduledExecutorService executor = Executors.newScheduledThreadPool(1); but this variable is never used since scheduling now goes through schedulingService. This is dead code and should be removed.

🔎 Proposed fix
 	public void createTeamFromGroupAsync(String groupId) throws MicrosoftCredentialsException {
-		ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);
-		
 		AtomicInteger counter = new AtomicInteger(1);
 		final int MAX_RETRY = 5;
🤖 Fix all issues with AI Agents
In
@kernel/kernel-impl/src/main/java/org/sakaiproject/config/impl/StoredConfigService.java:
- Around line 85-88: StoredConfigService has Lombok @Setter on fields
schedulingService, serverConfigurationService, dao, and textEncryptor which
conflicts with existing manual setter methods (the duplicated setters for these
fields); to fix, remove the manual setter methods for schedulingService,
serverConfigurationService, dao, and textEncryptor so Lombok-generated setters
are the sole definitions, or alternatively remove the @Setter annotations from
those fields—prefer removing the manual setters to keep Lombok usage consistent.

In @plus/impl/src/main/webapp/WEB-INF/components.xml:
- Around line 70-74: The PlusServiceImpl bean declaration lacks the
SchedulingService bean import; open the components.xml that declares
org.sakaiproject.plus.impl.PlusServiceImpl and add an import for the
scheduling-components.xml so Spring can wire the autowired SchedulingService in
PlusServiceImpl (i.e., include the resource classpath import for
org/sakaiproject/scheduling/impl/scheduling-components.xml near the top of the
file after namespace declarations).
🧹 Nitpick comments (7)
kernel/api/src/main/java/org/sakaiproject/messaging/api/model/UserNotification.java (1)

90-91: Explicit database migration scripts are recommended for the BROADCAST column.

With Sakai's default auto.ddl=true configuration, Hibernate will automatically add the BROADCAST column to USER_NOTIFICATIONS. However, organizations that disable auto-DDL in production for stability or control may need explicit migration scripts. Consider adding optional migration scripts for HSQLDB, MySQL, and Oracle if full deployment control is desired, though the feature will work out-of-the-box with the default Sakai configuration.

kernel/kernel-impl/src/main/java/org/sakaiproject/messaging/impl/repository/UserNotificationRepositoryImpl.java (1)

52-63: Consider adding @Transactional(readOnly = true) for read consistency.

This query method would benefit from explicit transaction boundaries with read-only semantics for better performance and consistency, similar to other repository query methods.

🔎 Proposed enhancement
+@Transactional(readOnly = true)
 public List<UserNotification> findByBroadcast(boolean broadcast) {
kernel/kernel-impl/src/main/java/org/sakaiproject/scheduling/impl/SchedulingServiceImpl.java (2)

34-38: Consider validating pool size configuration.

The pool size is read from configuration without validation. If the configured value is non-positive, Executors.newScheduledThreadPool() will throw an IllegalArgumentException.

🔎 Proposed defensive check
 public void init() {
 
     int poolSize = serverConfigurationService.getInt("schedulingservice.poolsize", 4);
+    if (poolSize <= 0) {
+        log.warn("Invalid schedulingservice.poolsize ({}), using default of 4", poolSize);
+        poolSize = 4;
+    }
     executor = Executors.newScheduledThreadPool(poolSize);
 }

40-43: Consider graceful shutdown option.

shutdownNow() immediately interrupts all running tasks. Depending on the scheduled tasks' nature, you might want to consider a graceful shutdown sequence that first calls shutdown(), awaits termination with a timeout, and only then calls shutdownNow() for remaining tasks.

🔎 Proposed graceful shutdown pattern
 public void destroy() {
 
-    executor.shutdownNow();
+    executor.shutdown();
+    try {
+        if (!executor.awaitTermination(5, TimeUnit.SECONDS)) {
+            executor.shutdownNow();
+        }
+    } catch (InterruptedException e) {
+        executor.shutdownNow();
+        Thread.currentThread().interrupt();
+    }
 }
samigo/samigo-services/src/java/org/sakaiproject/tool/assessment/TestsAndQuizzesUserNotificationHandler.java (2)

196-196: Use SLF4J parameterized logging instead of string concatenation.

As per coding guidelines, log messages should use parameterized logging.

Proposed fix
-            log.error("failed to delete TestAndQuizzes userNotification alerts: " + e2.getMessage());
+            log.error("failed to delete TestAndQuizzes userNotification alerts: {}", e2.getMessage());

237-237: Use SLF4J parameterized logging instead of string concatenation.

Same issue as above - use parameterized logging.

Proposed fix
-            log.error("failed to delete TestAndQuizzes UserNotificationData data " + e3.getMessage());
+            log.error("failed to delete TestAndQuizzes UserNotificationData data: {}", e3.getMessage());
kernel/kernel-impl/src/main/java/org/sakaiproject/messaging/impl/UserMessagingServiceImpl.java (1)

137-137: Unused field: deletingExpiredNotifications is declared but never used.

This field appears to be dead code. If it was intended to track state, the implementation is missing. Otherwise, remove it.

Proposed fix
-    private boolean deletingExpiredNotifications = false;
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2f1f6bf and 5cd321d.

📒 Files selected for processing (39)
  • announcement/announcement-api/api/src/java/org/sakaiproject/announcement/api/AnnouncementsUserNotificationHandler.java
  • assignment/api/src/java/org/sakaiproject/assignment/api/AddAssignmentUserNotificationHandler.java
  • assignment/api/src/java/org/sakaiproject/assignment/api/GradeAssignmentUserNotificationHandler.java
  • commons/api/src/java/org/sakaiproject/commons/api/CommonsCommentUserNotificationHandler.java
  • config/configuration/bundles/src/bundle/org/sakaiproject/config/bundle/default.sakai.properties
  • kernel/api/src/main/java/org/sakaiproject/messaging/api/UserMessagingService.java
  • kernel/api/src/main/java/org/sakaiproject/messaging/api/UserNotificationData.java
  • kernel/api/src/main/java/org/sakaiproject/messaging/api/UserNotificationTransferBean.java
  • kernel/api/src/main/java/org/sakaiproject/messaging/api/model/UserNotification.java
  • kernel/api/src/main/java/org/sakaiproject/messaging/api/repository/UserNotificationRepository.java
  • kernel/api/src/main/java/org/sakaiproject/scheduling/api/SchedulingService.java
  • kernel/kernel-impl/src/main/java/org/sakaiproject/authz/impl/AuthzGroupServiceTest.java
  • kernel/kernel-impl/src/main/java/org/sakaiproject/authz/impl/DbAuthzGroupService.java
  • kernel/kernel-impl/src/main/java/org/sakaiproject/config/impl/StoredConfigService.java
  • kernel/kernel-impl/src/main/java/org/sakaiproject/content/impl/FileConversionServiceImpl.java
  • kernel/kernel-impl/src/main/java/org/sakaiproject/event/impl/ClusterEventTracking.java
  • kernel/kernel-impl/src/main/java/org/sakaiproject/event/impl/EventTrackingTest.java
  • kernel/kernel-impl/src/main/java/org/sakaiproject/messaging/impl/UserMessagingServiceImpl.java
  • kernel/kernel-impl/src/main/java/org/sakaiproject/messaging/impl/repository/UserNotificationRepositoryImpl.java
  • kernel/kernel-impl/src/main/java/org/sakaiproject/scheduling/impl/SchedulingServiceImpl.java
  • kernel/kernel-impl/src/main/webapp/WEB-INF/authz-components.xml
  • kernel/kernel-impl/src/main/webapp/WEB-INF/components.xml
  • kernel/kernel-impl/src/main/webapp/WEB-INF/configstore-components.xml
  • kernel/kernel-impl/src/main/webapp/WEB-INF/event-components.xml
  • kernel/kernel-impl/src/main/webapp/WEB-INF/scheduling-components.xml
  • kernel/kernel-impl/src/test/java/org/sakaiproject/content/impl/test/FileConversionServiceTestConfiguration.java
  • kernel/kernel-impl/src/test/java/org/sakaiproject/event/impl/test/LearningResourceTestConfiguration.java
  • kernel/kernel-impl/src/test/java/org/sakaiproject/messaging/impl/UserMessagingServiceTestConfiguration.java
  • kernel/kernel-impl/src/test/java/org/sakaiproject/messaging/impl/UserMessagingServiceTests.java
  • lessonbuilder/components/src/java/org/sakaiproject/lessonbuildertool/impl/AddLessonsCommentUserNotificationHandlerImpl.java
  • microsoft-integration/impl/src/main/java/org/sakaiproject/microsoft/impl/MicrosoftCommonServiceImpl.java
  • msgcntr/messageforums-api/src/java/org/sakaiproject/api/app/messageforums/PrivateMessageUserNotificationHandler.java
  • plus/impl/src/main/java/org/sakaiproject/plus/impl/PlusServiceImpl.java
  • plus/impl/src/main/webapp/WEB-INF/components.xml
  • plus/impl/src/test/org/sakaiproject/plus/impl/PlusTestConfiguration.java
  • samigo/samigo-services/src/java/org/sakaiproject/tool/assessment/TestsAndQuizzesUserNotificationHandler.java
  • webapi/src/main/java/org/sakaiproject/webapi/controllers/NotificationsController.java
  • webapi/src/main/java/org/sakaiproject/webapi/controllers/SitesController.java
  • webcomponents/tool/src/main/frontend/packages/sakai-notifications/src/SakaiNotifications.js
🚧 Files skipped from review as they are similar to previous changes (15)
  • kernel/api/src/main/java/org/sakaiproject/messaging/api/repository/UserNotificationRepository.java
  • kernel/kernel-impl/src/test/java/org/sakaiproject/messaging/impl/UserMessagingServiceTestConfiguration.java
  • config/configuration/bundles/src/bundle/org/sakaiproject/config/bundle/default.sakai.properties
  • webcomponents/tool/src/main/frontend/packages/sakai-notifications/src/SakaiNotifications.js
  • msgcntr/messageforums-api/src/java/org/sakaiproject/api/app/messageforums/PrivateMessageUserNotificationHandler.java
  • assignment/api/src/java/org/sakaiproject/assignment/api/GradeAssignmentUserNotificationHandler.java
  • kernel/kernel-impl/src/test/java/org/sakaiproject/content/impl/test/FileConversionServiceTestConfiguration.java
  • kernel/kernel-impl/src/test/java/org/sakaiproject/messaging/impl/UserMessagingServiceTests.java
  • kernel/kernel-impl/src/main/java/org/sakaiproject/authz/impl/AuthzGroupServiceTest.java
  • kernel/kernel-impl/src/main/webapp/WEB-INF/components.xml
  • kernel/api/src/main/java/org/sakaiproject/messaging/api/UserNotificationTransferBean.java
  • lessonbuilder/components/src/java/org/sakaiproject/lessonbuildertool/impl/AddLessonsCommentUserNotificationHandlerImpl.java
  • kernel/kernel-impl/src/main/webapp/WEB-INF/scheduling-components.xml
  • webapi/src/main/java/org/sakaiproject/webapi/controllers/SitesController.java
  • kernel/kernel-impl/src/main/webapp/WEB-INF/authz-components.xml
🧰 Additional context used
📓 Path-based instructions (6)
**/*.java

📄 CodeRabbit inference engine (.cursor/rules/logging-rule.mdc)

**/*.java: Use SLF4J parameterized logging (logger.info("Value is: {}", value)) instead of string concatenation (logger.info("Value is: " + value))
Log messages and code comments should be in English. Log messages should never be translated.

**/*.java: Java: Never use local variable type inference (var). Always declare explicit types. Yes: Map<String, Integer> counts = new HashMap<>(); No: var counts = new HashMap<String, Integer>();
When proposing Java code, spell out full types in local variable declarations, for loops, and try-with-resources
When editing Java, prefer clarity over brevity; avoid introducing language features that aren't widely used in the repo
Treat any PR or suggestion containing Java var as non-compliant. Recommend replacing with explicit types before merge

**/*.java: Use Java 17 for trunk development (Java 11 was used for Sakai 22 and Sakai 23)
Do not use local variable type inference (var) in Java code. Always declare explicit types (e.g., List<String> names = new ArrayList<>(); not var names = new ArrayList<String>();). Enforced by Checkstyle rule during mvn validate

Files:

  • webapi/src/main/java/org/sakaiproject/webapi/controllers/NotificationsController.java
  • plus/impl/src/test/org/sakaiproject/plus/impl/PlusTestConfiguration.java
  • kernel/kernel-impl/src/test/java/org/sakaiproject/event/impl/test/LearningResourceTestConfiguration.java
  • kernel/api/src/main/java/org/sakaiproject/messaging/api/model/UserNotification.java
  • plus/impl/src/main/java/org/sakaiproject/plus/impl/PlusServiceImpl.java
  • kernel/api/src/main/java/org/sakaiproject/scheduling/api/SchedulingService.java
  • kernel/kernel-impl/src/main/java/org/sakaiproject/messaging/impl/repository/UserNotificationRepositoryImpl.java
  • kernel/kernel-impl/src/main/java/org/sakaiproject/event/impl/EventTrackingTest.java
  • kernel/kernel-impl/src/main/java/org/sakaiproject/event/impl/ClusterEventTracking.java
  • commons/api/src/java/org/sakaiproject/commons/api/CommonsCommentUserNotificationHandler.java
  • kernel/kernel-impl/src/main/java/org/sakaiproject/authz/impl/DbAuthzGroupService.java
  • kernel/api/src/main/java/org/sakaiproject/messaging/api/UserNotificationData.java
  • kernel/kernel-impl/src/main/java/org/sakaiproject/content/impl/FileConversionServiceImpl.java
  • kernel/kernel-impl/src/main/java/org/sakaiproject/messaging/impl/UserMessagingServiceImpl.java
  • microsoft-integration/impl/src/main/java/org/sakaiproject/microsoft/impl/MicrosoftCommonServiceImpl.java
  • assignment/api/src/java/org/sakaiproject/assignment/api/AddAssignmentUserNotificationHandler.java
  • kernel/api/src/main/java/org/sakaiproject/messaging/api/UserMessagingService.java
  • samigo/samigo-services/src/java/org/sakaiproject/tool/assessment/TestsAndQuizzesUserNotificationHandler.java
  • kernel/kernel-impl/src/main/java/org/sakaiproject/scheduling/impl/SchedulingServiceImpl.java
  • kernel/kernel-impl/src/main/java/org/sakaiproject/config/impl/StoredConfigService.java
  • announcement/announcement-api/api/src/java/org/sakaiproject/announcement/api/AnnouncementsUserNotificationHandler.java
**/*.{html,jsp,vm,xhtml,xml}

📄 CodeRabbit inference engine (.cursor/rules/frontend.mdc)

**/*.{html,jsp,vm,xhtml,xml}: Use Bootstrap 5.2 as the preferred UI framework for styling in Sakai frontends
Leverage Bootstrap 5 components for consistent UI/UX

Files:

  • kernel/kernel-impl/src/main/webapp/WEB-INF/event-components.xml
  • kernel/kernel-impl/src/main/webapp/WEB-INF/configstore-components.xml
  • plus/impl/src/main/webapp/WEB-INF/components.xml
**/*.{html,jsp,vm,xhtml,xml,css,scss}

📄 CodeRabbit inference engine (.cursor/rules/frontend.mdc)

Ensure all UI components work across different screen sizes (Responsive Design)

Files:

  • kernel/kernel-impl/src/main/webapp/WEB-INF/event-components.xml
  • kernel/kernel-impl/src/main/webapp/WEB-INF/configstore-components.xml
  • plus/impl/src/main/webapp/WEB-INF/components.xml
**/*.{js,html,jsp,vm,xhtml,xml}

📄 CodeRabbit inference engine (.cursor/rules/frontend.mdc)

Use the web components in the webcomponents/ directory when possible in Sakai frontends

Files:

  • kernel/kernel-impl/src/main/webapp/WEB-INF/event-components.xml
  • kernel/kernel-impl/src/main/webapp/WEB-INF/configstore-components.xml
  • plus/impl/src/main/webapp/WEB-INF/components.xml
**/*.{html,jsp,jspx,xml,ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

Prefer kebab-case for values of HTML class and id attributes

Files:

  • kernel/kernel-impl/src/main/webapp/WEB-INF/event-components.xml
  • kernel/kernel-impl/src/main/webapp/WEB-INF/configstore-components.xml
  • plus/impl/src/main/webapp/WEB-INF/components.xml
{**/UserMessagingServiceImpl.java,**/sakai-service-worker.js}

📄 CodeRabbit inference engine (AGENTS.md)

Use Web Push with VAPID via UserMessagingServiceImpl.java; rely on the service worker in sakai-service-worker.js

Files:

  • kernel/kernel-impl/src/main/java/org/sakaiproject/messaging/impl/UserMessagingServiceImpl.java
🧠 Learnings (12)
📓 Common learnings
Learnt from: CR
Repo: sakaiproject/sakai PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-24T19:12:27.663Z
Learning: Applies to **/sakai-notifications.properties : Reconcile subscriptions proactively and keep localization strings in `sakai-notifications.properties`
Learnt from: CR
Repo: sakaiproject/sakai PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-24T19:12:20.537Z
Learning: Applies to **/*.{java,properties} : Reconcile push notification subscriptions proactively and keep localization strings in `sakai-notifications.properties`
Learnt from: CR
Repo: sakaiproject/sakai PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-10-07T16:11:33.008Z
Learning: Applies to **/sakai-notifications.properties : Use sakai-notifications.properties for internationalized PWA installation messages
📚 Learning: 2025-10-07T16:11:33.008Z
Learnt from: CR
Repo: sakaiproject/sakai PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-10-07T16:11:33.008Z
Learning: Applies to **/UserMessagingServiceImpl.java : Implement backend Web Push (VAPID) in UserMessagingServiceImpl.java

Applied to files:

  • webapi/src/main/java/org/sakaiproject/webapi/controllers/NotificationsController.java
  • kernel/kernel-impl/src/main/java/org/sakaiproject/messaging/impl/UserMessagingServiceImpl.java
  • kernel/api/src/main/java/org/sakaiproject/messaging/api/UserMessagingService.java
📚 Learning: 2025-10-10T15:13:08.361Z
Learnt from: ern
Repo: sakaiproject/sakai PR: 14152
File: reset-pass/account-validator-impl/src/test/java/org/sakaiproject/accountvalidator/test/AccountValidationTestConfiguration.java:69-72
Timestamp: 2025-10-10T15:13:08.361Z
Learning: In the Sakai project, test-specific mock configurations should remain in the test class itself as part of the test logic. Only mock configurations that are common across multiple test classes should be centralized in test configuration classes.

Applied to files:

  • plus/impl/src/test/org/sakaiproject/plus/impl/PlusTestConfiguration.java
  • kernel/kernel-impl/src/test/java/org/sakaiproject/event/impl/test/LearningResourceTestConfiguration.java
📚 Learning: 2025-11-24T19:12:27.663Z
Learnt from: CR
Repo: sakaiproject/sakai PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-24T19:12:27.663Z
Learning: Applies to **/sakai-notifications.properties : Reconcile subscriptions proactively and keep localization strings in `sakai-notifications.properties`

Applied to files:

  • kernel/kernel-impl/src/main/java/org/sakaiproject/messaging/impl/repository/UserNotificationRepositoryImpl.java
  • kernel/kernel-impl/src/main/java/org/sakaiproject/messaging/impl/UserMessagingServiceImpl.java
  • announcement/announcement-api/api/src/java/org/sakaiproject/announcement/api/AnnouncementsUserNotificationHandler.java
📚 Learning: 2025-11-24T19:12:20.537Z
Learnt from: CR
Repo: sakaiproject/sakai PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-24T19:12:20.537Z
Learning: Applies to **/*.{java,properties} : Reconcile push notification subscriptions proactively and keep localization strings in `sakai-notifications.properties`

Applied to files:

  • kernel/kernel-impl/src/main/java/org/sakaiproject/messaging/impl/repository/UserNotificationRepositoryImpl.java
  • kernel/kernel-impl/src/main/java/org/sakaiproject/messaging/impl/UserMessagingServiceImpl.java
  • announcement/announcement-api/api/src/java/org/sakaiproject/announcement/api/AnnouncementsUserNotificationHandler.java
📚 Learning: 2025-11-24T19:12:27.663Z
Learnt from: CR
Repo: sakaiproject/sakai PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-24T19:12:27.663Z
Learning: Use the Kernel's service location mechanisms to access core services such as User Management, Email Service, Authorization, Content Hosting, and Session Management

Applied to files:

  • kernel/kernel-impl/src/main/webapp/WEB-INF/event-components.xml
📚 Learning: 2025-11-24T19:12:27.663Z
Learnt from: CR
Repo: sakaiproject/sakai PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-24T19:12:27.663Z
Learning: Applies to {**/UserMessagingServiceImpl.java,**/sakai-service-worker.js} : Use Web Push with VAPID via `UserMessagingServiceImpl.java`; rely on the service worker in `sakai-service-worker.js`

Applied to files:

  • kernel/kernel-impl/src/main/java/org/sakaiproject/messaging/impl/UserMessagingServiceImpl.java
  • kernel/api/src/main/java/org/sakaiproject/messaging/api/UserMessagingService.java
📚 Learning: 2025-11-24T19:12:20.537Z
Learnt from: CR
Repo: sakaiproject/sakai PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-24T19:12:20.537Z
Learning: Applies to **/UserMessagingServiceImpl.java : Use Web Push with VAPID via `UserMessagingServiceImpl.java` and the service worker in `sakai-service-worker.js`

Applied to files:

  • kernel/kernel-impl/src/main/java/org/sakaiproject/messaging/impl/UserMessagingServiceImpl.java
📚 Learning: 2025-10-07T16:11:33.008Z
Learnt from: CR
Repo: sakaiproject/sakai PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-10-07T16:11:33.008Z
Learning: Applies to **/sakai-notifications.properties : Use sakai-notifications.properties for internationalized PWA installation messages

Applied to files:

  • kernel/kernel-impl/src/main/java/org/sakaiproject/messaging/impl/UserMessagingServiceImpl.java
📚 Learning: 2025-10-07T16:11:33.008Z
Learnt from: CR
Repo: sakaiproject/sakai PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-10-07T16:11:33.008Z
Learning: Handle push token subscription updates and expirations properly

Applied to files:

  • kernel/kernel-impl/src/main/java/org/sakaiproject/messaging/impl/UserMessagingServiceImpl.java
📚 Learning: 2025-09-16T04:03:17.598Z
Learnt from: kunaljaykam
Repo: sakaiproject/sakai PR: 13974
File: syllabus/syllabus-impl/src/webapp/WEB-INF/components.xml:80-90
Timestamp: 2025-09-16T04:03:17.598Z
Learning: In Sakai's search infrastructure, content producers like SyllabusContentProducer don't require explicit destroy-method configuration in Spring XML. The search framework handles cleanup automatically, and adding destroy-method when no destroy() method exists would cause configuration errors.

Applied to files:

  • plus/impl/src/main/webapp/WEB-INF/components.xml
📚 Learning: 2025-10-07T15:11:27.298Z
Learnt from: ottenhoff
Repo: sakaiproject/sakai PR: 0
File: :0-0
Timestamp: 2025-10-07T15:11:27.298Z
Learning: In samigo’s Total Scores view (samigo/samigo-app/src/webapp/jsf/evaluation/totalScores.jsp), mailto links were hidden after commit dee05746 (PR #12312, SAK-49674) added a render check requiring email.fromEmailAddress to be non-empty; PR #14154 (SAK-52058) restores visibility by checking only description.email.

Applied to files:

  • samigo/samigo-services/src/java/org/sakaiproject/tool/assessment/TestsAndQuizzesUserNotificationHandler.java
🧬 Code graph analysis (3)
kernel/kernel-impl/src/main/java/org/sakaiproject/messaging/impl/repository/UserNotificationRepositoryImpl.java (1)
reset-pass/account-validator-impl/src/main/java/org/sakaiproject/accountvalidator/impl/repository/ValidationAccountRepositoryImpl.java (1)
  • Transactional (38-91)
commons/api/src/java/org/sakaiproject/commons/api/CommonsCommentUserNotificationHandler.java (1)
webcomponents/tool/src/main/frontend/packages/sakai-notifications/src/utils.js (1)
  • url (3-3)
samigo/samigo-services/src/java/org/sakaiproject/tool/assessment/TestsAndQuizzesUserNotificationHandler.java (1)
webcomponents/tool/src/main/frontend/packages/sakai-notifications/src/utils.js (1)
  • url (3-3)
⏰ Context from checks skipped due to timeout of 900000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: sakai-deploy
🔇 Additional comments (34)
kernel/kernel-impl/src/main/java/org/sakaiproject/event/impl/ClusterEventTracking.java (2)

44-44: LGTM! Clean dependency injection pattern.

The SchedulingService import and abstract method follow the established pattern for collaborator dependencies in this class.

Also applies to: 105-106


226-231: LGTM! Successful migration to SchedulingService.

The replacement of the local scheduler with schedulingService().scheduleWithFixedDelay(...) correctly maintains the original scheduling behavior with a 60-second initial delay and periodic execution every m_period seconds.

kernel/kernel-impl/src/main/java/org/sakaiproject/event/impl/EventTrackingTest.java (1)

31-31: LGTM! Proper test collaborator implementation.

The SchedulingService test dependency follows the same pattern as other collaborator methods in this test class, returning null to satisfy the abstract method requirement.

Also applies to: 94-101

microsoft-integration/impl/src/main/java/org/sakaiproject/microsoft/impl/MicrosoftCommonServiceImpl.java (1)

113-113: LGTM! SchedulingService dependency added.

The import and setter follow the established Lombok pattern used for other dependencies in this class.

Also applies to: 194-194

plus/impl/src/test/org/sakaiproject/plus/impl/PlusTestConfiguration.java (1)

31-31: LGTM! Consistent test configuration pattern.

The SchedulingService mock bean follows the same structure as other mock dependencies in this test configuration class, providing a common mock that can be used across multiple tests.

Also applies to: 100-104

kernel/kernel-impl/src/main/webapp/WEB-INF/configstore-components.xml (1)

43-46: LGTM!

The SchedulingService injection is correctly configured. The removal of destroy-method suggests that lifecycle management (likely scheduled cleanup tasks) is now delegated to the SchedulingService facade.

kernel/kernel-impl/src/test/java/org/sakaiproject/event/impl/test/LearningResourceTestConfiguration.java (1)

45-45: LGTM!

The SchedulingService mock bean follows the established pattern in this shared test configuration. This is appropriate for a configuration class used across multiple tests.

Also applies to: 200-203

webapi/src/main/java/org/sakaiproject/webapi/controllers/NotificationsController.java (1)

27-27: LGTM!

The change to UserNotificationTransferBean aligns with the service layer API change and follows best practices by exposing a transfer object rather than a JPA entity through the REST API.

Also applies to: 41-41

kernel/api/src/main/java/org/sakaiproject/messaging/api/UserMessagingService.java (1)

66-66: No action needed. The API change is already complete: the interface returns List<UserNotificationTransferBean>, the implementation correctly converts entities to transfer beans, and all callers (SitesController, NotificationsController, and tests) already handle the new type correctly.

kernel/api/src/main/java/org/sakaiproject/messaging/api/model/UserNotification.java (1)

70-71: Remove claim about nullable constraint change—cannot be verified.

The review claims the URL column "previously had nullable = false but now uses the default nullable = true." However, no evidence supports this: the current code does not specify nullable = false, no database migration exists, and no prior state is accessible. All existing code always populates the url field when creating UserNotificationData objects, indicating it's treated as required in practice despite the schema default.

If this change actually occurred, a critical database migration is missing. If it did not occur, the review comment is unsubstantiated.

Likely an incorrect or invalid review comment.

kernel/kernel-impl/src/main/webapp/WEB-INF/event-components.xml (1)

37-37: LGTM! SchedulingService lookup-method added correctly.

The lookup-method addition follows the established pattern for service dependencies in this bean configuration. This allows EventTrackingService to obtain the SchedulingService on-demand through Spring's method injection.

commons/api/src/java/org/sakaiproject/commons/api/CommonsCommentUserNotificationHandler.java (2)

110-110: Consistent with Line 94 – verify constructor parameters.

This notification for other commenters uses the same parameter values (false, null) as the post author notification, which is correct for consistency. Both are targeted notifications rather than broadcasts.


94-94: The constructor parameters are correctly used for comment notifications.

The UserNotificationData class (kernel/api/src/main/java/org/sakaiproject/messaging/api/UserNotificationData.java) confirms that:

  • The false parameter correctly sets broadcast = false, indicating these are targeted (non-broadcast) notifications appropriate for individual comment notifications
  • The null parameter correctly sets ttl = null (Duration field), representing no TTL expiry for comment notifications

The hardcoded values are appropriate.

plus/impl/src/main/java/org/sakaiproject/plus/impl/PlusServiceImpl.java (3)

105-105: LGTM! SchedulingService import added.

The import statement correctly references the new SchedulingService API interface.


149-149: LGTM! SchedulingService autowired correctly.

The SchedulingService dependency is properly autowired following Spring conventions. Ensure that the corresponding Spring configuration (components.xml) imports the scheduling-components.xml to make this bean available.


156-172: LGTM! Migration to SchedulingService completed successfully.

The migration from direct ScheduledExecutorService usage to the new SchedulingService abstraction is correctly implemented:

  • Initial delay: 120 seconds (allows Sakai to start)
  • Fixed delay: 60 seconds (from refreshTaskInterval)
  • Explicit TimeUnit.SECONDS parameter added

The scheduled task (refreshContextMembershipsTask) remains unchanged, ensuring behavioral consistency.

kernel/kernel-impl/src/main/java/org/sakaiproject/authz/impl/DbAuthzGroupService.java (3)

63-63: LGTM! SchedulingService import added.

The import statement correctly references the new SchedulingService API interface.


128-129: LGTM! Abstract accessor method for SchedulingService.

The abstract method declaration follows the Spring lookup-method injection pattern. This method will be implemented by Spring at runtime based on the <lookup-method> configuration in event-components.xml (Line 37).


256-302: LGTM! Migration to SchedulingService completed successfully.

The migration from direct ScheduledExecutorService usage to the new SchedulingService abstraction is correctly implemented:

  • Uses schedulingService() method call (Spring lookup-method pattern)
  • Initial delay: 120 seconds (allows Sakai to start)
  • Fixed delay: refreshTaskInterval (defaults to 60 seconds)
  • Explicit TimeUnit.SECONDS parameter added

The scheduled task (RefreshAuthzGroupTask) remains unchanged, ensuring behavioral consistency. The configuration in event-components.xml properly supports this via the lookup-method.

kernel/kernel-impl/src/main/java/org/sakaiproject/content/impl/FileConversionServiceImpl.java (1)

44-44: LGTM! Clean migration to centralized SchedulingService.

The replacement of the local master scheduler with the centralized SchedulingService is well-executed. The field injection, method call, and lifecycle management are all correct.

Also applies to: 58-58, 114-114

assignment/api/src/java/org/sakaiproject/assignment/api/AddAssignmentUserNotificationHandler.java (1)

130-130: LGTM! Consistent adaptation to extended constructor.

All three call sites correctly pass false for the broadcast flag and null for the extra payload, which is appropriate for individual user notifications in the assignment context.

Also applies to: 167-167, 181-181

kernel/api/src/main/java/org/sakaiproject/messaging/api/UserNotificationData.java (2)

20-26: LGTM! Appropriate Lombok annotations for a DTO.

The shift to @Data with both @NoArgsConstructor and @AllArgsConstructor is standard for data transfer objects, providing full accessor methods and multiple construction options.


35-36: Conversion logic from ttl to endDate is properly implemented in the persistence layer.

The ttl field (Duration) in UserNotificationData is converted to endDate (Instant) in UserNotification during entity creation. In UserMessagingServiceImpl, the conversion occurs with null-safety: if (und.getTtl() != null) { ba.setEndDate(eventDate.toInstant().plus(und.getTtl())); }. This correctly adds the TTL duration to the event timestamp to calculate the end date for persistence.

kernel/kernel-impl/src/main/java/org/sakaiproject/scheduling/impl/SchedulingServiceImpl.java (1)

45-58: LGTM! Clean delegation to underlying executor.

Both scheduling methods correctly delegate to the ScheduledExecutorService and return the ScheduledFuture handles. The thin-wrapper approach is appropriate for this facade pattern.

kernel/kernel-impl/src/main/java/org/sakaiproject/messaging/impl/repository/UserNotificationRepositoryImpl.java (1)

65-77: The UserNotification entity includes the endDate field (mapped to END_DATE column as Instant type), so the CriteriaDelete query in deleteExpiredNotifications() is correct.

samigo/samigo-services/src/java/org/sakaiproject/tool/assessment/TestsAndQuizzesUserNotificationHandler.java (1)

168-168: LGTM! Constructor updates align with the extended UserNotificationData signature.

The new parameters (false, null) correctly indicate these are user-specific notifications (not broadcasts) with no TTL expiration, consistent with the pattern across other handlers.

Also applies to: 210-210

kernel/kernel-impl/src/main/java/org/sakaiproject/config/impl/StoredConfigService.java (1)

129-139: Migration to SchedulingService looks correct.

The scheduling logic is properly migrated from a local ScheduledExecutorService to the centralized SchedulingService. The schedule parameters (initial delay, period, time unit) are preserved.

kernel/kernel-impl/src/main/java/org/sakaiproject/messaging/impl/UserMessagingServiceImpl.java (3)

162-170: Scheduled cleanup of expired notifications looks correct.

The task runs every minute and deletes expired notifications within a transaction. This aligns with the TTL-based expiration model.


379-400: Notification handling updated to use bean-based workflow.

The flow correctly:

  1. Inserts the notification via doInsert
  2. Converts to UserNotificationTransferBean
  3. Decorates and pushes (broadcast vs. single-user path)

415-448: TTL handling via endDate is correctly implemented.

The doInsert method properly calculates and sets the endDate from the TTL duration when present.

announcement/announcement-api/api/src/java/org/sakaiproject/announcement/api/AnnouncementsUserNotificationHandler.java (4)

88-93: MOTD TTL configuration via @PostConstruct is well implemented.

The configurable TTL (announcement.motd.notification.ttlhours, default 24 hours) provides flexibility for administrators.


113-116: Correct early exit to prevent duplicate MOTD processing.

The guard ensures SECURE_ANNC_ADD events for MOTD are ignored since they're handled by the dedicated EVENT_MOTD_NEW path.


123-133: MOTD broadcast notification with TTL is correctly implemented.

The broadcast notification:

  • Sets to to "BROADCAST"
  • Sets broadcast to true
  • Applies configurable TTL via Duration.ofHours(motdTTLHours)

This aligns with the PR objective to replace per-user MOTD entries with a single broadcast notification.


233-237: Notification creation for site announcements is correctly implemented.

The filtering logic properly:

  1. Excludes the event originator
  2. Excludes superusers
  3. Uses the updated UserNotificationData constructor with broadcast=false and ttl=null

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: 3

🤖 Fix all issues with AI Agents
In
@announcement/announcement-api/api/src/java/org/sakaiproject/announcement/api/AnnouncementsUserNotificationHandler.java:
- Around line 159-171: The Predicate eventMatcher built against eventQueryTable
(used by the SELECT CriteriaQuery) must not be reused for the DELETE; create a
new Root for the DELETE and build a new Predicate from that root (or extract
predicate creation into a helper like
createEventMatcherPredicate(CriteriaBuilder cb, Root<UserNotification> root,
String eventResource) and call it for both queries). Replace reuse of
eventMatcher in the CriteriaDelete<UserNotification> eventDeleteQuery with a
predicate created from the delete root, and ensure
eventDeleteQuery.from(UserNotification.class) is used before applying the
predicate so eventDeleteQuery.where(...) uses a predicate bound to its own root.
- Around line 198-200: The code eagerly loads all users via
userDirectoryService.getUsers() when an MOTD availability event
(EVENT_AVAILABLE_ANNC or EVENT_ANNC_UPDATE_AVAILABILITY) occurs, which will OOM
at scale; replace this per-user loading with the same broadcast mechanism used
for EVENT_MOTD_NEW (see the logic around lines 124-134) so availability changes
are sent as a broadcast instead of building usersToNotify, or if broadcast is
unsuitable, change the usersToNotify population to a streaming/paged approach
(avoid userDirectoryService.getUsers() returning a full list) so you never
materialize the entire user set; update AnnouncementsUserNotificationHandler to
detect SiteService.ADMIN_SITE_ID && eventResource.contains("motd") and either
call the existing MOTD broadcast path or iterate users via a paging iterator to
send notifications.

In
@kernel/api/src/main/java/org/sakaiproject/scheduling/api/SchedulingService.java:
- Around line 25-49: Add a scheduleAtFixedRate method to the SchedulingService
interface to match ScheduledExecutorService semantics: declare
ScheduledFuture<?> scheduleAtFixedRate(Runnable command, long initialDelay, long
period, TimeUnit unit) with Javadoc similar to scheduleWithFixedDelay; then
update every implementation of SchedulingService to implement this new method
and adjust callers (e.g., TimedAssessmentQueue, PrecachingDbUserService) to use
the injected SchedulingService.scheduleAtFixedRate instead of calling
ScheduledExecutorService.scheduleAtFixedRate directly so the abstraction is
complete and consistent with existing schedule and scheduleWithFixedDelay
methods.
🧹 Nitpick comments (3)
kernel/api/src/main/java/org/sakaiproject/messaging/api/UserMessagingService.java (1)

24-24: Remove the unused import for UserNotification.

After the return type change on line 65, UserNotification is no longer referenced in this interface. Clean up by removing this import.

🔎 Proposed fix
-import org.sakaiproject.messaging.api.model.UserNotification;
kernel/api/src/main/java/org/sakaiproject/scheduling/api/SchedulingService.java (2)

27-34: Consider adding @return documentation.

The method documentation is clear but missing a @return tag to describe what the returned ScheduledFuture<?> represents. Consider adding documentation like: @return a ScheduledFuture representing pending completion of the task.

🔎 Proposed enhancement
     /**
      * Schedule the Runnable to be executed after delay units, that is delay number of time units.
      *
      * @param command The command to run
      * @param delay The time, in units, to wait until execution of command
      * @param unit The type of time unit to use.
+     * @return a ScheduledFuture representing pending completion of the task
      */
     ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit);

36-48: Consider adding @return documentation.

Similar to the schedule method, this method is missing a @return tag. Consider documenting the returned ScheduledFuture<?>.

🔎 Proposed enhancement
     /**
      * Schedule the Runnable to be executed after initialDelay units and then every delay units
      * thereafter.
      *
      * @param command The command to run
      * @param initialDelay The time, in units, to wait until execution of command
      * @param delay The time, in units, to wait between each further execution of command
      * @param unit The type of time unit to use.
+     * @return a ScheduledFuture representing pending completion of the task
      */
     ScheduledFuture<?> scheduleWithFixedDelay(Runnable command,
                                                 long initialDelay,
                                                 long delay,
                                                 TimeUnit unit);
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5cd321d and c561570.

📒 Files selected for processing (3)
  • announcement/announcement-api/api/src/java/org/sakaiproject/announcement/api/AnnouncementsUserNotificationHandler.java
  • kernel/api/src/main/java/org/sakaiproject/messaging/api/UserMessagingService.java
  • kernel/api/src/main/java/org/sakaiproject/scheduling/api/SchedulingService.java
🧰 Additional context used
📓 Path-based instructions (1)
**/*.java

📄 CodeRabbit inference engine (.cursor/rules/logging-rule.mdc)

**/*.java: Use SLF4J parameterized logging (logger.info("Value is: {}", value)) instead of string concatenation (logger.info("Value is: " + value))
Log messages and code comments should be in English. Log messages should never be translated.

**/*.java: Java: Never use local variable type inference (var). Always declare explicit types. Yes: Map<String, Integer> counts = new HashMap<>(); No: var counts = new HashMap<String, Integer>();
When proposing Java code, spell out full types in local variable declarations, for loops, and try-with-resources
When editing Java, prefer clarity over brevity; avoid introducing language features that aren't widely used in the repo
Treat any PR or suggestion containing Java var as non-compliant. Recommend replacing with explicit types before merge

**/*.java: Use Java 17 for trunk development (Java 11 was used for Sakai 22 and Sakai 23)
Do not use local variable type inference (var) in Java code. Always declare explicit types (e.g., List<String> names = new ArrayList<>(); not var names = new ArrayList<String>();). Enforced by Checkstyle rule during mvn validate

Files:

  • kernel/api/src/main/java/org/sakaiproject/messaging/api/UserMessagingService.java
  • announcement/announcement-api/api/src/java/org/sakaiproject/announcement/api/AnnouncementsUserNotificationHandler.java
  • kernel/api/src/main/java/org/sakaiproject/scheduling/api/SchedulingService.java
🧠 Learnings (3)
📓 Common learnings
Learnt from: CR
Repo: sakaiproject/sakai PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-24T19:12:27.663Z
Learning: Applies to **/sakai-notifications.properties : Reconcile subscriptions proactively and keep localization strings in `sakai-notifications.properties`
Learnt from: CR
Repo: sakaiproject/sakai PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-24T19:12:20.537Z
Learning: Applies to **/*.{java,properties} : Reconcile push notification subscriptions proactively and keep localization strings in `sakai-notifications.properties`
Learnt from: CR
Repo: sakaiproject/sakai PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-10-07T16:11:33.008Z
Learning: Applies to **/sakai-notifications.properties : Use sakai-notifications.properties for internationalized PWA installation messages
📚 Learning: 2025-11-24T19:12:27.663Z
Learnt from: CR
Repo: sakaiproject/sakai PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-24T19:12:27.663Z
Learning: Applies to {**/UserMessagingServiceImpl.java,**/sakai-service-worker.js} : Use Web Push with VAPID via `UserMessagingServiceImpl.java`; rely on the service worker in `sakai-service-worker.js`

Applied to files:

  • kernel/api/src/main/java/org/sakaiproject/messaging/api/UserMessagingService.java
📚 Learning: 2025-11-24T19:12:27.663Z
Learnt from: CR
Repo: sakaiproject/sakai PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-24T19:12:27.663Z
Learning: Applies to **/sakai-notifications.properties : Reconcile subscriptions proactively and keep localization strings in `sakai-notifications.properties`

Applied to files:

  • announcement/announcement-api/api/src/java/org/sakaiproject/announcement/api/AnnouncementsUserNotificationHandler.java
⏰ Context from checks skipped due to timeout of 900000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: sakai-deploy
  • GitHub Check: maven-build
  • GitHub Check: maven-build
🔇 Additional comments (4)
kernel/api/src/main/java/org/sakaiproject/messaging/api/UserMessagingService.java (1)

65-65: Breaking API change is fully implemented and all implementations and consumers have been updated consistently.

The return type change from List<UserNotification> to List<UserNotificationTransferBean> has been properly applied across all layers: the interface definition, the main implementation (UserMessagingServiceImpl), controller overrides, and all call sites. Tests have been updated accordingly, and the repository layer appropriately remains isolated with its own entity types.

kernel/api/src/main/java/org/sakaiproject/scheduling/api/SchedulingService.java (1)

21-24: LGTM! Typo fixed.

The Javadoc is clear, and the property name typo from the previous review has been corrected.

announcement/announcement-api/api/src/java/org/sakaiproject/announcement/api/AnnouncementsUserNotificationHandler.java (2)

87-94: LGTM: MOTD TTL configuration.

The PostConstruct initialization reads the TTL configuration with a sensible default of 24 hours. The field and method follow standard conventions.


124-134: LGTM: MOTD broadcast notification.

The EVENT_MOTD_NEW handling correctly creates a single broadcast notification instead of per-user entries, which addresses the scalability goal mentioned in the PR objectives. The TTL is properly configured using the initialized motdTTLHours value.

Comment on lines +25 to +49
public interface SchedulingService {

/**
* Schedule the Runnable to be executed after delay units, that is delay number of time units.
*
* @param command The command to run
* @param delay The time, in units, to wait until execution of command
* @param unit The type of time unit to use.
*/
ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit);

/**
* Schedule the Runnable to be executed after initialDelay units and then every delay units
* thereafter.
*
* @param command The command to run
* @param initialDelay The time, in units, to wait until execution of command
* @param delay The time, in units, to wait between each further execution of command
* @param unit The type of time unit to use.
*/
ScheduledFuture<?> scheduleWithFixedDelay(Runnable command,
long initialDelay,
long delay,
TimeUnit unit);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Search for scheduleAtFixedRate usage to determine if it's needed
rg -n --type java 'scheduleAtFixedRate' -C 3

Repository: sakaiproject/sakai

Length of output: 2701


🏁 Script executed:

#!/bin/bash
# Find and examine the SchedulingServiceImpl implementation
find . -name 'SchedulingServiceImpl.java' -type f

Repository: sakaiproject/sakai

Length of output: 156


🏁 Script executed:

#!/bin/bash
# Search for any comments or documentation about the scheduling API design
rg -n --type java 'SchedulingService' | head -20

Repository: sakaiproject/sakai

Length of output: 3163


🏁 Script executed:

#!/bin/bash
# Read the SchedulingServiceImpl to understand its implementation
cat -n ./kernel/kernel-impl/src/main/java/org/sakaiproject/scheduling/impl/SchedulingServiceImpl.java

Repository: sakaiproject/sakai

Length of output: 2513


Add scheduleAtFixedRate to SchedulingService interface for consistency.

The limited API surface (only schedule and scheduleWithFixedDelay) is intentional, but the codebase contains examples where components need scheduleAtFixedRate and bypass SchedulingService entirely:

  • TimedAssessmentQueue.java:84 calls threadPool.scheduleAtFixedRate()
  • PrecachingDbUserService.java:143 calls dailyTimer.scheduleAtFixedRate()

This creates inconsistency where some components use the injected SchedulingService (e.g., PlusServiceImpl, MicrosoftCommonServiceImpl) while others manage their own ScheduledExecutorService instances to access missing scheduling methods. Adding scheduleAtFixedRate to the interface would provide a complete scheduling abstraction and eliminate these workarounds.

🤖 Prompt for AI Agents
In
@kernel/api/src/main/java/org/sakaiproject/scheduling/api/SchedulingService.java
around lines 25 - 49, Add a scheduleAtFixedRate method to the SchedulingService
interface to match ScheduledExecutorService semantics: declare
ScheduledFuture<?> scheduleAtFixedRate(Runnable command, long initialDelay, long
period, TimeUnit unit) with Javadoc similar to scheduleWithFixedDelay; then
update every implementation of SchedulingService to implement this new method
and adjust callers (e.g., TimedAssessmentQueue, PrecachingDbUserService) to use
the injected SchedulingService.scheduleAtFixedRate instead of calling
ScheduledExecutorService.scheduleAtFixedRate directly so the abstraction is
complete and consistent with existing schedule and scheduleWithFixedDelay
methods.

@adrianfish adrianfish marked this pull request as draft January 7, 2026 15:08
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

🤖 Fix all issues with AI agents
In
@announcement/announcement-api/api/src/java/org/sakaiproject/announcement/api/AnnouncementsUserNotificationHandler.java:
- Around line 158-171: The predicate eventMatcher was built against the SELECT
root eventQueryTable and then reused for the DELETE, which is invalid; create a
new root for the delete (e.g., eventDeleteRoot =
eventDeleteQuery.from(UserNotification.class)) and rebuild the predicate against
that root instead of reusing eventMatcher—use the same attribute names (e.g.,
get("event") and get("ref")) and the same AnnouncementService constants to
compose a new predicate for eventDeleteQuery.where(...), then execute the
delete; keep the original SELECT/query detach logic unchanged.

In
@kernel/kernel-impl/src/main/java/org/sakaiproject/messaging/impl/UserMessagingServiceImpl.java:
- Around line 162-170: The scheduled cleanup task created by
schedulingService.scheduleWithFixedDelay(...) is never cancelled in destroy(),
so store the returned ScheduledFuture<?> when scheduling (e.g.,
notificationExpiryTaskFuture for the deleteExpiredNotifications block and
notificationPublishTaskFuture for the other scheduled block around lines
226-237) as fields initialized in init(), and in destroy() check each future for
non-null and not done then call cancel(false) (and set to null) to stop the
recurring task; ensure you reference the existing
schedulingService.scheduleWithFixedDelay(...) invocations and the class
destroy()/init() methods when adding these fields and cancellation logic.
- Around line 595-601: When building per-user notifications, the new
UserNotificationTransferBean is only populating from, to, event, ref and title;
update the construction in the block that creates "un" (the
UserNotificationTransferBean used with decorateNotification(...) and push(...))
to also copy bean.eventDate, bean.siteId, bean.url, bean.tool and bean.broadcast
(and any other relevant metadata present on the original bean) so the per-user
notification preserves the original notification context; locate the creation of
"un" where un.from = bean.from and un.to = u.getId() and add assignments for the
missing fields before calling decorateNotification(un) and push(...).
- Around line 583-606: In pushToAllUsers fix pagination by increasing batch size
(replace pageSize = 1 with a reasonable value like 500), correct inclusive
bounds for getUsers by setting last = first + pageSize - 1 (and update the loop
progression to first = last + 1; last = first + pageSize - 1), and simplify
iteration by replacing users.stream().forEach(...) with users.forEach(...); keep
using userDirectoryService.getUsers(...), decorateNotification(...), push(...)
and UserNotificationTransferBean as in the diff.
🧹 Nitpick comments (1)
kernel/kernel-impl/src/main/java/org/sakaiproject/messaging/impl/UserMessagingServiceImpl.java (1)

137-137: Unused field deletingExpiredNotifications.

This field is declared but never read or modified elsewhere in the class. If it was intended to guard against concurrent deletion runs, consider removing it or implementing the guard logic.

🧹 Suggested removal
-    private boolean deletingExpiredNotifications = false;
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c561570 and 7ce88e1.

📒 Files selected for processing (3)
  • announcement/announcement-api/api/src/java/org/sakaiproject/announcement/api/AnnouncementsUserNotificationHandler.java
  • kernel/kernel-impl/src/main/java/org/sakaiproject/config/impl/StoredConfigService.java
  • kernel/kernel-impl/src/main/java/org/sakaiproject/messaging/impl/UserMessagingServiceImpl.java
🧰 Additional context used
📓 Path-based instructions (2)
**/*.java

📄 CodeRabbit inference engine (.cursor/rules/logging-rule.mdc)

**/*.java: Use SLF4J parameterized logging (logger.info("Value is: {}", value)) instead of string concatenation (logger.info("Value is: " + value))
Log messages and code comments should be in English. Log messages should never be translated.

**/*.java: Java: Never use local variable type inference (var). Always declare explicit types. Yes: Map<String, Integer> counts = new HashMap<>(); No: var counts = new HashMap<String, Integer>();
When proposing Java code, spell out full types in local variable declarations, for loops, and try-with-resources
When editing Java, prefer clarity over brevity; avoid introducing language features that aren't widely used in the repo
Treat any PR or suggestion containing Java var as non-compliant. Recommend replacing with explicit types before merge

**/*.java: Use Java 17 for trunk development (Java 11 was used for Sakai 22 and Sakai 23)
Do not use local variable type inference (var) in Java code. Always declare explicit types (e.g., List<String> names = new ArrayList<>(); not var names = new ArrayList<String>();). Enforced by Checkstyle rule during mvn validate

Files:

  • announcement/announcement-api/api/src/java/org/sakaiproject/announcement/api/AnnouncementsUserNotificationHandler.java
  • kernel/kernel-impl/src/main/java/org/sakaiproject/config/impl/StoredConfigService.java
  • kernel/kernel-impl/src/main/java/org/sakaiproject/messaging/impl/UserMessagingServiceImpl.java
{**/UserMessagingServiceImpl.java,**/sakai-service-worker.js}

📄 CodeRabbit inference engine (AGENTS.md)

Use Web Push with VAPID via UserMessagingServiceImpl.java; rely on the service worker in sakai-service-worker.js

Files:

  • kernel/kernel-impl/src/main/java/org/sakaiproject/messaging/impl/UserMessagingServiceImpl.java
🧠 Learnings (9)
📓 Common learnings
Learnt from: CR
Repo: sakaiproject/sakai PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-24T19:12:27.663Z
Learning: Applies to **/sakai-notifications.properties : Reconcile subscriptions proactively and keep localization strings in `sakai-notifications.properties`
Learnt from: CR
Repo: sakaiproject/sakai PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-24T19:12:20.537Z
Learning: Applies to **/*.{java,properties} : Reconcile push notification subscriptions proactively and keep localization strings in `sakai-notifications.properties`
Learnt from: CR
Repo: sakaiproject/sakai PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-10-07T16:11:33.008Z
Learning: Applies to **/sakai-notifications.properties : Use sakai-notifications.properties for internationalized PWA installation messages
Learnt from: CR
Repo: sakaiproject/sakai PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-24T19:12:27.663Z
Learning: Applies to {**/UserMessagingServiceImpl.java,**/sakai-service-worker.js} : Use Web Push with VAPID via `UserMessagingServiceImpl.java`; rely on the service worker in `sakai-service-worker.js`
📚 Learning: 2025-11-24T19:12:20.537Z
Learnt from: CR
Repo: sakaiproject/sakai PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-24T19:12:20.537Z
Learning: Applies to **/*.{java,properties} : Reconcile push notification subscriptions proactively and keep localization strings in `sakai-notifications.properties`

Applied to files:

  • announcement/announcement-api/api/src/java/org/sakaiproject/announcement/api/AnnouncementsUserNotificationHandler.java
  • kernel/kernel-impl/src/main/java/org/sakaiproject/messaging/impl/UserMessagingServiceImpl.java
📚 Learning: 2025-11-24T19:12:27.663Z
Learnt from: CR
Repo: sakaiproject/sakai PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-24T19:12:27.663Z
Learning: Applies to **/sakai-notifications.properties : Reconcile subscriptions proactively and keep localization strings in `sakai-notifications.properties`

Applied to files:

  • announcement/announcement-api/api/src/java/org/sakaiproject/announcement/api/AnnouncementsUserNotificationHandler.java
  • kernel/kernel-impl/src/main/java/org/sakaiproject/messaging/impl/UserMessagingServiceImpl.java
📚 Learning: 2025-11-24T19:12:27.663Z
Learnt from: CR
Repo: sakaiproject/sakai PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-24T19:12:27.663Z
Learning: Applies to {**/UserMessagingServiceImpl.java,**/sakai-service-worker.js} : Use Web Push with VAPID via `UserMessagingServiceImpl.java`; rely on the service worker in `sakai-service-worker.js`

Applied to files:

  • announcement/announcement-api/api/src/java/org/sakaiproject/announcement/api/AnnouncementsUserNotificationHandler.java
  • kernel/kernel-impl/src/main/java/org/sakaiproject/messaging/impl/UserMessagingServiceImpl.java
📚 Learning: 2025-11-24T19:12:20.537Z
Learnt from: CR
Repo: sakaiproject/sakai PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-24T19:12:20.537Z
Learning: Applies to **/UserMessagingServiceImpl.java : Use Web Push with VAPID via `UserMessagingServiceImpl.java` and the service worker in `sakai-service-worker.js`

Applied to files:

  • announcement/announcement-api/api/src/java/org/sakaiproject/announcement/api/AnnouncementsUserNotificationHandler.java
  • kernel/kernel-impl/src/main/java/org/sakaiproject/messaging/impl/UserMessagingServiceImpl.java
📚 Learning: 2025-09-25T13:34:06.742Z
Learnt from: susowrc
Repo: sakaiproject/sakai PR: 14105
File: assignment/tool/src/java/org/sakaiproject/assignment/entityproviders/AssignmentEntityProvider.java:1736-1745
Timestamp: 2025-09-25T13:34:06.742Z
Learning: In Sakai, each site can only have one tool placement for a given common tool ID (e.g., "sakai.assignment.grades" for the Assignment tool). The method getToolForCommonId() returns a single ToolConfiguration object, not a collection, confirming this one-to-one relationship between tools and sites.

Applied to files:

  • announcement/announcement-api/api/src/java/org/sakaiproject/announcement/api/AnnouncementsUserNotificationHandler.java
📚 Learning: 2025-10-07T16:11:33.008Z
Learnt from: CR
Repo: sakaiproject/sakai PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-10-07T16:11:33.008Z
Learning: Applies to **/UserMessagingServiceImpl.java : Implement backend Web Push (VAPID) in UserMessagingServiceImpl.java

Applied to files:

  • kernel/kernel-impl/src/main/java/org/sakaiproject/messaging/impl/UserMessagingServiceImpl.java
📚 Learning: 2025-10-07T16:11:33.008Z
Learnt from: CR
Repo: sakaiproject/sakai PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-10-07T16:11:33.008Z
Learning: Applies to **/sakai-notifications.properties : Use sakai-notifications.properties for internationalized PWA installation messages

Applied to files:

  • kernel/kernel-impl/src/main/java/org/sakaiproject/messaging/impl/UserMessagingServiceImpl.java
📚 Learning: 2025-10-07T16:11:33.008Z
Learnt from: CR
Repo: sakaiproject/sakai PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-10-07T16:11:33.008Z
Learning: Handle push token subscription updates and expirations properly

Applied to files:

  • kernel/kernel-impl/src/main/java/org/sakaiproject/messaging/impl/UserMessagingServiceImpl.java
🧬 Code graph analysis (1)
kernel/kernel-impl/src/main/java/org/sakaiproject/messaging/impl/UserMessagingServiceImpl.java (1)
kernel/api/src/main/java/org/sakaiproject/messaging/api/UserNotificationTransferBean.java (1)
  • UserNotificationTransferBean (26-60)
⏰ Context from checks skipped due to timeout of 900000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: maven-build
  • GitHub Check: sakai-deploy
  • GitHub Check: maven-build
🔇 Additional comments (14)
announcement/announcement-api/api/src/java/org/sakaiproject/announcement/api/AnnouncementsUserNotificationHandler.java (5)

24-24: LGTM: Imports and initialization.

The new imports (Duration, PostConstruct, Predicate) and MOTD TTL configuration field with @PostConstruct initialization are well-structured and follow Spring best practices.

Also applies to: 31-31, 36-36, 86-93


114-116: LGTM: Guard prevents duplicate MOTD processing.

The early return for annc.new events with motd in the resource correctly delegates MOTD handling to the EVENT_MOTD_NEW path, avoiding duplicate notifications.


123-133: LGTM: MOTD broadcast notification correctly implemented.

The EVENT_MOTD_NEW handler creates a single broadcast notification with TTL instead of creating per-user entries, which aligns with the PR objective to replace MOTD-type notifications that create one database entry per user.


176-228: LGTM: Non-MOTD notification path correctly implemented.

The recipient determination logic appropriately:

  • Retrieves only site/group members with read permission (no eager loading of all system users)
  • Applies role-based filtering when SELECTED_ROLES_PROPERTY is specified
  • Filters out the event originator and superusers
  • Creates individual (non-broadcast) notifications for regular announcements

This addresses the scalability concern from previous reviews.


232-237: LGTM: Exception handling and resource cleanup.

The catch block uses SLF4J parameterized logging, and the finally block ensures the security advisor is properly removed regardless of success or exception.

kernel/kernel-impl/src/main/java/org/sakaiproject/config/impl/StoredConfigService.java (3)

36-36: LGTM! Imports support the refactoring to SchedulingService.

The new imports for Lombok's @Setter and SchedulingService are appropriate for the dependency injection and scheduling refactoring described in the PR objectives.

Also applies to: 52-52


129-141: LGTM! Scheduling refactored to use SchedulingService.

The migration from a local ScheduledExecutorService to the injected SchedulingService aligns with the PR objectives to centralize scheduling across Sakai. The method signature and parameters are correct, and the scheduling logic remains sound.


85-88: @Setter annotations correctly replace the manual setters that were previously removed.

The field injections via @Setter are properly wired in configstore-components.xml with lazy-init="false", ensuring that SchedulingService and other dependencies are available before init() is invoked. Spring guarantees that if any required property cannot be wired, the application fails to start—not at runtime. The code pattern is correct and requires no null-safety checks.

Likely an incorrect or invalid review comment.

kernel/kernel-impl/src/main/java/org/sakaiproject/messaging/impl/UserMessagingServiceImpl.java (6)

379-400: LGTM!

The bean-based notification flow correctly handles both broadcast and non-broadcast cases, with proper decoration before pushing.


415-448: LGTM!

The refactored doInsert method correctly extracts fields from UserNotificationData and properly calculates endDate from TTL when present.


520-540: LGTM!

The decoration logic correctly populates display names and site titles with appropriate fallbacks when lookups fail.


561-574: LGTM!

The test notification creation is straightforward and correctly uses the new bean-based approach.


616-669: LGTM!

The push method correctly handles the UserNotificationTransferBean, includes proper null checks, and appropriately cleans up invalid subscriptions on permanent failure status codes.


310-331: LGTM!

The handler registration/unregistration logic correctly uses copy-on-write semantics with atomic reference replacement for thread safety.

Also applies to: 334-353

Comment on lines +162 to +170
schedulingService.scheduleWithFixedDelay(() -> {

transactionTemplate.execute(new TransactionCallbackWithoutResult() {

protected void doInTransactionWithoutResult(TransactionStatus status) {
userNotificationRepository.deleteExpiredNotifications();
}
});
}, 0, 1, TimeUnit.MINUTES);
Copy link
Contributor

@coderabbitai coderabbitai bot Jan 7, 2026

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Scheduled task is never cancelled on shutdown.

The task scheduled via schedulingService.scheduleWithFixedDelay() is not cancelled in destroy(). This could cause the task to continue running after the service is destroyed, potentially leading to errors if dependencies are no longer available.

🔧 Proposed fix

Store the returned ScheduledFuture and cancel it in destroy():

+    private java.util.concurrent.ScheduledFuture<?> expiredNotificationsTask;
     private boolean deletingExpiredNotifications = false;

In init():

-            schedulingService.scheduleWithFixedDelay(() -> {
+            expiredNotificationsTask = schedulingService.scheduleWithFixedDelay(() -> {

                 transactionTemplate.execute(new TransactionCallbackWithoutResult() {

In destroy():

     public void destroy() {

+        if (expiredNotificationsTask != null) {
+            expiredNotificationsTask.cancel(false);
+        }
+
         if (executor != null) {

Also applies to: 226-237

🤖 Prompt for AI Agents
In
@kernel/kernel-impl/src/main/java/org/sakaiproject/messaging/impl/UserMessagingServiceImpl.java
around lines 162 - 170, The scheduled cleanup task created by
schedulingService.scheduleWithFixedDelay(...) is never cancelled in destroy(),
so store the returned ScheduledFuture<?> when scheduling (e.g.,
notificationExpiryTaskFuture for the deleteExpiredNotifications block and
notificationPublishTaskFuture for the other scheduled block around lines
226-237) as fields initialized in init(), and in destroy() check each future for
non-null and not done then call cancel(false) (and set to null) to stop the
recurring task; ensure you reference the existing
schedulingService.scheduleWithFixedDelay(...) invocations and the class
destroy()/init() methods when adding these fields and cancellation logic.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The schedulingservice destroy method shuts down the threads and cancels all of the queued tasks. That should be adequate I would have thought.

Copy link
Contributor

Choose a reason for hiding this comment

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

@adrianfish, you're absolutely right! I verified the SchedulingServiceImpl implementation, and its destroy() method does call shutdownNow() on the internal executor, which cancels all queued and scheduled tasks. Since the SchedulingService is autowired into UserMessagingServiceImpl, Spring's dependency management ensures that UserMessagingServiceImpl.destroy() is called before SchedulingService.destroy(), so the centralized cleanup is sufficient.

I also noticed that this pattern is consistent across the codebase—other services like DbAuthzGroupService, ClusterEventTracking, FileConversionServiceImpl, and PlusServiceImpl also use SchedulingService without explicitly cancelling their scheduled tasks. The centralized shutdown approach is cleaner and avoids duplicating cancellation logic in every service.

Thank you for the clarification!


🧠 Learnings used
Learnt from: CR
Repo: sakaiproject/sakai PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-24T19:12:27.663Z
Learning: Applies to {**/UserMessagingServiceImpl.java,**/sakai-service-worker.js} : Use Web Push with VAPID via `UserMessagingServiceImpl.java`; rely on the service worker in `sakai-service-worker.js`

Learnt from: CR
Repo: sakaiproject/sakai PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-10-07T16:11:33.008Z
Learning: Applies to **/UserMessagingServiceImpl.java : Implement backend Web Push (VAPID) in UserMessagingServiceImpl.java

Learnt from: CR
Repo: sakaiproject/sakai PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-24T19:12:27.663Z
Learning: Applies to **/sakai-notifications.properties : Reconcile subscriptions proactively and keep localization strings in `sakai-notifications.properties`

Learnt from: CR
Repo: sakaiproject/sakai PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-24T19:12:20.537Z
Learning: Applies to **/UserMessagingServiceImpl.java : Use Web Push with VAPID via `UserMessagingServiceImpl.java` and the service worker in `sakai-service-worker.js`

Learnt from: CR
Repo: sakaiproject/sakai PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-24T19:12:20.537Z
Learning: Applies to **/*.{java,properties} : Reconcile push notification subscriptions proactively and keep localization strings in `sakai-notifications.properties`

Learnt from: CR
Repo: sakaiproject/sakai PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-10-07T16:11:33.008Z
Learning: Applies to **/sakai-notifications.properties : Use sakai-notifications.properties for internationalized PWA installation messages

Comment on lines +595 to +601
UserNotificationTransferBean un = new UserNotificationTransferBean();
un.from = bean.from;
un.to = u.getId();
un.event = bean.event;
un.ref = bean.ref;
un.title = bean.title;
push(decorateNotification(un));
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Incomplete field copying when creating per-user notification beans.

The new UserNotificationTransferBean copies only from, to, event, ref, and title from the original bean. Important fields like eventDate, siteId, url, tool, and broadcast are not copied, which may cause push notifications to display incomplete or incorrect information.

🔧 Proposed fix
                 users.forEach(u -> {

                     UserNotificationTransferBean un = new UserNotificationTransferBean();
                     un.from  = bean.from;
                     un.to = u.getId();
                     un.event = bean.event;
                     un.ref = bean.ref;
                     un.title = bean.title;
+                    un.siteId = bean.siteId;
+                    un.url = bean.url;
+                    un.eventDate = bean.eventDate;
+                    un.tool = bean.tool;
+                    un.broadcast = bean.broadcast;
                     push(decorateNotification(un));
                 });
🤖 Prompt for AI Agents
In
@kernel/kernel-impl/src/main/java/org/sakaiproject/messaging/impl/UserMessagingServiceImpl.java
around lines 595 - 601, When building per-user notifications, the new
UserNotificationTransferBean is only populating from, to, event, ref and title;
update the construction in the block that creates "un" (the
UserNotificationTransferBean used with decorateNotification(...) and push(...))
to also copy bean.eventDate, bean.siteId, bean.url, bean.tool and bean.broadcast
(and any other relevant metadata present on the original bean) so the per-user
notification preserves the original notification context; locate the creation of
"un" where un.from = bean.from and un.to = u.getId() and add assignments for the
missing fields before calling decorateNotification(un) and push(...).

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

🤖 Fix all issues with AI agents
In
@announcement/announcement-api/api/src/java/org/sakaiproject/announcement/api/AnnouncementsUserNotificationHandler.java:
- Line 92: There's a typo in the configuration key used when reading the MOTD
TTL: update the string passed to serverConfigurationService.getInt in
AnnouncementsUserNotificationHandler (where motdTTLHours is set) from
"announcement.motod.notification.ttlhours" to the correct
"announcement.motd.notification.ttlhours" so the intended configuration value is
read; leave the motdTTLHours variable and getInt call otherwise unchanged.
- Around line 207-213: The stream filter in AnnouncementsUserNotificationHandler
that sets filteredUsersToNotify calls site.getMember(u).getRole() without
checking for null; change the filter on usersToNotify to first retrieve
SiteMember member = site.getMember(u) and guard that member != null and
member.getRole() != null before calling getId(), e.g. replace the lambda u ->
selectedRoles.contains(site.getMember(u).getRole().getId()) with a safe check
that skips users with null member/role so no NPE occurs when computing
filteredUsersToNotify.
- Around line 123-133: The MOTD branch in AnnouncementsUserNotificationHandler
is missing siteId, url and toolId on the UserNotificationData; set
userNotificationData.setSiteId(event.getContext()), set
userNotificationData.setToolId(AnnouncementService.SAKAI_ANNOUNCEMENT_TOOL_ID),
and populate userNotificationData.setUrl(...) with the same announcement
navigation URL you build for regular announcements (using the message/header
identifier from message.getHeader() or message.getId() and
event.getContext()/tool id) so broadcast notifications include site, tool and a
clickable link.

In
@kernel/kernel-impl/src/main/java/org/sakaiproject/messaging/impl/UserMessagingServiceImpl.java:
- Around line 583-606: The current loop uses a pageSize set to 1 and calls
users.stream().forEach(...), which is inefficient; change the pageSize
initialization to a larger value (e.g., 100) to reduce DB round-trips and
replace users.stream().forEach(...) with users.forEach(...) for direct
iteration; keep the logic that constructs UserNotificationTransferBean (un.from,
un.to, un.event, un.ref, un.title), calls decorateNotification(un) and then
push(...), and update the pagination math using the same first/last/done
variables around the userDirectoryService.getUsers(...) call.
🧹 Nitpick comments (2)
announcement/announcement-api/api/src/java/org/sakaiproject/announcement/api/AnnouncementsUserNotificationHandler.java (1)

219-219: Consider consistent UserNotificationData creation pattern.

Line 219 uses the UserNotificationData constructor, while the MOTD path (lines 126-132) uses setters. For consistency, consider using the same pattern throughout, though the current approach works correctly.

kernel/kernel-impl/src/main/java/org/sakaiproject/messaging/impl/UserMessagingServiceImpl.java (1)

137-137: Unused field deletingExpiredNotifications.

This field is declared but never read or written anywhere in the class. Remove it or implement its intended usage.

🧹 Suggested removal
-    private boolean deletingExpiredNotifications = false;
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c561570 and 545cb65.

📒 Files selected for processing (3)
  • announcement/announcement-api/api/src/java/org/sakaiproject/announcement/api/AnnouncementsUserNotificationHandler.java
  • kernel/kernel-impl/src/main/java/org/sakaiproject/config/impl/StoredConfigService.java
  • kernel/kernel-impl/src/main/java/org/sakaiproject/messaging/impl/UserMessagingServiceImpl.java
🧰 Additional context used
📓 Path-based instructions (2)
**/*.java

📄 CodeRabbit inference engine (.cursor/rules/logging-rule.mdc)

**/*.java: Use SLF4J parameterized logging (logger.info("Value is: {}", value)) instead of string concatenation (logger.info("Value is: " + value))
Log messages and code comments should be in English. Log messages should never be translated.

**/*.java: Java: Never use local variable type inference (var). Always declare explicit types. Yes: Map<String, Integer> counts = new HashMap<>(); No: var counts = new HashMap<String, Integer>();
When proposing Java code, spell out full types in local variable declarations, for loops, and try-with-resources
When editing Java, prefer clarity over brevity; avoid introducing language features that aren't widely used in the repo
Treat any PR or suggestion containing Java var as non-compliant. Recommend replacing with explicit types before merge

**/*.java: Use Java 17 for trunk development (Java 11 was used for Sakai 22 and Sakai 23)
Do not use local variable type inference (var) in Java code. Always declare explicit types (e.g., List<String> names = new ArrayList<>(); not var names = new ArrayList<String>();). Enforced by Checkstyle rule during mvn validate

Files:

  • kernel/kernel-impl/src/main/java/org/sakaiproject/messaging/impl/UserMessagingServiceImpl.java
  • announcement/announcement-api/api/src/java/org/sakaiproject/announcement/api/AnnouncementsUserNotificationHandler.java
  • kernel/kernel-impl/src/main/java/org/sakaiproject/config/impl/StoredConfigService.java
{**/UserMessagingServiceImpl.java,**/sakai-service-worker.js}

📄 CodeRabbit inference engine (AGENTS.md)

Use Web Push with VAPID via UserMessagingServiceImpl.java; rely on the service worker in sakai-service-worker.js

Files:

  • kernel/kernel-impl/src/main/java/org/sakaiproject/messaging/impl/UserMessagingServiceImpl.java
🧠 Learnings (9)
📓 Common learnings
Learnt from: CR
Repo: sakaiproject/sakai PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-24T19:12:27.663Z
Learning: Applies to **/sakai-notifications.properties : Reconcile subscriptions proactively and keep localization strings in `sakai-notifications.properties`
Learnt from: CR
Repo: sakaiproject/sakai PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-24T19:12:20.537Z
Learning: Applies to **/*.{java,properties} : Reconcile push notification subscriptions proactively and keep localization strings in `sakai-notifications.properties`
Learnt from: CR
Repo: sakaiproject/sakai PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-10-07T16:11:33.008Z
Learning: Applies to **/sakai-notifications.properties : Use sakai-notifications.properties for internationalized PWA installation messages
📚 Learning: 2025-11-24T19:12:27.663Z
Learnt from: CR
Repo: sakaiproject/sakai PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-24T19:12:27.663Z
Learning: Applies to {**/UserMessagingServiceImpl.java,**/sakai-service-worker.js} : Use Web Push with VAPID via `UserMessagingServiceImpl.java`; rely on the service worker in `sakai-service-worker.js`

Applied to files:

  • kernel/kernel-impl/src/main/java/org/sakaiproject/messaging/impl/UserMessagingServiceImpl.java
  • announcement/announcement-api/api/src/java/org/sakaiproject/announcement/api/AnnouncementsUserNotificationHandler.java
📚 Learning: 2025-10-07T16:11:33.008Z
Learnt from: CR
Repo: sakaiproject/sakai PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-10-07T16:11:33.008Z
Learning: Applies to **/UserMessagingServiceImpl.java : Implement backend Web Push (VAPID) in UserMessagingServiceImpl.java

Applied to files:

  • kernel/kernel-impl/src/main/java/org/sakaiproject/messaging/impl/UserMessagingServiceImpl.java
📚 Learning: 2025-11-24T19:12:20.537Z
Learnt from: CR
Repo: sakaiproject/sakai PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-24T19:12:20.537Z
Learning: Applies to **/UserMessagingServiceImpl.java : Use Web Push with VAPID via `UserMessagingServiceImpl.java` and the service worker in `sakai-service-worker.js`

Applied to files:

  • kernel/kernel-impl/src/main/java/org/sakaiproject/messaging/impl/UserMessagingServiceImpl.java
  • announcement/announcement-api/api/src/java/org/sakaiproject/announcement/api/AnnouncementsUserNotificationHandler.java
📚 Learning: 2025-11-24T19:12:20.537Z
Learnt from: CR
Repo: sakaiproject/sakai PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-24T19:12:20.537Z
Learning: Applies to **/*.{java,properties} : Reconcile push notification subscriptions proactively and keep localization strings in `sakai-notifications.properties`

Applied to files:

  • kernel/kernel-impl/src/main/java/org/sakaiproject/messaging/impl/UserMessagingServiceImpl.java
  • announcement/announcement-api/api/src/java/org/sakaiproject/announcement/api/AnnouncementsUserNotificationHandler.java
📚 Learning: 2025-11-24T19:12:27.663Z
Learnt from: CR
Repo: sakaiproject/sakai PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-24T19:12:27.663Z
Learning: Applies to **/sakai-notifications.properties : Reconcile subscriptions proactively and keep localization strings in `sakai-notifications.properties`

Applied to files:

  • kernel/kernel-impl/src/main/java/org/sakaiproject/messaging/impl/UserMessagingServiceImpl.java
  • announcement/announcement-api/api/src/java/org/sakaiproject/announcement/api/AnnouncementsUserNotificationHandler.java
📚 Learning: 2025-10-07T16:11:33.008Z
Learnt from: CR
Repo: sakaiproject/sakai PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-10-07T16:11:33.008Z
Learning: Applies to **/sakai-notifications.properties : Use sakai-notifications.properties for internationalized PWA installation messages

Applied to files:

  • kernel/kernel-impl/src/main/java/org/sakaiproject/messaging/impl/UserMessagingServiceImpl.java
📚 Learning: 2025-10-07T16:11:33.008Z
Learnt from: CR
Repo: sakaiproject/sakai PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-10-07T16:11:33.008Z
Learning: Handle push token subscription updates and expirations properly

Applied to files:

  • kernel/kernel-impl/src/main/java/org/sakaiproject/messaging/impl/UserMessagingServiceImpl.java
📚 Learning: 2025-09-25T13:34:06.742Z
Learnt from: susowrc
Repo: sakaiproject/sakai PR: 14105
File: assignment/tool/src/java/org/sakaiproject/assignment/entityproviders/AssignmentEntityProvider.java:1736-1745
Timestamp: 2025-09-25T13:34:06.742Z
Learning: In Sakai, each site can only have one tool placement for a given common tool ID (e.g., "sakai.assignment.grades" for the Assignment tool). The method getToolForCommonId() returns a single ToolConfiguration object, not a collection, confirming this one-to-one relationship between tools and sites.

Applied to files:

  • announcement/announcement-api/api/src/java/org/sakaiproject/announcement/api/AnnouncementsUserNotificationHandler.java
🧬 Code graph analysis (1)
kernel/kernel-impl/src/main/java/org/sakaiproject/messaging/impl/UserMessagingServiceImpl.java (2)
kernel/api/src/main/java/org/sakaiproject/messaging/api/UserNotificationTransferBean.java (1)
  • UserNotificationTransferBean (26-60)
kernel/api/src/main/java/org/sakaiproject/serialization/MapperFactory.java (1)
  • MapperFactory (36-427)
⏰ Context from checks skipped due to timeout of 900000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: sakai-deploy
  • GitHub Check: maven-build
  • GitHub Check: maven-build
🔇 Additional comments (13)
kernel/kernel-impl/src/main/java/org/sakaiproject/config/impl/StoredConfigService.java (2)

85-88: LGTM - Lombok @Setter annotations are correctly applied.

The @Setter annotations properly generate setters for Spring dependency injection. The previous concern about duplicate manual setters has been resolved.


129-139: LGTM - Migration to SchedulingService is correct.

The replacement of the local ScheduledExecutorService with the centralized SchedulingService is implemented correctly:

  • The scheduleWithFixedDelay call signature and parameters are correct
  • The anonymous Runnable with an instance field pollDate appropriately maintains state between polling iterations
  • The minimum 120-second initial delay is preserved for Sakai startup
  • Lifecycle management is now centralized in SchedulingService (the removed destroy() method is no longer needed)
announcement/announcement-api/api/src/java/org/sakaiproject/announcement/api/AnnouncementsUserNotificationHandler.java (4)

113-116: LGTM: Early exit prevents duplicate MOTD processing.

The guard correctly prevents "annc.new" events with MOTD resources from being processed twice, since they're handled by the dedicated "motd.new" event path.


135-141: LGTM: Early initialization enables draft/future visibility logic.

Moving the draft and future message flag initialization before the removal logic allows them to influence the removal conditions at line 146.


143-169: LGTM: Removal logic correctly fixed.

The refactored removal logic addresses the previously flagged issues:

  • Uses buildWhere helper with appropriate table roots for both SELECT and DELETE queries
  • Properly detaches notifications before bulk delete to maintain persistence context consistency
  • Includes transaction handling with error logging

236-245: LGTM: Helper method correctly encapsulates predicate logic.

The buildWhere helper properly accepts the query-specific Root parameter, allowing safe reuse across SELECT and DELETE queries without the previous predicate-sharing issues.

kernel/kernel-impl/src/main/java/org/sakaiproject/messaging/impl/UserMessagingServiceImpl.java (7)

162-170: Scheduled cleanup task looks good.

The expired notification cleanup is properly scheduled with transaction handling. The 1-minute interval is reasonable for this type of housekeeping task.


378-400: Bean-based notification flow is well-structured.

The refactoring to use UserNotificationData and UserNotificationTransferBean improves clarity. The conditional branching for broadcast vs. per-user push is correct.


415-448: Clean refactoring of doInsert() to use UserNotificationData.

The method now properly extracts all fields from the data bean, including TTL-based end date calculation. The null check on getTtl() before setting endDate is correct.


520-540: Decoration logic is correctly adapted for the bean-based model.

Exception handling provides sensible fallbacks when user or site lookups fail.


616-669: Push notification handling is well-implemented.

The method correctly validates inputs, handles subscription errors with appropriate cleanup for permanent failures (410, 404, 400), and logs using parameterized format as per coding guidelines.


561-574: Test notification implementation is clean.

Correctly creates and decorates a test notification using the new bean-based model.


493-495: Add .distinct() to prevent duplicate notifications.

The stream concatenates user-specific and broadcast notifications without deduplication. If a record has both broadcast=true and toUser matching the current user, it would appear twice.

🔧 Suggested fix
         List<UserNotificationTransferBean> beans = Stream.concat(userNotificationRepository.findByToUser(userId).stream(),
                 userNotificationRepository.findByBroadcast(true).stream())
-            .map(UserNotificationTransferBean::of).map(this::decorateNotification).collect(Collectors.toList());
+            .distinct()
+            .map(UserNotificationTransferBean::of).map(this::decorateNotification).collect(Collectors.toList());

Note: Apply .distinct() on UserNotification before mapping, and ensure UserNotification has proper equals/hashCode implementation based on its ID.

Likely an incorrect or invalid review comment.


@PostConstruct
private void init() {
motdTTLHours = serverConfigurationService.getInt("announcement.motd.notification.ttlhours", 24);
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Fix typo in configuration property name.

The configuration property name has a typo: "announcement.motod.notification.ttlhours" should be "announcement.motd.notification.ttlhours" (motod → motd).

🔧 Proposed fix
-        motdTTLHours = serverConfigurationService.getInt("announcement.motod.notification.ttlhours", 24);
+        motdTTLHours = serverConfigurationService.getInt("announcement.motd.notification.ttlhours", 24);
🤖 Prompt for AI Agents
In
@announcement/announcement-api/api/src/java/org/sakaiproject/announcement/api/AnnouncementsUserNotificationHandler.java
at line 92, There's a typo in the configuration key used when reading the MOTD
TTL: update the string passed to serverConfigurationService.getInt in
AnnouncementsUserNotificationHandler (where motdTTLHours is set) from
"announcement.motod.notification.ttlhours" to the correct
"announcement.motd.notification.ttlhours" so the intended configuration value is
read; leave the motdTTLHours variable and getInt call otherwise unchanged.

Comment on lines +207 to 213
if (selectedRoles != null && !selectedRoles.isEmpty()) {
filteredUsersToNotify = usersToNotify.stream()
.filter(u -> selectedRoles.contains(site.getMember(u).getRole().getId()))
.collect(Collectors.toSet());
} else {
filteredUsersToNotify = usersToNotify;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Guard against NPE when filtering by role.

Line 209 calls site.getMember(u).getRole() without null-checking the member. If a user is no longer a site member (race condition or async site update), getMember returns null, causing a NullPointerException.

🛡️ Proposed fix
                         Set<String> filteredUsersToNotify;
                         // filter users to notify based on their role
                         List<String> selectedRoles = message.getProperties().getPropertyList(SELECTED_ROLES_PROPERTY);
                         if (selectedRoles != null && !selectedRoles.isEmpty()) {
                             filteredUsersToNotify = usersToNotify.stream()
-                                    .filter(u -> selectedRoles.contains(site.getMember(u).getRole().getId()))
+                                    .filter(u -> {
+                                        var member = site.getMember(u);
+                                        return member != null && selectedRoles.contains(member.getRole().getId());
+                                    })
                                     .collect(Collectors.toSet());
                         } else {
                             filteredUsersToNotify = usersToNotify;
                         }

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In
@announcement/announcement-api/api/src/java/org/sakaiproject/announcement/api/AnnouncementsUserNotificationHandler.java
around lines 207 - 213, The stream filter in
AnnouncementsUserNotificationHandler that sets filteredUsersToNotify calls
site.getMember(u).getRole() without checking for null; change the filter on
usersToNotify to first retrieve SiteMember member = site.getMember(u) and guard
that member != null and member.getRole() != null before calling getId(), e.g.
replace the lambda u ->
selectedRoles.contains(site.getMember(u).getRole().getId()) with a safe check
that skips users with null member/role so no NPE occurs when computing
filteredUsersToNotify.

Comment on lines +583 to +606
int pageSize = 1;
int first = 1;
int last = first + pageSize;
int total = userDirectoryService.countUsers();
int done = 0;

while (done < total) {

List<User> users = userDirectoryService.getUsers(first, last);

users.stream().forEach(u -> {

UserNotificationTransferBean un = new UserNotificationTransferBean();
un.from = bean.from;
un.to = u.getId();
un.event = bean.event;
un.ref = bean.ref;
un.title = bean.title;
push(decorateNotification(un));
});

first = last + pageSize;
last = first + pageSize;
done += users.size();
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# Find UserDirectoryService interface/class
fd -t f -name "*.java" | xargs rg "interface UserDirectoryService|class UserDirectoryService" -l

Repository: sakaiproject/sakai

Length of output: 563


🏁 Script executed:

# Search for getUsers method definition with documentation
rg -n "getUsers\s*\(\s*int\s*,\s*int\s*\)" --type java -B 5 -A 15 | head -150

Repository: sakaiproject/sakai

Length of output: 44


🏁 Script executed:

# Look for any javadoc or comments around getUsers in UserDirectoryService
rg -n "getUsers" --type java -B 3 -A 10 | grep -A 10 "int.*int" | head -100

Repository: sakaiproject/sakai

Length of output: 11582


🏁 Script executed:

# Get the UserDirectoryService interface with JavaDoc
cat -n kernel/api/src/main/java/org/sakaiproject/user/api/UserDirectoryService.java | sed -n '415,435p'

Repository: sakaiproject/sakai

Length of output: 856


🏁 Script executed:

# Also check the test file to see how getUsers is actually used
cat -n kernel/kernel-impl/src/test/java/org/sakaiproject/user/impl/test/UserDirectoryServiceGetTest.java | head -150

Repository: sakaiproject/sakai

Length of output: 6959


🏁 Script executed:

# Find actual usages of getUsers with int parameters
rg "getUsers\s*\(\s*\d+\s*,\s*\d+" --type java -B 2 -A 2

Repository: sakaiproject/sakai

Length of output: 44


🏁 Script executed:

# Also search for pagination patterns similar to the reviewed code
rg -A 5 "getUsers\s*\(\s*first\s*,\s*last\s*\)" --type java

Repository: sakaiproject/sakai

Length of output: 2177


🏁 Script executed:

# Check the implementation in BaseUserDirectoryService to see what m_storage.getAll does
cat -n kernel/kernel-impl/src/main/java/org/sakaiproject/user/impl/BaseUserDirectoryService.java | sed -n '1375,1382p'

Repository: sakaiproject/sakai

Length of output: 258


🏁 Script executed:

# Find the storage interface and implementation for getAll
rg "interface.*Storage|class.*Storage" --type java kernel/kernel-impl/src/main/java/org/sakaiproject/user/impl/ | grep -i user

Repository: sakaiproject/sakai

Length of output: 801


🏁 Script executed:

# Search for getAll method implementation
rg "getAll\s*\(\s*int\s+\w+\s*,\s*int" --type java -A 15 kernel/kernel-impl/src/main/java/org/sakaiproject/user/impl/

Repository: sakaiproject/sakai

Length of output: 3465


🏁 Script executed:

# Find getAllResources implementation in BaseDbFlatStorage
rg "getAllResources\s*\(\s*int\s+\w+\s*,\s*int" --type java -A 20

Repository: sakaiproject/sakai

Length of output: 9844


🏁 Script executed:

# Check how the indices are used in the SQL or pagination logic
rg "LIMIT|OFFSET|first.*last" --type java -B 3 -A 3 kernel/kernel-impl/src/main/java/org/sakaiproject/user/impl/DbUserService.java | head -80

Repository: sakaiproject/sakai

Length of output: 1192


🏁 Script executed:

# Check the BaseDbFlatStorage to confirm the offset/limit calculation
cat -n kernel/kernel-storage-util/src/main/java/org/sakaiproject/util/BaseDbFlatStorage.java | sed -n '260,285p'

Repository: sakaiproject/sakai

Length of output: 606


🏁 Script executed:

# Also look at how other code uses pagination with getUsers
cat -n entitybroker/core-providers/src/java/org/sakaiproject/entitybroker/providers/UserEntityProvider.java | sed -n '100,150p'

Repository: sakaiproject/sakai

Length of output: 2701


🏁 Script executed:

# Find the exact line with the offset/limit calculation
cat -n kernel/kernel-storage-util/src/main/java/org/sakaiproject/util/BaseDbFlatStorage.java | sed -n '175,220p'

Repository: sakaiproject/sakai

Length of output: 1951


🏁 Script executed:

# Let me also check the actual getUsers pagination in UserMessagingServiceImpl more carefully
cat -n kernel/kernel-impl/src/main/java/org/sakaiproject/messaging/impl/UserMessagingServiceImpl.java | sed -n '575,610p'

Repository: sakaiproject/sakai

Length of output: 1387


🏁 Script executed:

# Let me verify by manually computing what happens with the current code
cat > /tmp/test_pagination.py << 'EOF'
# Current code with pageSize = 1
pageSize = 1
first = 1
last = first + pageSize  # = 2
total = 10
done = 0

print("Current code with pageSize=1:")
iteration = 1
while done < total:
    # Calculate OFFSET and LIMIT as per BaseDbFlatStorage
    offset = first - 1
    limit = last - first + 1
    print(f"  Iteration {iteration}: first={first}, last={last}, OFFSET={offset}, LIMIT={limit}, fetches positions {first} to {last}")
    
    # Simulate fetching users
    users_fetched = min(limit, total - done)
    done += users_fetched
    
    # Next iteration
    first = last + pageSize  # = last + 1
    last = first + pageSize  # = first + 1
    iteration += 1
    if iteration > 15:
        break

print("\n" + "="*60 + "\n")

# With suggested fix (pageSize=100)
pageSize = 100
first = 1
last = first + pageSize - 1  # = 100
total = 10
done = 0

print("Suggested fix with pageSize=100:")
iteration = 1
while done < total:
    offset = first - 1
    limit = last - first + 1
    print(f"  Iteration {iteration}: first={first}, last={last}, OFFSET={offset}, LIMIT={limit}, fetches positions {first} to {last}")
    
    users_fetched = min(limit, total - done)
    done += users_fetched
    
    first = last + 1
    last = first + pageSize - 1
    iteration += 1
    if iteration > 15:
        break

print("\n" + "="*60 + "\n")

# Original code with pageSize=100 (unfixed)
pageSize = 100
first = 1
last = first + pageSize  # = 101
total = 1000
done = 0

print("Original code with pageSize=100 (buggy):")
iteration = 1
while done < total and iteration <= 5:
    offset = first - 1
    limit = last - first + 1
    print(f"  Iteration {iteration}: first={first}, last={last}, OFFSET={offset}, LIMIT={limit}, fetches positions {first} to {last}")
    
    users_fetched = min(limit, total - done)
    done += users_fetched
    
    first = last + pageSize  # = last + 100
    last = first + pageSize  # = first + 100
    iteration += 1
EOF
python /tmp/test_pagination.py

Repository: sakaiproject/sakai

Length of output: 148


🏁 Script executed:

# From the earlier output, the key line was:
# (first - 1), (last - first + 1)
# This means: offset = first - 1, limit = last - first + 1

cat > /tmp/analyze_pagination.txt << 'EOF'
Based on BaseDbFlatStorage.java line: (first - 1), (last - first + 1)
This means: offset = first - 1, limit = last - first + 1

Current pagination code analysis:
- Iteration 1: first=1, last=1+1=2
  → offset=0, limit=2-1+1=2
  → fetches records at positions 0-1 (users 1-2 in 1-based indexing) ✓

- Iteration 2: first=2+1=3, last=3+1=4
  → offset=2, limit=4-3+1=2
  → fetches records at positions 2-3 (users 3-4 in 1-based indexing) ✓

Conclusion: NO GAP in pagination! Records are consecutive.

Other issues verified:
1. pageSize=1 is inefficient - causes many DB calls ✓
2. users.stream().forEach() - need to verify if this is considered bad practice
EOF
cat /tmp/analyze_pagination.txt

Repository: sakaiproject/sakai

Length of output: 714


🏁 Script executed:

# Verify the stream().forEach() claim in Java best practices and this codebase
rg "\.forEach\(" --type java kernel/kernel-impl/src/main/java/org/sakaiproject/messaging/impl/UserMessagingServiceImpl.java

Repository: sakaiproject/sakai

Length of output: 322


🏁 Script executed:

# Check line 604-605 more carefully with context
cat -n kernel/kernel-impl/src/main/java/org/sakaiproject/messaging/impl/UserMessagingServiceImpl.java | sed -n '589,608p'

Repository: sakaiproject/sakai

Length of output: 879


Reduce page size and simplify stream usage.

  1. Inefficient page size: pageSize = 1 causes excessive database queries for large installations. Increase to at least 100.

  2. Unnecessary stream: Use users.forEach() instead of users.stream().forEach() for simpler, more direct iteration.

🔧 Suggested fix
     private void pushToAllUsers(UserNotificationTransferBean bean) {
 
         executor.execute(() -> {
 
-            int pageSize = 1;
+            int pageSize = 100;
             int first = 1;
             int last = first + pageSize;
             int total = userDirectoryService.countUsers();
             int done = 0;
 
             while (done < total) {
 
                 List<User> users = userDirectoryService.getUsers(first, last);
 
-                users.stream().forEach(u -> {
+                users.forEach(u -> {
 
                     UserNotificationTransferBean un = new UserNotificationTransferBean();
                     un.from  = bean.from;
                     un.to = u.getId();
                     un.event = bean.event;
                     un.ref = bean.ref;
                     un.title = bean.title;
                     push(decorateNotification(un));
                 });
 
                 first = last + pageSize;
                 last = first + pageSize;
                 done += users.size();
             }
         });
     }
🤖 Prompt for AI Agents
In
@kernel/kernel-impl/src/main/java/org/sakaiproject/messaging/impl/UserMessagingServiceImpl.java
around lines 583 - 606, The current loop uses a pageSize set to 1 and calls
users.stream().forEach(...), which is inefficient; change the pageSize
initialization to a larger value (e.g., 100) to reduce DB round-trips and
replace users.stream().forEach(...) with users.forEach(...) for direct
iteration; keep the logic that constructs UserNotificationTransferBean (un.from,
un.to, un.event, un.ref, un.title), calls decorateNotification(un) and then
push(...), and update the pagination math using the same first/last/done
variables around the userDirectoryService.getUsers(...) call.

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: 2

🤖 Fix all issues with AI agents
In
@announcement/announcement-api/api/src/java/org/sakaiproject/announcement/api/AnnouncementsUserNotificationHandler.java:
- Around line 123-133: The MOTD branch handling
AnnouncementService.EVENT_MOTD_NEW builds a UserNotificationData but omits
siteId, url, and toolId; update the MOTD path (in
AnnouncementsUserNotificationHandler where UserNotificationData is constructed)
to mirror the regular notification path by setting
userNotificationData.setSiteId(event.getContext()),
userNotificationData.setUrl(...) using the same URL construction used for
non‑MOTD notifications (e.g., build the announcement link from the
message/header or AnnouncementReference), and set
userNotificationData.setToolId(...) to the same tool id value used in the normal
notification flow so broadcast notifications include site context, a
clickthrough URL, and tool association.
- Around line 206-213: The role-filtering stream can NPE because
site.getMember(u) or getRole() may be null; update the block that sets
filteredUsersToNotify (using selectedRoles, usersToNotify, site.getMember,
getRole, getId) to defensively check for nulls before calling
getRole().getId()—e.g., map each user to SiteMember, skip if null, get role and
skip if null, then test role.getId() against selectedRoles; ensure you use
explicit types (no var) per guidelines when adding any local variables.
🧹 Nitpick comments (1)
announcement/announcement-api/api/src/java/org/sakaiproject/announcement/api/AnnouncementsUserNotificationHandler.java (1)

176-176: Consider using constant for tool common ID.

The hardcoded string "sakai.announcements" should ideally be a constant from AnnouncementService (similar to SAKAI_ANNOUNCEMENT_TOOL_ID used at line 219) for consistency and maintainability.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7ce88e1 and 545cb65.

📒 Files selected for processing (1)
  • announcement/announcement-api/api/src/java/org/sakaiproject/announcement/api/AnnouncementsUserNotificationHandler.java
🧰 Additional context used
📓 Path-based instructions (1)
**/*.java

📄 CodeRabbit inference engine (.cursor/rules/logging-rule.mdc)

**/*.java: Use SLF4J parameterized logging (logger.info("Value is: {}", value)) instead of string concatenation (logger.info("Value is: " + value))
Log messages and code comments should be in English. Log messages should never be translated.

**/*.java: Java: Never use local variable type inference (var). Always declare explicit types. Yes: Map<String, Integer> counts = new HashMap<>(); No: var counts = new HashMap<String, Integer>();
When proposing Java code, spell out full types in local variable declarations, for loops, and try-with-resources
When editing Java, prefer clarity over brevity; avoid introducing language features that aren't widely used in the repo
Treat any PR or suggestion containing Java var as non-compliant. Recommend replacing with explicit types before merge

**/*.java: Use Java 17 for trunk development (Java 11 was used for Sakai 22 and Sakai 23)
Do not use local variable type inference (var) in Java code. Always declare explicit types (e.g., List<String> names = new ArrayList<>(); not var names = new ArrayList<String>();). Enforced by Checkstyle rule during mvn validate

Files:

  • announcement/announcement-api/api/src/java/org/sakaiproject/announcement/api/AnnouncementsUserNotificationHandler.java
🧠 Learnings (6)
📓 Common learnings
Learnt from: CR
Repo: sakaiproject/sakai PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-24T19:12:27.663Z
Learning: Applies to **/sakai-notifications.properties : Reconcile subscriptions proactively and keep localization strings in `sakai-notifications.properties`
Learnt from: CR
Repo: sakaiproject/sakai PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-24T19:12:20.537Z
Learning: Applies to **/*.{java,properties} : Reconcile push notification subscriptions proactively and keep localization strings in `sakai-notifications.properties`
Learnt from: CR
Repo: sakaiproject/sakai PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-10-07T16:11:33.008Z
Learning: Applies to **/sakai-notifications.properties : Use sakai-notifications.properties for internationalized PWA installation messages
📚 Learning: 2025-11-24T19:12:20.537Z
Learnt from: CR
Repo: sakaiproject/sakai PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-24T19:12:20.537Z
Learning: Applies to **/*.{java,properties} : Reconcile push notification subscriptions proactively and keep localization strings in `sakai-notifications.properties`

Applied to files:

  • announcement/announcement-api/api/src/java/org/sakaiproject/announcement/api/AnnouncementsUserNotificationHandler.java
📚 Learning: 2025-11-24T19:12:27.663Z
Learnt from: CR
Repo: sakaiproject/sakai PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-24T19:12:27.663Z
Learning: Applies to **/sakai-notifications.properties : Reconcile subscriptions proactively and keep localization strings in `sakai-notifications.properties`

Applied to files:

  • announcement/announcement-api/api/src/java/org/sakaiproject/announcement/api/AnnouncementsUserNotificationHandler.java
📚 Learning: 2025-11-24T19:12:27.663Z
Learnt from: CR
Repo: sakaiproject/sakai PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-24T19:12:27.663Z
Learning: Applies to {**/UserMessagingServiceImpl.java,**/sakai-service-worker.js} : Use Web Push with VAPID via `UserMessagingServiceImpl.java`; rely on the service worker in `sakai-service-worker.js`

Applied to files:

  • announcement/announcement-api/api/src/java/org/sakaiproject/announcement/api/AnnouncementsUserNotificationHandler.java
📚 Learning: 2025-11-24T19:12:20.537Z
Learnt from: CR
Repo: sakaiproject/sakai PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-24T19:12:20.537Z
Learning: Applies to **/UserMessagingServiceImpl.java : Use Web Push with VAPID via `UserMessagingServiceImpl.java` and the service worker in `sakai-service-worker.js`

Applied to files:

  • announcement/announcement-api/api/src/java/org/sakaiproject/announcement/api/AnnouncementsUserNotificationHandler.java
📚 Learning: 2025-09-25T13:34:06.742Z
Learnt from: susowrc
Repo: sakaiproject/sakai PR: 14105
File: assignment/tool/src/java/org/sakaiproject/assignment/entityproviders/AssignmentEntityProvider.java:1736-1745
Timestamp: 2025-09-25T13:34:06.742Z
Learning: In Sakai, each site can only have one tool placement for a given common tool ID (e.g., "sakai.assignment.grades" for the Assignment tool). The method getToolForCommonId() returns a single ToolConfiguration object, not a collection, confirming this one-to-one relationship between tools and sites.

Applied to files:

  • announcement/announcement-api/api/src/java/org/sakaiproject/announcement/api/AnnouncementsUserNotificationHandler.java
⏰ Context from checks skipped due to timeout of 900000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: sakai-deploy
  • GitHub Check: maven-build
  • GitHub Check: maven-build
🔇 Additional comments (5)
announcement/announcement-api/api/src/java/org/sakaiproject/announcement/api/AnnouncementsUserNotificationHandler.java (5)

86-93: LGTM: Clean configuration initialization.

The constant and PostConstruct initialization for MOTD TTL configuration follows best practices.


113-116: LGTM: Correct delegation to MOTD_NEW.

The early return prevents duplicate processing by delegating MOTD announcements to the dedicated EVENT_MOTD_NEW handler.


135-141: LGTM: Good refactoring.

Early initialization of isDraftMessage and isFutureMessage improves readability and avoids repeated message header access.


143-169: LGTM: Correct transaction handling.

The deletion logic properly:

  • Queries and detaches entities before bulk delete
  • Uses separate roots for SELECT and DELETE queries via buildWhere helper
  • Handles transaction exceptions with appropriate logging

236-245: LGTM: Excellent refactoring.

The buildWhere helper method eliminates the previous predicate reuse issue by allowing each query to build its own predicate from its own root. This is a clean solution that makes the code more maintainable.

Comment on lines +123 to +133
if (eventName.equals(AnnouncementService.EVENT_MOTD_NEW)) {

String title = ((AnnouncementMessageHeader) message.getHeader()).getSubject();
UserNotificationData userNotificationData = new UserNotificationData();
userNotificationData.setFrom(event.getUserId());
userNotificationData.setTo("BROADCAST");
userNotificationData.setTitle(title);
userNotificationData.setBroadcast(true);
userNotificationData.setTtl(Duration.ofHours(motdTTLHours));
return Optional.of(List.of(userNotificationData));
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Missing required fields in MOTD broadcast notification.

The MOTD broadcast notification is missing siteId, url, and toolId that are set in the regular notification path (lines 179-184, 187, 219). Without these fields:

  • Users cannot click through to the announcement (no URL)
  • Site context is missing for downstream processing
  • Tool association is absent for proper categorization
🔧 Proposed fix
             if (eventName.equals(AnnouncementService.EVENT_MOTD_NEW)) {

                 String title = ((AnnouncementMessageHeader) message.getHeader()).getSubject();
+                String eventContext = event.getContext();
+                
+                // Build URL similar to regular notification flow
+                Site site = siteService.getSite(eventContext);
+                ToolConfiguration toolConfig = site.getToolForCommonId("sakai.announcements");
+                String url = "";
+                if (toolConfig != null) {
+                    url = serverConfigurationService.getPortalUrl()
+                            + "/directtool/"
+                            + toolConfig.getId()
+                            + "?itemReference="
+                            + eventResource
+                            + "&sakai_action=doShowmetadata";
+                }
+                
                 UserNotificationData userNotificationData = new UserNotificationData();
                 userNotificationData.setFrom(event.getUserId());
                 userNotificationData.setTo("BROADCAST");
+                userNotificationData.setSiteId(eventContext);
                 userNotificationData.setTitle(title);
+                userNotificationData.setUrl(url);
+                userNotificationData.setToolId(AnnouncementService.SAKAI_ANNOUNCEMENT_TOOL_ID);
                 userNotificationData.setBroadcast(true);
                 userNotificationData.setTtl(Duration.ofHours(motdTTLHours));
                 return Optional.of(List.of(userNotificationData));
             }

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In
@announcement/announcement-api/api/src/java/org/sakaiproject/announcement/api/AnnouncementsUserNotificationHandler.java
around lines 123 - 133, The MOTD branch handling
AnnouncementService.EVENT_MOTD_NEW builds a UserNotificationData but omits
siteId, url, and toolId; update the MOTD path (in
AnnouncementsUserNotificationHandler where UserNotificationData is constructed)
to mirror the regular notification path by setting
userNotificationData.setSiteId(event.getContext()),
userNotificationData.setUrl(...) using the same URL construction used for
non‑MOTD notifications (e.g., build the announcement link from the
message/header or AnnouncementReference), and set
userNotificationData.setToolId(...) to the same tool id value used in the normal
notification flow so broadcast notifications include site context, a
clickthrough URL, and tool association.

Comment on lines +206 to 213
List<String> selectedRoles = message.getProperties().getPropertyList(SELECTED_ROLES_PROPERTY);
if (selectedRoles != null && !selectedRoles.isEmpty()) {
filteredUsersToNotify = usersToNotify.stream()
.filter(u -> selectedRoles.contains(site.getMember(u).getRole().getId()))
.collect(Collectors.toSet());
} else {
filteredUsersToNotify = usersToNotify;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Potential NPE in role-based filtering.

The role filtering at lines 208-209 calls site.getMember(u).getRole().getId() without null checks. If a user's membership changes between getUsersIsAllowed() (lines 195, 200) and this filter, or if getRole() returns null, this will throw a NullPointerException.

🛡️ Proposed defensive fix
                         if (selectedRoles != null && !selectedRoles.isEmpty()) {
                             filteredUsersToNotify = usersToNotify.stream()
-                                    .filter(u -> selectedRoles.contains(site.getMember(u).getRole().getId()))
+                                    .filter(u -> {
+                                        var member = site.getMember(u);
+                                        return member != null && member.getRole() != null 
+                                            && selectedRoles.contains(member.getRole().getId());
+                                    })
                                     .collect(Collectors.toSet());
                         } else {

Note: Replace var with explicit type per coding guidelines:

                         if (selectedRoles != null && !selectedRoles.isEmpty()) {
                             filteredUsersToNotify = usersToNotify.stream()
-                                    .filter(u -> selectedRoles.contains(site.getMember(u).getRole().getId()))
+                                    .filter(u -> {
+                                        org.sakaiproject.site.api.Member member = site.getMember(u);
+                                        return member != null && member.getRole() != null 
+                                            && selectedRoles.contains(member.getRole().getId());
+                                    })
                                     .collect(Collectors.toSet());
                         } else {
📝 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.

Suggested change
List<String> selectedRoles = message.getProperties().getPropertyList(SELECTED_ROLES_PROPERTY);
if (selectedRoles != null && !selectedRoles.isEmpty()) {
filteredUsersToNotify = usersToNotify.stream()
.filter(u -> selectedRoles.contains(site.getMember(u).getRole().getId()))
.collect(Collectors.toSet());
} else {
filteredUsersToNotify = usersToNotify;
}
List<String> selectedRoles = message.getProperties().getPropertyList(SELECTED_ROLES_PROPERTY);
if (selectedRoles != null && !selectedRoles.isEmpty()) {
filteredUsersToNotify = usersToNotify.stream()
.filter(u -> {
org.sakaiproject.site.api.Member member = site.getMember(u);
return member != null && member.getRole() != null
&& selectedRoles.contains(member.getRole().getId());
})
.collect(Collectors.toSet());
} else {
filteredUsersToNotify = usersToNotify;
}
🤖 Prompt for AI Agents
In
@announcement/announcement-api/api/src/java/org/sakaiproject/announcement/api/AnnouncementsUserNotificationHandler.java
around lines 206 - 213, The role-filtering stream can NPE because
site.getMember(u) or getRole() may be null; update the block that sets
filteredUsersToNotify (using selectedRoles, usersToNotify, site.getMember,
getRole, getId) to defensively check for nulls before calling
getRole().getId()—e.g., map each user to SiteMember, skip if null, get role and
skip if null, then test role.getId() against selectedRoles; ensure you use
explicit types (no var) per guidelines when adding any local variables.

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.

1 participant