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

    • MOTD delivered as broadcast notifications with configurable TTL (default 24h)
    • Central SchedulingService used for background scheduling across services
  • Improvements

    • Automatic expiration and periodic cleanup of old notifications
    • Enhanced broadcast and per-user push handling with improved error handling
    • MOTD items (and test messages) no longer show a close button in the header
  • Public API

    • Notifications now return a lightweight transfer representation including broadcast and TTL
  • Configuration

    • New properties: announcement.motd.notification.ttlhours, schedulingservice.poolsize, messaging.delete.expired.periodminutes

✏️ 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 getNotifications return type, adds a SchedulingService and implementation, migrates multiple schedulers to SchedulingService, introduces MOTD TTL config and handler changes, updates repository for expiry, and updates many call sites to a widened UserNotificationData constructor.

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/ttl/endDate fields and BROADCAST constant; changed Lombok annotations; added transfer bean; repository gained findByBroadcast and deleteExpiredNotifications; UserMessagingService.getNotifications() return type changed to transfer beans.
Messaging implementation & repository impl
kernel/kernel-impl/.../messaging/.../UserMessagingServiceImpl.java, kernel/kernel-impl/.../repository/UserNotificationRepositoryImpl.java
Switched internals to use UserNotificationTransferBean and UserNotificationData, added broadcast push path, compute endDate from TTL, added scheduled cleanup calling deleteExpiredNotifications, adjusted transactional annotation on findByToUser.
Scheduling service & wiring
kernel/api/src/main/java/org/sakaiproject/scheduling/api/SchedulingService.java, kernel/kernel-impl/src/main/java/org/sakaiproject/scheduling/impl/SchedulingServiceImpl.java, kernel/kernel-impl/src/main/webapp/WEB-INF/scheduling-components.xml, kernel/kernel-impl/src/main/webapp/WEB-INF/components.xml
Introduced SchedulingService interface and SchedulingServiceImpl with config-driven thread pool; registered bean and wired imports into component XMLs.
Consumers migrated to SchedulingService
kernel/kernel-impl/.../DbAuthzGroupService.java, ClusterEventTracking.java, StoredConfigService.java, FileConversionServiceImpl.java, PlusServiceImpl.java, microsoft-integration/impl/.../MicrosoftCommonServiceImpl.java, samigo/.../TimedAssessmentQueue.java, ...
Replaced local ScheduledExecutorService usage with injected SchedulingService, removed local scheduler lifecycle/shutdown in many services, adapted scheduling calls to SchedulingService methods and TimeUnit usage.
Announcement MOTD & config
announcement/announcement-api/api/.../AnnouncementsUserNotificationHandler.java, config/configuration/bundles/src/bundle/org/sakaiproject/config/bundle/default.sakai.properties
Added MOTD-specific broadcast handling, motdTTLHours config (default 24) read at init, applied TTL to MOTD notifications; added messaging/scheduling related properties.
Handlers & constructor callsites
many handlers and tests (e.g. assignment/.../AddAssignmentUserNotificationHandler.java, GradeAssignmentUserNotificationHandler.java, commons/.../CommonsCommentUserNotificationHandler.java, msgcntr/.../PrivateMessageUserNotificationHandler.java, lessonbuilder/..., samigo/..., tests/...)
Updated numerous call sites to the expanded UserNotificationData constructor by appending two trailing parameters (commonly false, null) to match new signature.
Web API & controllers
webapi/src/main/java/org/sakaiproject/webapi/controllers/NotificationsController.java, webapi/src/main/java/org/sakaiproject/webapi/controllers/SitesController.java
Controllers switched to use UserNotificationTransferBean and adjusted code to access bean fields directly.
Frontend
webcomponents/tool/src/main/frontend/packages/sakai-notifications/src/SakaiNotifications.js
Removed fetch credentials inclusion and adjusted rendering to hide close button for MOTD (and test) prefixes.
Tests & test configurations
kernel/kernel-impl/src/test/..., plus/impl/src/test/..., samigo/.../tests
Added/mock SchedulingService beans in multiple test configs; updated tests to new UserNotificationData constructor and adjusted default threadpool/test config values.
Properties & defaults
config/configuration/bundles/src/bundle/org/sakaiproject/config/bundle/default.sakai.properties
Added announcement.motd.notification.ttlhours=24 and new messaging/scheduling defaults (messaging.delete.expired.periodminutes, messaging.threadpool.size, schedulingservice.poolsize).

Suggested labels

do-not-close

Suggested reviewers

  • ern
  • ottenhoff
🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 22.11% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title accurately summarizes the main change: adding broadcast-type notifications to replace per-user MOTD notification entries.

✏️ 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

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.

@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

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.

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.

@kunaljaykam kunaljaykam changed the title SAK-51956 motifications Add broadcast type notification SAK-51956 notifications Add broadcast type notification Jan 8, 2026
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
@kernel/kernel-impl/src/main/java/org/sakaiproject/messaging/impl/UserMessagingServiceImpl.java:
- Around line 577-610: The pushToAllUsers method currently uses a pageSize of 1
causing one DB call per user; change it to use a larger, configurable batch size
(e.g., default 500) retrieved from ServerConfigurationService and applied to the
local pageSize variable in pushToAllUsers, keep the existing pagination loop
(first/last/done) logic but compute last = first + pageSize and increment done
by users.size(), and ensure executor and userDirectoryService calls
(getUsers/countUsers) use the new page size to drastically reduce queries during
broadcast.

In
@kernel/kernel-impl/src/main/java/org/sakaiproject/scheduling/impl/SchedulingServiceImpl.java:
- Around line 34-39: The init() method calls setRemoveOnCancelPolicy(true) on
the executor but the executor field is declared as a ScheduledExecutorService
(interface) so compilation fails; change the executor field in
SchedulingServiceImpl from ScheduledExecutorService to
ScheduledThreadPoolExecutor (or cast the returned executor to
ScheduledThreadPoolExecutor where assigned in init()), and ensure the import for
java.util.concurrent.ScheduledThreadPoolExecutor is present so
setRemoveOnCancelPolicy can be called on the concrete instance.
🧹 Nitpick comments (1)
kernel/kernel-impl/src/main/java/org/sakaiproject/messaging/impl/UserMessagingServiceImpl.java (1)

135-135: Remove unused field.

The deletingExpiredNotifications field is declared but never read or written. Consider removing it unless it's planned for future use.

♻️ Proposed cleanup
-    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 545cb65 and 07b5d1d.

📒 Files selected for processing (5)
  • config/configuration/bundles/src/bundle/org/sakaiproject/config/bundle/default.sakai.properties
  • kernel/api/src/main/java/org/sakaiproject/scheduling/api/SchedulingService.java
  • kernel/kernel-impl/src/main/java/org/sakaiproject/messaging/impl/UserMessagingServiceImpl.java
  • kernel/kernel-impl/src/main/java/org/sakaiproject/scheduling/impl/SchedulingServiceImpl.java
  • samigo/samigo-app/src/java/org/sakaiproject/tool/assessment/ui/queue/delivery/TimedAssessmentQueue.java
🚧 Files skipped from review as they are similar to previous changes (1)
  • config/configuration/bundles/src/bundle/org/sakaiproject/config/bundle/default.sakai.properties
🧰 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/api/src/main/java/org/sakaiproject/scheduling/api/SchedulingService.java
  • kernel/kernel-impl/src/main/java/org/sakaiproject/messaging/impl/UserMessagingServiceImpl.java
  • samigo/samigo-app/src/java/org/sakaiproject/tool/assessment/ui/queue/delivery/TimedAssessmentQueue.java
  • kernel/kernel-impl/src/main/java/org/sakaiproject/scheduling/impl/SchedulingServiceImpl.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 (8)
📓 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:

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

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

  • 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/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
📚 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)
🪛 GitHub Actions: Maven Build
kernel/kernel-impl/src/main/java/org/sakaiproject/scheduling/impl/SchedulingServiceImpl.java

[error] 38-38: cannot find symbol: method setRemoveOnCancelPolicy(boolean) on ScheduledExecutorService (likely API mismatch).

🪛 GitHub Actions: Windows Build
kernel/kernel-impl/src/main/java/org/sakaiproject/scheduling/impl/SchedulingServiceImpl.java

[error] 38-38: cannot find symbol: method setRemoveOnCancelPolicy(boolean) in class java.util.concurrent.ScheduledExecutorService

⏰ 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 (10)
kernel/api/src/main/java/org/sakaiproject/scheduling/api/SchedulingService.java (1)

21-47: LGTM! Clean facade design.

The interface provides a well-designed abstraction over Java's ScheduledExecutorService, exposing the three core scheduling methods with appropriate return types for task cancellation.

samigo/samigo-app/src/java/org/sakaiproject/tool/assessment/ui/queue/delivery/TimedAssessmentQueue.java (2)

46-54: LGTM! Proper migration to SchedulingService.

The constructor now accepts SchedulingService via dependency injection through ComponentManager. The Loader pattern ensures lazy initialization, and since this is a tool-level component, the kernel's SchedulingService will be available when needed.


71-76: LGTM! Correct delegation to SchedulingService.

The migration from a local thread pool to the centralized SchedulingService maintains the same scheduling parameters (0ms initial delay, 3000ms fixed rate).

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

46-68: LGTM! Clean delegation to the underlying executor.

The three scheduling methods correctly delegate to the internal executor, maintaining the same semantics as the standard Java concurrent API.

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

150-171: LGTM! Proper integration of scheduled cleanup.

The initialization correctly uses the SchedulingService to schedule periodic deletion of expired notifications. The transactional wrapper ensures data consistency, and the configurable period (default 30 minutes) provides operational flexibility.


380-401: LGTM! Proper broadcast notification handling.

The event handling correctly distinguishes between broadcast and per-user notifications, routing them appropriately through pushToAllUsers() or push(). The bean-based transformation and decoration flow is well-structured.


416-449: LGTM! Clean refactoring to use data transfer object.

The method signature now accepts a UserNotificationData bean, which improves encapsulation. The TTL-based end date calculation (lines 440-442) correctly computes expiration using eventDate.toInstant().plus(und.getTtl()).


489-499: LGTM! Correct merging of personal and broadcast notifications.

The method correctly combines per-user notifications with broadcast notifications using Stream concatenation. Since broadcast notifications have broadcast=true and per-user notifications have broadcast=false, there's no risk of duplicates. The design appropriately shows broadcasts to all users while maintaining separate personal notifications.


521-541: LGTM! Consistent decoration logic.

The refactoring to use UserNotificationTransferBean maintains the same decoration logic, properly enriching notifications with display names, formatted dates, and site titles with appropriate fallback handling.


562-575: LGTM! Test notification updated to use transfer bean.

The test notification method correctly constructs and pushes a UserNotificationTransferBean, consistent with the new notification flow.

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

🧹 Nitpick comments (7)
announcement/announcement-api/api/src/java/org/sakaiproject/announcement/api/AnnouncementsUserNotificationHandler.java (3)

115-117: Consider more robust MOTD detection.

The string contains check eventResource.contains("motd") may be fragile and could match unintended resources. Consider using a more specific pattern match (e.g., startsWith, endsWith, or exact reference comparison) to avoid false positives.

♻️ Consider a more specific check
-        if (AnnouncementService.SECURE_ANNC_ADD.equals(eventName) && eventResource.contains("motd")) {
+        if (AnnouncementService.SECURE_ANNC_ADD.equals(eventName) && eventResource.endsWith("/motd")) {
             return Optional.of(bhEvents);
         }

Alternatively, verify the expected format of MOTD resource references and adjust accordingly.


127-133: Extract "BROADCAST" as a constant.

The string literal "BROADCAST" on line 129 should be extracted as a named constant to improve maintainability and prevent typos. This is especially important if this value is used elsewhere in the codebase.

♻️ Extract constant

At the class level:

+    private static final String BROADCAST_RECIPIENT = "BROADCAST";
     private static final String SELECTED_ROLES_PROPERTY = "selectedRoles";

Then update the usage:

                 userNotificationData.setFrom(event.getUserId());
-                userNotificationData.setTo("BROADCAST");
+                userNotificationData.setTo(BROADCAST_RECIPIENT);
                 userNotificationData.setTitle(title);

207-217: Consolidate SELECTED_ROLES_PROPERTY usage across the announcement service.

The code at lines 207-217 correctly uses the SELECTED_ROLES_PROPERTY constant. However, the announcement service has an inconsistency: SELECTED_ROLES_PROPERTY is defined separately in both AnnouncementsUserNotificationHandler.java and AnnouncementAction.java, while other files (BaseAnnouncementService.java, SiteEmailNotificationAnnc.java, AnnouncementEntityProviderImpl.java, BaseMessage.java) hardcode the string "selectedRoles" directly. Consider defining the constant in a shared location and using it consistently across all announcement-related classes to improve maintainability.

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

135-135: Remove unused field.

The deletingExpiredNotifications field is declared but never used anywhere in the code.

♻️ Proposed fix
-    private boolean deletingExpiredNotifications = false;

160-171: Consider error handling and initial delay for scheduled task.

The scheduled expiry cleanup has two considerations:

  1. Initial delay of 0: The task runs immediately on startup, which could occur before the system is fully initialized. Consider adding a small initial delay.
  2. Error handling: While the scheduler will continue running after exceptions, adding explicit error handling would improve observability.
💡 Suggested improvements
         long deleteExpiredPeriod = serverConfigurationService.getLong("messaging.delete.expired.periodminutes", 30);

         // Clean up expired notifications every 30 minutes
         schedulingService.scheduleWithFixedDelay(() -> {
-
-                transactionTemplate.execute(new TransactionCallbackWithoutResult() {
-
-                    protected void doInTransactionWithoutResult(TransactionStatus status) {
-                        userNotificationRepository.deleteExpiredNotifications();
-                    }
-                });
-            }, 0, deleteExpiredPeriod, TimeUnit.MINUTES);
+            try {
+                transactionTemplate.execute(new TransactionCallbackWithoutResult() {
+                    protected void doInTransactionWithoutResult(TransactionStatus status) {
+                        int deleted = userNotificationRepository.deleteExpiredNotifications();
+                        log.debug("Deleted {} expired notifications", deleted);
+                    }
+                });
+            } catch (Exception e) {
+                log.error("Failed to delete expired notifications", e);
+            }
+        }, deleteExpiredPeriod, deleteExpiredPeriod, TimeUnit.MINUTES);

617-670: Consider PII in logging statements.

Lines 637 and 658 log user IDs (un.to). While user IDs are typically less sensitive than emails or names, consider whether this aligns with your privacy policy for production logs.

Based on learnings, avoid logging sensitive identifiers like emails. If user IDs are considered PII in your context, consider using a hashed value or omitting them from production logs.


153-155: Verify thread pool sizing and cleanup.

The thread pool is configured with messaging.threadpool.size (default 10) but the destroy() method only attempts graceful shutdown without forcing termination. For long-running tasks, consider adding a timeout with awaitTermination().

♻️ Optional improvement for graceful shutdown
     public void destroy() {

         if (executor != null) {
             try {
                 // Attempt a graceful shutdown
                 executor.shutdown();
+                if (!executor.awaitTermination(60, TimeUnit.SECONDS)) {
+                    executor.shutdownNow();
+                    if (!executor.awaitTermination(60, TimeUnit.SECONDS)) {
+                        log.error("Messaging thread pool did not terminate");
+                    }
+                }
                 log.info("Messaging thread pool shutdown initiated");
             } catch (Exception e) {
                 log.error("Error shutting down messaging thread pool: {}", e.toString());
+                executor.shutdownNow();
             }
         }
     }
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 07b5d1d and 9034cdd.

📒 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/messaging/impl/UserMessagingServiceImpl.java
  • kernel/kernel-impl/src/main/java/org/sakaiproject/scheduling/impl/SchedulingServiceImpl.java
🚧 Files skipped from review as they are similar to previous changes (1)
  • kernel/kernel-impl/src/main/java/org/sakaiproject/scheduling/impl/SchedulingServiceImpl.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
{**/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 (14)
📓 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:

  • 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-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-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-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
📚 Learning: 2025-11-24T19:11:42.092Z
Learnt from: CR
Repo: sakaiproject/sakai PR: 0
File: .cursor/rules/sakai-rules.md:0-0
Timestamp: 2025-11-24T19:11:42.092Z
Learning: Applies to **/*.java : Treat any PR or suggestion containing Java `var` as non-compliant. Recommend replacing with explicit types before merge

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 **/*.java : 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`

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 **/*.java : Do not use local variable type inference (`var`) in Java code; always declare explicit types (e.g., `List<String> names = new ArrayList<>();`)

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 **/*.java : Do not use local variable type inference (var) in Java; always declare explicit types

Applied to files:

  • announcement/announcement-api/api/src/java/org/sakaiproject/announcement/api/AnnouncementsUserNotificationHandler.java
📚 Learning: 2025-11-24T19:11:42.092Z
Learnt from: CR
Repo: sakaiproject/sakai PR: 0
File: .cursor/rules/sakai-rules.md:0-0
Timestamp: 2025-11-24T19:11:42.092Z
Learning: Applies to **/*.java : When proposing Java code, spell out full types in local variable declarations, `for` loops, and try-with-resources

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
🧬 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). (1)
  • GitHub Check: sakai-deploy
🔇 Additional comments (7)
announcement/announcement-api/api/src/java/org/sakaiproject/announcement/api/AnnouncementsUserNotificationHandler.java (3)

220-224: LGTM: Constructor usage is clear and explicit.

The explicit constructor call with all parameters clearly specified aligns with the coding guidelines. The parameters correctly indicate a non-broadcast notification with no TTL, which is appropriate for regular announcement notifications.


240-249: LGTM: Good DRY principle application.

Extracting the common query predicate into a helper method improves maintainability and reduces duplication. The method signature and logic are clear and correct.


91-94: Configuration key is already documented. The "announcement.motd.notification.ttlhours" configuration key is documented in config/configuration/bundles/src/bundle/org/sakaiproject/config/bundle/default.sakai.properties (line 2260). For defensive robustness, consider validating that the retrieved value is positive before using it in Duration.ofHours() to prevent unexpected behavior from misconfiguration.

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

377-401: LGTM - Notification handling flow updated correctly.

The refactored flow properly:

  • Creates notifications via doInsert with the new UserNotificationData parameter
  • Converts to transfer bean and decorates before pushing
  • Distinguishes between broadcast and per-user push notifications
  • Only pushes non-deferred notifications

416-449: LGTM - doInsert refactored to use UserNotificationData.

The method correctly:

  • Extracts all required fields from the UserNotificationData bean
  • Processes the title using formattedText.processFormattedText
  • Calculates endDate from ttl when provided
  • Handles the new broadcast flag
  • Maintains transactional integrity

489-499: LGTM - getNotifications returns transfer beans and includes broadcasts.

The method correctly:

  • Returns decorated UserNotificationTransferBean instances
  • Combines user-specific notifications with broadcast notifications
  • Applies decoration to all notifications before returning

The stream concatenation is appropriate since broadcast notifications have no specific toUser.


521-541: LGTM - decorateNotification updated for transfer bean.

The method correctly:

  • Operates on UserNotificationTransferBean parameter
  • Populates display name, formatted date, and site title
  • Handles missing users and sites gracefully with fallback values
  • Converts formatted title to plain text

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
@kernel/kernel-impl/src/main/java/org/sakaiproject/messaging/impl/UserMessagingServiceImpl.java:
- Line 135: Remove the unused boolean field deletingExpiredNotifications from
the UserMessagingServiceImpl class; locate the declaration "private boolean
deletingExpiredNotifications = false;" in UserMessagingServiceImpl and delete
that line (ensure no other code references it before removal).
- Around line 582-610: The background task launched in executor.execute
currently calls userDirectoryService.countUsers() and
userDirectoryService.getUsers() without any exception handling, which can let
exceptions kill the thread silently; wrap the whole runnable body (the loop and
the initial count) in a try-catch (catching Exception), log the error via the
class logger (or process logger) including context (e.g. bean.event/ref) and
ensure the loop terminates or retries safely; specifically add a try { ... }
catch (Exception e) { logger.error("Error processing users in executor.execute
for UserNotificationTransferBean event={}, ref={}", bean.event, bean.ref, e); }
around the code that uses userDirectoryService, and avoid losing partial
progress (e.g. break/return after logging) to prevent repeated failures in
UserNotificationTransferBean, push, decorateNotification paths.
- Around line 163-171: Remove the redundant transactionTemplate.execute wrapper
in the scheduled task: inside the lambda passed to
schedulingService.scheduleWithFixedDelay, call
userNotificationRepository.deleteExpiredNotifications() directly instead of
wrapping it in transactionTemplate.execute(...), since
deleteExpiredNotifications() in UserNotificationRepositoryImpl is already
annotated with @Transactional and will manage its own transaction.
🧹 Nitpick comments (2)
kernel/kernel-impl/src/main/java/org/sakaiproject/messaging/impl/UserMessagingServiceImpl.java (2)

590-595: Refactor manual field copying to use a helper method or constructor.

The manual field-by-field copying is error-prone and will require maintenance if new fields are added to UserNotificationTransferBean. Consider adding a copy constructor or factory method to the bean class.

♻️ Proposed refactor

Add to UserNotificationTransferBean:

public static UserNotificationTransferBean copyForBroadcast(UserNotificationTransferBean source) {
    UserNotificationTransferBean bean = new UserNotificationTransferBean();
    bean.from = source.from;
    bean.event = source.event;
    bean.ref = source.ref;
    bean.title = source.title;
    // Only copy fields relevant for broadcast
    return bean;
}

Then update the code:

-            UserNotificationTransferBean un = new UserNotificationTransferBean();
-            un.from  = bean.from;
-            un.event = bean.event;
-            un.ref = bean.ref;
-            un.title = bean.title;
+            UserNotificationTransferBean template = UserNotificationTransferBean.copyForBroadcast(bean);
 
             while (done < total) {
 
                 List<User> users = userDirectoryService.getUsers(first, last);
 
                 users.forEach(u -> {
 
-                    un.to = u.getId();
-                    push(decorateNotification(un));
+                    template.to = u.getId();
+                    push(decorateNotification(template));
                 });

600-604: Optimize decoration to avoid redundant lookups.

The decorateNotification call inside the loop will look up the from user repeatedly for every recipient. Consider decorating once before the loop and only setting the to field per user.

♻️ Proposed optimization
             UserNotificationTransferBean un = new UserNotificationTransferBean();
             un.from  = bean.from;
             un.event = bean.event;
             un.ref = bean.ref;
             un.title = bean.title;
+            
+            // Decorate once to get fromDisplayName, formattedEventDate, siteTitle
+            un.to = ""; // temporary placeholder
+            decorateNotification(un);
 
             while (done < total) {
 
                 List<User> users = userDirectoryService.getUsers(first, last);
 
                 users.forEach(u -> {
 
                     un.to = u.getId();
-                    push(decorateNotification(un));
+                    push(un);
                 });
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9034cdd and 91252e6.

📒 Files selected for processing (1)
  • 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
{**/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 (8)
📓 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:

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

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

  • 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
📚 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
⏰ 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). (2)
  • GitHub Check: sakai-deploy
  • GitHub Check: maven-build
🔇 Additional comments (6)
kernel/kernel-impl/src/main/java/org/sakaiproject/messaging/impl/UserMessagingServiceImpl.java (6)

380-401: LGTM! Clean refactor to support broadcast notifications.

The update to use UserNotificationTransferBean and the new broadcast handling logic is well-structured. The distinction between broadcast and per-user push notifications is clear and correct.


416-449: LGTM! The refactor to accept UserNotificationData improves cohesion.

The method now accepts a cohesive data object instead of multiple parameters, and the TTL-based endDate calculation is correctly implemented. The mapping from UserNotificationData to UserNotification is complete and accurate.


521-541: LGTM! Transfer bean decoration is correctly implemented.

The method properly populates display fields (fromDisplayName, formattedEventDate, siteTitle) from the transfer bean and handles exceptions gracefully with appropriate fallbacks.


562-575: LGTM! Test notification correctly uses transfer bean.

The test notification creation properly uses UserNotificationTransferBean with all required fields populated. The use of resourceLoader for the title maintains i18n support.


489-499: LGTM! Broadcast notification aggregation is correctly implemented.

The method properly combines per-user and broadcast notifications using Stream.concat. The conversion through UserNotificationTransferBean::of and decoration is clean. The underlying findByBroadcast(true) repository method already includes appropriate filtering to exclude expired notifications (via endDate > now), so notifications returned are guaranteed to be active.


83-83: Remove unused Duration import at line 83.

The Duration class is imported but never used in this file. Remove the import statement to keep imports clean.

⛔ Skipped due to learnings
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-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-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: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-11-24T19:12:27.663Z
Learning: Applies to **/sakai-notifications.properties : Reconcile subscriptions proactively and keep localization strings in `sakai-notifications.properties`

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

🤖 Fix all issues with AI agents
In
@kernel/kernel-impl/src/main/java/org/sakaiproject/messaging/impl/UserMessagingServiceImpl.java:
- Line 135: The field deletingExpiredNotifications in class
UserMessagingServiceImpl is unused and should be removed; locate the private
boolean deletingExpiredNotifications declaration in UserMessagingServiceImpl and
delete that field declaration, then run a build/IDE compile to ensure no
references remain and update any related javadoc or comments if they referenced
this flag.
🧹 Nitpick comments (2)
kernel/kernel-impl/src/main/java/org/sakaiproject/messaging/impl/UserMessagingServiceImpl.java (2)

392-392: Remove redundant tool assignment.

Line 392 sets bean.tool = und.getCommonToolId(), but doInsert() already sets the tool on line 444, and UserNotificationTransferBean.of() copies this field. This assignment is redundant and can be removed.

♻️ Proposed fix
                                 if (!finalDeferred) {
-                                    bean.tool = und.getCommonToolId();
-
                                     if (und.isBroadcast()) {

577-611: Optimize broadcast decoration to avoid redundant lookups.

The method calls decorateNotification(un) inside the loop for every user (line 603), performing identical lookups for fromDisplayName, formattedEventDate, and siteTitle each time. Since decorateNotification() doesn't depend on the to field, these fields are the same for all recipients.

Decorate once before the loop to eliminate thousands of redundant database/service calls.

♻️ Proposed optimization
     private void pushToAllUsers(UserNotificationTransferBean bean) {
 
         executor.execute(() -> {
 
             int pageSize = 100;
             int first = 1;
             int last = first + pageSize;
             int total = userDirectoryService.countUsers();
             int done = 0;
 
             UserNotificationTransferBean un = new UserNotificationTransferBean();
             un.from  = bean.from;
             un.event = bean.event;
             un.ref = bean.ref;
             un.title = bean.title;
+            // Decorate once before the loop
+            UserNotificationTransferBean decorated = decorateNotification(un);
 
             while (done < total) {
 
                 List<User> users = userDirectoryService.getUsers(first, last);
 
                 users.forEach(u -> {
 
-                    un.to = u.getId();
-                    push(decorateNotification(un));
+                    decorated.to = u.getId();
+                    push(decorated);
                 });
 
                 first = last + 1;
                 last = first + pageSize;
                 done += users.size();
             }
         });
     }
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 91252e6 and 5f4e264.

📒 Files selected for processing (1)
  • 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
{**/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 (7)
📓 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:

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

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

  • 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
📚 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
🧬 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). (2)
  • GitHub Check: sakai-deploy
  • GitHub Check: maven-build
🔇 Additional comments (5)
kernel/kernel-impl/src/main/java/org/sakaiproject/messaging/impl/UserMessagingServiceImpl.java (5)

416-448: LGTM!

The refactoring to accept UserNotificationData consolidates parameter passing and cleanly handles the new TTL-based endDate calculation.


456-464: LGTM!

Good refactoring to extract common user ID retrieval logic.


489-499: LGTM!

The method correctly merges per-user and broadcast notifications into a unified transfer bean list.


521-541: LGTM!

The method correctly decorates the transfer bean with display names and formatted dates, with appropriate fallback handling for missing entities.


656-673: LGTM!

Excellent enhanced error handling with appropriate actions for different HTTP status codes, including the 429 rate limit logging mentioned in the commit message.

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

🤖 Fix all issues with AI agents
In
@kernel/kernel-impl/src/main/java/org/sakaiproject/messaging/impl/UserMessagingServiceImpl.java:
- Around line 153-154: UserMessagingServiceImpl currently reads threadPoolSize
via serverConfigurationService.getInt and passes it directly to
Executors.newFixedThreadPool, which can throw IllegalArgumentException for
zero/negative values; validate the value (e.g., enforce minimum 1 or fallback to
the default 10) before creating the executor and log a warning when an invalid
config is detected so initialization uses a safe pool size; update the code
around the threadPoolSize variable and the executor =
Executors.newFixedThreadPool(...) call accordingly.
🧹 Nitpick comments (4)
kernel/kernel-impl/src/main/java/org/sakaiproject/messaging/impl/UserMessagingServiceImpl.java (4)

135-135: Remove unused field.

The field deletingExpiredNotifications is declared but never used anywhere in the code.

🧹 Proposed cleanup
-    private boolean deletingExpiredNotifications = false;

160-165: Update comment to reflect configurable period.

The comment states "every 30 minutes" but the actual period is configurable via messaging.delete.expired.periodminutes.

📝 Proposed fix for clarity
         long deleteExpiredPeriod = serverConfigurationService.getLong("messaging.delete.expired.periodminutes", 30);

-        // Clean up expired notifications every 30 minutes
+        // Clean up expired notifications periodically (default: 30 minutes)
         schedulingService.scheduleWithFixedDelay(() -> {
             userNotificationRepository.deleteExpiredNotifications();
         }, 0, deleteExpiredPeriod, TimeUnit.MINUTES);

376-393: Consider removing redundant tool assignment.

Line 386 sets bean.tool = und.getCommonToolId() after doInsert() already sets the tool field from the same source at line 438. This redundant assignment may cause confusion.

♻️ Proposed simplification

If the tool is already correctly set by doInsert(), this line can be removed:

                                UserNotificationTransferBean bean
                                    = UserNotificationTransferBean.of(doInsert(from,
                                        und,
                                        event,
                                        ref,
                                        e.getEventTime(),
                                        finalDeferred)
                                    );

                                if (!finalDeferred) {
-                                    bean.tool = und.getCommonToolId();
-
                                    if (und.isBroadcast()) {
                                        pushToAllUsers(decorateNotification(bean));
                                    } else {
                                        push(decorateNotification(bean));
                                    }
                                }

However, verify that doInsert() always correctly sets the tool field before removing this line.


574-605: Optimize broadcast push for large user bases.

The current implementation has several efficiency concerns:

  1. Redundant bean creation (lines 584-589): Creates a new bean and copies fields from the input instead of reusing the decorated bean.
  2. Repeated decoration (line 597): Calls decorateNotification() for each user, which fetches and formats the same from-user details repeatedly.
  3. Synchronous per-user processing: Pushes to all users sequentially, which could be slow for thousands of users.
♻️ Proposed optimization
 private void pushToAllUsers(UserNotificationTransferBean bean) {

     executor.execute(() -> {

         int pageSize = 100;
         int first = 1;
         int last = first + pageSize;
         int total = userDirectoryService.countUsers();
         int done = 0;

-        UserNotificationTransferBean un = new UserNotificationTransferBean();
-        un.from  = bean.from;
-        un.event = bean.event;
-        un.ref = bean.ref;
-        un.title = bean.title;
-
         while (done < total) {

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

             users.forEach(u -> {

-                un.to = u.getId();
-                push(decorateNotification(un));
+                // Create a minimal per-user bean with pre-decorated shared fields
+                UserNotificationTransferBean userBean = new UserNotificationTransferBean();
+                userBean.from = bean.from;
+                userBean.to = u.getId();
+                userBean.event = bean.event;
+                userBean.ref = bean.ref;
+                userBean.title = bean.title;
+                userBean.fromDisplayName = bean.fromDisplayName;
+                userBean.formattedEventDate = bean.formattedEventDate;
+                userBean.siteTitle = bean.siteTitle;
+                push(userBean);
             });

             first = last + 1;
             last = first + pageSize;
             done += users.size();
         }
     });
 }

This avoids re-fetching and re-formatting the same sender information for every user.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5f4e264 and f3a880b.

📒 Files selected for processing (1)
  • 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
{**/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 (7)
📓 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:

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

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

  • 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
📚 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
🧬 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)
🪛 GitHub Actions: Windows Build
kernel/kernel-impl/src/main/java/org/sakaiproject/messaging/impl/UserMessagingServiceImpl.java

[error] 154-154: Failed to initialize ThreadPool in init()


[error] 154-154: Invalid number of threads for ThreadPool creation


[error] 154-154: IllegalArgumentException during ThreadPoolExecutor creation: thread count must be > 0

⏰ 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 (4)
kernel/kernel-impl/src/main/java/org/sakaiproject/messaging/impl/UserMessagingServiceImpl.java (4)

650-667: LGTM! Robust error handling for push notifications.

The status code handling is well-implemented:

  • Invalid subscriptions (410, 404, 400) are properly cleaned up
  • Configuration issues (403, 429) are logged with helpful context
  • Follows Web Push best practices for error handling

515-535: LGTM! Well-structured notification decoration.

The bean-based decoration correctly:

  • Fetches and sets user display name
  • Formats event date using user timezone preferences
  • Handles missing users/sites gracefully with fallback values
  • Converts formatted text titles to plain text

397-401: Verify transaction management approach aligns with PR objectives.

The PR objectives mention "Removed TransactionTemplate wrapper" but this file still uses TransactionTemplate at lines 397 and 423. Confirm whether:

  1. TransactionTemplate removal applies to other files only
  2. These usages are intentional exceptions
  3. Further refactoring is planned

Also applies to: 423-442


488-490: No changes needed. The stream concatenation logic is safe from duplicates—broadcast notifications consistently use "BROADCAST" as the toUser value, which prevents overlap with user-specific notifications. This design ensures findByToUser(userId) and findByBroadcast(true) return distinct sets without requiring explicit deduplication.

Likely an incorrect or invalid review comment.

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 (3)
webcomponents/tool/src/main/frontend/packages/sakai-notifications/src/SakaiNotifications.js (1)

87-90: Restore credentials: "include" to the initial notifications fetch.

The /users/me/notifications endpoint requires authentication via checkSakaiSession(). Removing credentials: "include" prevents the browser from sending authentication cookies, causing the initial notification load to fail with a 403 Forbidden response.

🔧 Required fix
 fetch(this.url, {
   cache: "no-cache",
   headers: { "Content-Type": "application/json" },
+  credentials: "include",
 })
samigo/samigo-app/src/java/org/sakaiproject/tool/assessment/ui/queue/delivery/TimedAssessmentQueue.java (1)

31-81: Fix thread-safety, null-safety, and logging issues in task scheduling and removal.

  1. NPE in remove(): Line 94 calls tasks.get(timedAG).cancel(true) without null check; if the task doesn't exist, this crashes.
  2. Unsafe task map: Line 44 uses HashMap<Long, ScheduledFuture<?>> which is not thread-safe; must be ConcurrentHashMap.
  3. Unchecked null SchedulingService: Line 47 passes ComponentManager.get(SchedulingService.class) to constructor without null check; if unavailable, calling schedulingService.scheduleAtFixedRate() at line 72 will NPE.
  4. Queue entry left on scheduling failure: Line 66 adds to queue before scheduling (line 71–76); if scheduling throws (line 78), the queue entry remains with no corresponding task.
  5. Logging violations: Lines 77, 79, and 92 use string concatenation instead of SLF4J parameterized logging.
Proposed fix
@@
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ScheduledFuture;
 import java.util.concurrent.TimeUnit;
-import java.util.HashMap;
+import java.util.Map;
@@
 public class TimedAssessmentQueue { 
 
   private ConcurrentHashMap queue;
-  private SchedulingService schedulingService;
-  private HashMap<Long, ScheduledFuture<?>> tasks;
+  private final SchedulingService schedulingService;
+  private final Map<Long, ScheduledFuture<?>> tasks;
@@
   private TimedAssessmentQueue(SchedulingService schedulingService) {
+    if (schedulingService == null) {
+      throw new IllegalStateException("SchedulingService is not available");
+    }
     this.schedulingService = schedulingService;
     queue = new ConcurrentHashMap();
-    tasks = new HashMap<>();
+    tasks = new ConcurrentHashMap<>();
   } 
@@
   public void add(TimedAssessmentGradingModel timedAG){
           queue.put(timedAG.getAssessmentGradingId(),timedAG);
           try {
               tasks.put(timedAG.getAssessmentGradingId(),
                             schedulingService.scheduleAtFixedRate(
                             new TimedAssessmentRunnable(timedAG.getAssessmentGradingId()),
                             0,
                             3000,
                             TimeUnit.MILLISECONDS)); 
-              log.info( "SAMIGO_TIMED_ASSESSMENT:QUEUE:NEWTIMER:SUCCESS ID:" + timedAG.getAssessmentGradingId());
+              log.info("SAMIGO_TIMED_ASSESSMENT:QUEUE:NEWTIMER:SUCCESS ID:{}", timedAG.getAssessmentGradingId());
           } catch (Exception ex) {
+              queue.remove(timedAG.getAssessmentGradingId());
-              log.error("SAMIGO_TIMED_ASSESSMENT:QUEUE:NEWTIMER:FAILED ID:" + timedAG.getAssessmentGradingId() + " Exception:" + ex);
+              log.error("SAMIGO_TIMED_ASSESSMENT:QUEUE:NEWTIMER:FAILED ID:{} Exception:{}", timedAG.getAssessmentGradingId(), ex);
           }
   }
@@
   public void remove(long timedAG){
-    log.info("SAMIGO_TIMED_ASSESSMENT:QUEUE:REMOVE ID:" + timedAG);
+    log.info("SAMIGO_TIMED_ASSESSMENT:QUEUE:REMOVE ID:{}", timedAG);
     ScheduledFuture<?> task = tasks.get(timedAG);
     if (task != null) {
       task.cancel(true);
kernel/kernel-impl/src/main/java/org/sakaiproject/authz/impl/DbAuthzGroupService.java (1)

63-130: Store the scheduled task handle and cancel it in destroy() to prevent accessing closed caches.

The RefreshAuthzGroupTask continues running after destroy() closes the caches (authzUserGroupIdsCache, maintainRolesCache, realmLocksCache), causing potential NullPointerException or other errors. The SchedulingService.scheduleWithFixedDelay() returns a ScheduledFuture<?> that must be stored and cancelled in destroy(). Also consider cancelling any previous task if init() is called multiple times.

Proposed fix
@@
 import java.util.concurrent.TimeUnit;
+import java.util.concurrent.ScheduledFuture;
@@
 public abstract class DbAuthzGroupService extends BaseAuthzGroupService implements Observer
 {
@@
 	private MemoryService m_memoryService;
 
 	protected abstract SchedulingService schedulingService();
+
+    private ScheduledFuture<?> refreshAuthzGroupTask;
@@
-            schedulingService().scheduleWithFixedDelay(
+            refreshAuthzGroupTask = schedulingService().scheduleWithFixedDelay(
                 new RefreshAuthzGroupTask(),
                 120, // minimally wait 2 mins for sakai to start
                 refreshTaskInterval, // delay before running again
                 TimeUnit.SECONDS
             );
@@
 	public void destroy()
 	{
+        if (refreshAuthzGroupTask != null) {
+            refreshAuthzGroupTask.cancel(true);
+            refreshAuthzGroupTask = null;
+        }
+
 		// done with event watching
 		eventTrackingService().deleteObserver(this);
🤖 Fix all issues with AI agents
In
@kernel/api/src/main/java/org/sakaiproject/messaging/api/UserNotificationTransferBean.java:
- Around line 44-59: The factory method
UserNotificationTransferBean.of(UserNotification un) currently dereferences un
without validation; add a null check at the start of that method (e.g., use
Objects.requireNonNull(un, "un must not be null") or explicitly throw
IllegalArgumentException/NullPointerException) so the method fails fast with a
clear error instead of causing a NPE later when calling un.get...; keep the rest
of the method unchanged.

In
@kernel/kernel-impl/src/test/java/org/sakaiproject/messaging/impl/RuntimeRegistrationTest.java:
- Line 23: Remove the unused import statement for ServerConfigurationService
from the RuntimeRegistrationTest class; locate the import line "import
org.sakaiproject.component.api.ServerConfigurationService;" at the top of
RuntimeRegistrationTest.java and delete it, then save and re-run static
import/organize imports to ensure no other unused imports remain.
🧹 Nitpick comments (8)
kernel/kernel-impl/src/main/java/org/sakaiproject/event/impl/EventTrackingTest.java (1)

95-101: Add @Override and verify tests wire a non-null SchedulingService when scheduling is exercised.
Returning null is consistent with the other collaborator accessors here, but if ClusterEventTracking now calls schedulingService() on any exercised code path, this can become a latent NPE in tests.

Proposed tweak
+	@Override
 	protected SchedulingService schedulingService()
 	{
 		return null;
 	}
webcomponents/tool/src/main/frontend/packages/sakai-notifications/src/SakaiNotifications.js (1)

148-148: Consider readability: if statement vs. short-circuit pattern.

While the short-circuit assignment is functionally correct and concise, an explicit if statement may be clearer for readers less familiar with this pattern:

if (!this._filteredNotifications.has(toolEventPrefix)) {
  this._filteredNotifications.set(toolEventPrefix, []);
}

This is a minor style preference; the current code is valid.

kernel/kernel-impl/src/test/java/org/sakaiproject/messaging/impl/UserMessagingServiceTestConfiguration.java (1)

93-96: Consider returning a smaller messaging.threadpool.size in tests if it spawns real threads.
If this value is used to create real executor pools in the test context, dropping it to 1/2 can make tests faster and less flaky.

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

90-115: Avoid “magic args” for new UserNotificationData fields; centralize defaults.

Now that UserNotificationData has extra fields, repeating ..., false, null in multiple places is easy to get wrong. Consider introducing a small private helper (or builder/factory if available) so call sites read intent (e.g., “non-broadcast, no TTL”) instead of positional args.

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

22-26: Remove unused Lombok imports.

The Lombok annotations (@AllArgsConstructor, @NoArgsConstructor, @Data) are imported but not applied to the class. Either apply these annotations to generate constructors and accessors, or remove the unused imports.

♻️ Remove unused imports
-import lombok.AllArgsConstructor;
-import lombok.NoArgsConstructor;
-import lombok.Data;
-
 public class UserNotificationTransferBean {

28-42: Consider using proper encapsulation.

All fields are declared as public, which violates encapsulation best practices. Consider applying Lombok's @Data annotation (which is already imported) to generate getters and setters while keeping fields private.

♻️ Apply Lombok @DaTa for encapsulation
+@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;
+    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;
 
-    public String fromDisplayName;
-    public String siteTitle;
-    public String formattedEventDate;
+    private String fromDisplayName;
+    private String siteTitle;
+    private String formattedEventDate;

Note: This change would require updating direct field access to use getters in other files (e.g., SitesController.java).

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

573-604: Optimize broadcast push for large user bases.

The current implementation pages through all users and calls decorateNotification for each one, which performs database lookups for user details and site information. For systems with thousands of users, this could cause performance issues and database load spikes.

Consider optimizing by:

  • Batch-fetching user display names before the loop
  • Avoiding decoration for push notifications (push client can decorate on receipt)
  • Using a background job queue for very large broadcasts
💡 Alternative approach sketch
private void pushToAllUsers(UserNotificationTransferBean bean) {
    executor.execute(() -> {
        int pageSize = 100;
        int first = 1;
        int total = userDirectoryService.countUsers();
        
        // Prepare the base notification once
        UserNotificationTransferBean un = new UserNotificationTransferBean();
        un.from = bean.from;
        un.event = bean.event;
        un.ref = bean.ref;
        un.title = bean.title;
        // Avoid calling decorateNotification per user
        
        while (first < total) {
            int last = first + pageSize;
            List<User> users = userDirectoryService.getUsers(first, last);
            
            // Push without decoration; client decorates
            users.forEach(u -> {
                un.to = u.getId();
                push(un); // Push without decoration
            });
            
            first = last + 1;
        }
    });
}

Note: This assumes push clients can handle undecorated notifications.


649-666: Consider using enhanced switch expressions (Java 17).

Since the project uses Java 17, consider refactoring to enhanced switch syntax for improved readability and safety. This is a minor style improvement and entirely optional.

♻️ Enhanced switch syntax example
-                    switch (statusCode) {
-                        case 410:
-                        case 404:
-                        case 400:
-                            log.info("Removing invalid push subscription for user {} due to status {}", 
-                                un.to, statusCode);
-                            // Clear the invalid subscription
-                            clearPushSubscription(pushSubscription);
-                            break;
-                        case 403:
-                            log.warn("Push failed with {}. Check VAPID configuration", statusCode);
-                            break;
-                        case 429:
-                            log.warn("Push failed with {}. Check rate of push message sending", statusCode);
-                            break;
-                        default:
-                            log.warn("Push failed with {}", statusCode);
-                    }
+                    switch (statusCode) {
+                        case 410, 404, 400 -> {
+                            log.info("Removing invalid push subscription for user {} due to status {}", 
+                                un.to, statusCode);
+                            clearPushSubscription(pushSubscription);
+                        }
+                        case 403 -> log.warn("Push failed with {}. Check VAPID configuration", statusCode);
+                        case 429 -> log.warn("Push failed with {}. Check rate of push message sending", statusCode);
+                        default -> log.warn("Push failed with {}", statusCode);
+                    }
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f3a880b and e1b9e6e.

📒 Files selected for processing (41)
  • 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/RuntimeRegistrationTest.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-app/src/java/org/sakaiproject/tool/assessment/ui/queue/delivery/TimedAssessmentQueue.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)
  • webapi/src/main/java/org/sakaiproject/webapi/controllers/NotificationsController.java
  • kernel/kernel-impl/src/main/webapp/WEB-INF/scheduling-components.xml
  • kernel/kernel-impl/src/main/webapp/WEB-INF/event-components.xml
  • kernel/kernel-impl/src/main/webapp/WEB-INF/configstore-components.xml
  • kernel/kernel-impl/src/main/java/org/sakaiproject/scheduling/impl/SchedulingServiceImpl.java
  • kernel/api/src/main/java/org/sakaiproject/messaging/api/UserNotificationData.java
  • plus/impl/src/main/java/org/sakaiproject/plus/impl/PlusServiceImpl.java
  • assignment/api/src/java/org/sakaiproject/assignment/api/GradeAssignmentUserNotificationHandler.java
  • assignment/api/src/java/org/sakaiproject/assignment/api/AddAssignmentUserNotificationHandler.java
  • samigo/samigo-services/src/java/org/sakaiproject/tool/assessment/TestsAndQuizzesUserNotificationHandler.java
  • kernel/kernel-impl/src/main/java/org/sakaiproject/messaging/impl/repository/UserNotificationRepositoryImpl.java
  • microsoft-integration/impl/src/main/java/org/sakaiproject/microsoft/impl/MicrosoftCommonServiceImpl.java
  • kernel/kernel-impl/src/main/java/org/sakaiproject/authz/impl/AuthzGroupServiceTest.java
  • kernel/api/src/main/java/org/sakaiproject/scheduling/api/SchedulingService.java
  • kernel/kernel-impl/src/main/java/org/sakaiproject/event/impl/ClusterEventTracking.java
🧰 Additional context used
📓 Path-based instructions (10)
**/*.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/authz/impl/DbAuthzGroupService.java
  • lessonbuilder/components/src/java/org/sakaiproject/lessonbuildertool/impl/AddLessonsCommentUserNotificationHandlerImpl.java
  • kernel/kernel-impl/src/test/java/org/sakaiproject/messaging/impl/RuntimeRegistrationTest.java
  • kernel/kernel-impl/src/test/java/org/sakaiproject/event/impl/test/LearningResourceTestConfiguration.java
  • kernel/api/src/main/java/org/sakaiproject/messaging/api/UserNotificationTransferBean.java
  • kernel/kernel-impl/src/main/java/org/sakaiproject/config/impl/StoredConfigService.java
  • kernel/kernel-impl/src/main/java/org/sakaiproject/event/impl/EventTrackingTest.java
  • samigo/samigo-app/src/java/org/sakaiproject/tool/assessment/ui/queue/delivery/TimedAssessmentQueue.java
  • plus/impl/src/test/org/sakaiproject/plus/impl/PlusTestConfiguration.java
  • webapi/src/main/java/org/sakaiproject/webapi/controllers/SitesController.java
  • kernel/kernel-impl/src/test/java/org/sakaiproject/messaging/impl/UserMessagingServiceTests.java
  • announcement/announcement-api/api/src/java/org/sakaiproject/announcement/api/AnnouncementsUserNotificationHandler.java
  • kernel/kernel-impl/src/test/java/org/sakaiproject/content/impl/test/FileConversionServiceTestConfiguration.java
  • kernel/kernel-impl/src/main/java/org/sakaiproject/content/impl/FileConversionServiceImpl.java
  • commons/api/src/java/org/sakaiproject/commons/api/CommonsCommentUserNotificationHandler.java
  • msgcntr/messageforums-api/src/java/org/sakaiproject/api/app/messageforums/PrivateMessageUserNotificationHandler.java
  • kernel/kernel-impl/src/main/java/org/sakaiproject/messaging/impl/UserMessagingServiceImpl.java
  • kernel/api/src/main/java/org/sakaiproject/messaging/api/model/UserNotification.java
  • kernel/api/src/main/java/org/sakaiproject/messaging/api/UserMessagingService.java
  • kernel/kernel-impl/src/test/java/org/sakaiproject/messaging/impl/UserMessagingServiceTestConfiguration.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:

  • plus/impl/src/main/webapp/WEB-INF/components.xml
  • kernel/kernel-impl/src/main/webapp/WEB-INF/authz-components.xml
  • kernel/kernel-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:

  • plus/impl/src/main/webapp/WEB-INF/components.xml
  • kernel/kernel-impl/src/main/webapp/WEB-INF/authz-components.xml
  • kernel/kernel-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:

  • plus/impl/src/main/webapp/WEB-INF/components.xml
  • kernel/kernel-impl/src/main/webapp/WEB-INF/authz-components.xml
  • kernel/kernel-impl/src/main/webapp/WEB-INF/components.xml
  • webcomponents/tool/src/main/frontend/packages/sakai-notifications/src/SakaiNotifications.js
**/*.{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:

  • plus/impl/src/main/webapp/WEB-INF/components.xml
  • kernel/kernel-impl/src/main/webapp/WEB-INF/authz-components.xml
  • kernel/kernel-impl/src/main/webapp/WEB-INF/components.xml
  • webcomponents/tool/src/main/frontend/packages/sakai-notifications/src/SakaiNotifications.js
**/*.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 (18)
📓 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-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/RuntimeRegistrationTest.java
  • kernel/kernel-impl/src/test/java/org/sakaiproject/event/impl/test/LearningResourceTestConfiguration.java
  • plus/impl/src/test/org/sakaiproject/plus/impl/PlusTestConfiguration.java
  • kernel/kernel-impl/src/test/java/org/sakaiproject/messaging/impl/UserMessagingServiceTestConfiguration.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-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/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 **/*.{java,properties} : Reconcile push notification subscriptions proactively and keep localization strings in `sakai-notifications.properties`

Applied to files:

  • webapi/src/main/java/org/sakaiproject/webapi/controllers/SitesController.java
  • config/configuration/bundles/src/bundle/org/sakaiproject/config/bundle/default.sakai.properties
  • kernel/kernel-impl/src/test/java/org/sakaiproject/messaging/impl/UserMessagingServiceTests.java
  • announcement/announcement-api/api/src/java/org/sakaiproject/announcement/api/AnnouncementsUserNotificationHandler.java
  • webcomponents/tool/src/main/frontend/packages/sakai-notifications/src/SakaiNotifications.js
  • 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:

  • webapi/src/main/java/org/sakaiproject/webapi/controllers/SitesController.java
  • config/configuration/bundles/src/bundle/org/sakaiproject/config/bundle/default.sakai.properties
  • kernel/kernel-impl/src/test/java/org/sakaiproject/messaging/impl/UserMessagingServiceTests.java
  • announcement/announcement-api/api/src/java/org/sakaiproject/announcement/api/AnnouncementsUserNotificationHandler.java
  • webcomponents/tool/src/main/frontend/packages/sakai-notifications/src/SakaiNotifications.js
  • 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:

  • webapi/src/main/java/org/sakaiproject/webapi/controllers/SitesController.java
  • config/configuration/bundles/src/bundle/org/sakaiproject/config/bundle/default.sakai.properties
  • 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:

  • webapi/src/main/java/org/sakaiproject/webapi/controllers/SitesController.java
  • kernel/kernel-impl/src/test/java/org/sakaiproject/messaging/impl/UserMessagingServiceTests.java
  • announcement/announcement-api/api/src/java/org/sakaiproject/announcement/api/AnnouncementsUserNotificationHandler.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
  • 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:

  • kernel/kernel-impl/src/test/java/org/sakaiproject/messaging/impl/UserMessagingServiceTests.java
  • 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-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
  • 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-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-11-24T19:11:42.092Z
Learnt from: CR
Repo: sakaiproject/sakai PR: 0
File: .cursor/rules/sakai-rules.md:0-0
Timestamp: 2025-11-24T19:11:42.092Z
Learning: Applies to **/*.java : Treat any PR or suggestion containing Java `var` as non-compliant. Recommend replacing with explicit types before merge

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 **/*.java : 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`

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 **/*.java : Do not use local variable type inference (`var`) in Java code; always declare explicit types (e.g., `List<String> names = new ArrayList<>();`)

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 **/*.java : Do not use local variable type inference (var) in Java; always declare explicit types

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 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-12-10T10:41:09.339Z
Learnt from: st-manu
Repo: sakaiproject/sakai PR: 14256
File: kernel/kernel-impl/src/main/sql/mysql/sakai_realm.sql:734-734
Timestamp: 2025-12-10T10:41:09.339Z
Learning: In Sakai, DB upgrade/backfill for new realm functions and grants belongs in the sakai-reference repo under docs/conversion (e.g., sakai_25_1-25_2_*_conversion.sql). For SAK-51713, assessment.all.groups is granted to maintain and Instructor and backfilled across existing realms via conversion scripts.

Applied to files:

  • kernel/api/src/main/java/org/sakaiproject/messaging/api/model/UserNotification.java
🧬 Code graph analysis (3)
lessonbuilder/components/src/java/org/sakaiproject/lessonbuildertool/impl/AddLessonsCommentUserNotificationHandlerImpl.java (1)
lessonbuilder/api/src/java/org/sakaiproject/lessonbuildertool/api/LessonBuilderConstants.java (1)
  • LessonBuilderConstants (18-21)
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)
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)
⏰ 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 (29)
kernel/kernel-impl/src/main/java/org/sakaiproject/event/impl/EventTrackingTest.java (1)

24-33: Import addition looks fine; ensure it stays consistent with Checkstyle/import rules.
This should be OK since it’s now referenced by the new schedulingService() hook.

webcomponents/tool/src/main/frontend/packages/sakai-notifications/src/SakaiNotifications.js (1)

404-411: LGTM! Close button correctly hidden for MOTD notifications.

The expanded condition properly excludes both MOTD and test notifications from showing the close button, which aligns with the PR objectives to implement broadcast-type notifications. This is consistent with the similar logic on line 417 for the navigation arrow.

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

85-85: No changes needed. The schedulingService dependency is explicitly configured in Spring XML (configstore-components.xml via <property name="schedulingService" ref="org.sakaiproject.scheduling.api.SchedulingService"/>), and the init-method="init" ensures initialization occurs only after all properties are injected by Spring. The @Setter annotation generates the setter method that Spring uses for property injection, making the lazy-init="false" and explicit property configuration sufficient to guarantee the dependency is available at line 129.


129-139: Looks good. The migration to SchedulingService is correctly implemented, and the lifecycle is properly managed by SchedulingServiceImpl.destroy(), which calls executor.shutdownNow() during application shutdown.

lessonbuilder/components/src/java/org/sakaiproject/lessonbuildertool/impl/AddLessonsCommentUserNotificationHandlerImpl.java (1)

75-75: LGTM! Constructor calls updated with appropriate defaults for comment notifications.

The two additional parameters (false, null) passed to the UserNotificationData constructor are appropriate for lesson comment notifications:

  • false indicates these are per-user (not broadcast) notifications
  • null indicates no custom TTL (uses default expiration)

This aligns with the expected behavior for comment notifications where each participant receives an individual notification.

Also applies to: 90-90

msgcntr/messageforums-api/src/java/org/sakaiproject/api/app/messageforums/PrivateMessageUserNotificationHandler.java (1)

97-97: LGTM! Constructor call updated with appropriate defaults for read receipt notifications.

The two additional parameters (false, null) are appropriate for private message read receipt notifications:

  • false indicates this is a per-user notification (sender notified when recipient reads message)
  • null indicates default TTL

This is consistent with the expected behavior for read receipts where individual users are notified about message reads.

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

27-27: Unable to complete verification - repository access unavailable. The review comment cannot be fully validated without access to the UserNotificationRepository.java file and its surrounding codebase context. Manual verification is required to confirm: (1) whether the method declaration at line 27 matches the shown snippet, and (2) whether Spring Data will properly generate the implementation at runtime based on the naming convention.


29-29: No changes needed; implementation is correct.

The deleteExpiredNotifications() method implementation in UserNotificationRepositoryImpl properly handles TTL-based expiration using Hibernate's Criteria API. The expiration logic compares endDate < now to identify expired notifications, and executeUpdate() correctly returns the count of deleted rows. The @transactional annotation ensures proper transaction management.

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

70-74: LGTM! Bean lifecycle configuration correctly reflects migration to SchedulingService.

The removal of destroy-method="destroy" aligns with the migration from a local refresh scheduler to the centralized SchedulingService. Cleanup is now handled by the scheduling infrastructure, eliminating the need for a component-specific destroy method.

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

65-65: All implementations and call sites have been properly updated.

The return type change from List<UserNotification> to List<UserNotificationTransferBean> is fully implemented:

  • The single implementation (UserMessagingServiceImpl) correctly returns the new type, converting UserNotification objects via UserNotificationTransferBean::of
  • All call sites in NotificationsController and SitesController are updated to handle List<UserNotificationTransferBean>
  • Tests properly work with the new return type

The change is complete and consistent across the codebase.

kernel/kernel-impl/src/test/java/org/sakaiproject/messaging/impl/UserMessagingServiceTestConfiguration.java (1)

87-90: Good test wiring for SchedulingService.
This should prevent Spring context failures after migrating consumers to SchedulingService.

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

200-203: Verify SchedulingService mock bean scope before merging.

Check whether LearningResourceTestConfiguration is used by multiple test classes. If only a single test class uses this mock, move it into that test class instead of the centralized configuration (per learnings: test-specific mocks should remain in the test class itself). If multiple tests reuse it, keeping it here is appropriate.

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

40-40: The import statement is correctly configured. The scheduling-components.xml file is packaged in the correct location (kernel/kernel-impl/src/main/webapp/WEB-INF/) and the bean org.sakaiproject.scheduling.api.SchedulingService is properly defined within it. This import is actively used by multiple components across the codebase (samigo, plus, microsoft-integration, messaging, event tracking, authz, content), confirming the resource resolves successfully at runtime.

config/configuration/bundles/src/bundle/org/sakaiproject/config/bundle/default.sakai.properties (2)

5774-5785: Defaults for messaging and scheduling properties are correctly aligned with implementation.
Verified that messaging.threadpool.size (DEFAULT: 10), schedulingservice.poolsize (DEFAULT: 8), and messaging.delete.expired.periodminutes (DEFAULT: 30) all match their fallback values in the code.


2258-2261: Config default verified and correct.

The property announcement.motd.notification.ttlhours is correctly documented with DEFAULT: 24 and the code in AnnouncementsUserNotificationHandler.java (line 93) reads it with the matching default of 24 hours via serverConfigurationService.getInt("announcement.motd.notification.ttlhours", 24). The configuration is consistent.

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

22-22: The lookup-method wiring is correctly configured.

DbAuthzGroupService declares protected abstract SchedulingService schedulingService() (line 129), which matches the lookup-method definition. The Spring bean injection will properly satisfy this abstract method at context initialization.

kernel/kernel-impl/src/test/java/org/sakaiproject/content/impl/test/FileConversionServiceTestConfiguration.java (1)

29-73: Mock SchedulingService bean looks fine; verify it belongs in shared test config (vs per-test).

Adding the SchedulingService mock bean unblocks Spring wiring for components now depending on scheduling. Just double-check this config is shared by multiple tests that need it; otherwise consider keeping the mock in the specific test(s) to avoid “global” mocks drifting from test intent. Based on learnings, test-specific mock configurations should remain in the test class unless shared across multiple tests.

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

31-105: Mock SchedulingService bean is OK; confirm it’s truly shared test infrastructure.

This keeps Plus tests wiring clean as more code moves to SchedulingService. Please confirm this configuration class is reused by multiple Plus tests that need scheduling; otherwise, prefer defining the mock in the specific test class to keep mocking aligned with test intent. Based on learnings, only common mocks should be centralized in test configuration classes.

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

129-130: All concrete subclasses already implement schedulingService().

DbAuthzGroupService now has protected abstract SchedulingService schedulingService(); at line 129. The only concrete subclass, AuthzGroupServiceTest, already implements this method at line 56.

samigo/samigo-app/src/java/org/sakaiproject/tool/assessment/ui/queue/delivery/TimedAssessmentQueue.java (1)

47-48: Verify Samigo deployment includes the scheduler component.

The concern about SchedulingService availability is valid: ComponentManager.get(SchedulingService.class) in a static initializer could return null if the scheduler component is not deployed. While SchedulingService is a core Sakai service (part of scheduler-api/scheduler-component) and ComponentManager.get(...) is a standard pattern throughout Sakai, you should confirm that samigo-app's deployment configuration includes the scheduler component and that the static initializer runs only after component initialization completes. If the service is optional, add a null check or lazy initialization instead of eagerly initializing in the static block.

kernel/kernel-impl/src/test/java/org/sakaiproject/messaging/impl/UserMessagingServiceTests.java (1)

172-172: LGTM! Test updated to match new API.

The constructor call correctly passes the two new parameters (broadcast: false, ttl: null) that were added to UserNotificationData.

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

44-44: LGTM! Migration to SchedulingService.

The code correctly migrates from a local ScheduledExecutorService to the centralized SchedulingService for periodic task scheduling, consistent with the PR objectives. The workers ExecutorService is appropriately retained for parallel conversion task execution.

Also applies to: 58-58, 114-114

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

21-21: LGTM! Migrated to UserNotificationTransferBean.

The code correctly updates from UserNotification to UserNotificationTransferBean and uses direct field access (n.siteId, sn.viewed, sn.tool) which aligns with the bean's current public field design.

Note: If the transfer bean is refactored to use private fields with getters (as suggested in my review of UserNotificationTransferBean.java), these direct field accesses will need to be updated to use getter methods.

Also applies to: 65-65, 77-77, 99-99

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

70-71: The URL field in UserNotification has not had a breaking schema change. The field was already optional (without explicit nullable = false) and remains optional. No constraint removal occurred.

Looking at the git history, the URL field at line 70 shows @Column(name="URL", length = 2048) in both the current code and prior versions—it was never explicitly marked as nullable = false. This is intentional design: optional fields like URL, TITLE, SITE_ID, VIEWED, and END_DATE contrast with required fields like ID, FROM_USER, TO_USER, EVENT, REF, EVENT_DATE, and DEFERRED, which explicitly specify nullable = false.

The commit SAK-51956 ("Add broadcast type notification") logically supports optional URLs, as broadcast notifications may not require a specific URL destination.

No database migration is needed because the schema constraint was not changed.

Likely an incorrect or invalid review comment.


77-79: Database migration scripts should be prepared in the sakai-reference repository, not in the sakai repository.

The USER_NOTIFICATIONS table and its new columns (END_DATE and BROADCAST) are JPA entity mappings, and their corresponding SQL migration scripts belong in the separate sakai-reference repository under docs/conversion/. This PR handles the code changes in the sakai repository; the database migrations are managed in sakai-reference following Sakai's standard upgrade script location pattern.

Likely an incorrect or invalid review comment.

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

124-134: MOTD broadcast notification implementation looks good.

The MOTD handling correctly creates a single broadcast notification with the configured TTL, which aligns with the PR objective to replace per-user MOTD entries with a single broadcast entry.


207-217: Role-based filtering implementation is correct.

The logic properly filters recipients based on the selectedRoles property, checking site membership and role matching before creating notifications. The fallback to all users when no roles are specified is appropriate.


91-94: The @PostConstruct specification explicitly permits private visibility. According to the java.annotation.PostConstruct documentation, the annotated method "MAY be public, protected, package private or private." Private visibility is fully compliant with the specification and is used elsewhere in the codebase (e.g., StatisticsService). No action is needed.

Likely an incorrect or invalid review comment.

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

159-164: Periodic cleanup scheduling is correctly implemented.

The scheduled cleanup of expired notifications uses the new SchedulingService abstraction with configurable intervals. The initial delay of 0 ensures immediate cleanup on startup, and the default 30-minute period is reasonable for production workloads.

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

🤖 Fix all issues with AI agents
In
@kernel/api/src/main/java/org/sakaiproject/messaging/api/UserNotificationTransferBean.java:
- Around line 22-26: The imports for Lombok (@AllArgsConstructor,
@NoArgsConstructor, @Data) are present but not applied to the
UserNotificationTransferBean class; either remove the unused imports or annotate
the class to generate boilerplate. To fix, either add the annotations directly
above the class declaration (e.g., annotate class UserNotificationTransferBean
with @Data @NoArgsConstructor @AllArgsConstructor and ensure Lombok is on the
classpath) or delete the three unused import lines for AllArgsConstructor,
NoArgsConstructor, and Data so there are no unused imports remaining.

In
@kernel/kernel-impl/src/main/java/org/sakaiproject/messaging/impl/UserMessagingServiceImpl.java:
- Around line 579-610: pushToAllUsers currently pages through all users and
synchronously calls push(decorateNotification(un)) for each, which can block the
executor and waste work for users without push subscriptions; instead fetch
active push subscriptions (e.g., via pushSubscriptionRepository.findAll() or a
repository method that returns userIds with subscriptions), iterate those
subscriptions inside the executor, and for each create a new
UserNotificationTransferBean copying bean.from/event/ref/title and setting to =
subscription.getUserId(), then call push(decorateNotification(un)); this limits
work to actual subscribers, avoids paging all users, and keeps each notification
bean instance local to the loop.
- Around line 493-496: The concatenation of
userNotificationRepository.findByToUser(userId) and findByBroadcast(true) can
yield duplicates; update the stream in UserMessagingServiceImpl where you build
List<UserNotificationTransferBean> (using UserNotificationTransferBean.of and
decorateNotification) to deduplicate before collecting — e.g., insert a
.distinct() after mapping or deduplicate by notification identifier (map to id,
filter unique, then map back) so each notification only appears once in the
final list.

In
@kernel/kernel-impl/src/main/java/org/sakaiproject/scheduling/impl/SchedulingServiceImpl.java:
- Around line 40-43: The destroy() method currently calls executor.shutdownNow()
unconditionally and can NPE if init() failed; change it to null-check the
executor first, call executor.shutdown() to request a graceful shutdown, wait
for a bounded timeout (e.g. via awaitTermination) and if it still hasn't
terminated call executor.shutdownNow(), and add process/instance logging at each
step (shutdown requested, timeout expired, forced shutdown, or terminated
successfully) to aid observability; reference the
SchedulingServiceImpl.destroy() method and the executor field when making these
changes.
- Around line 33-38: Validate and log the pool size inside init(): retrieve
poolSize via serverConfigurationService.getInt(...) and if poolSize < 1 set it
to 1 (or throw a clear IllegalArgumentException) and log the final value using a
class logger; create the ScheduledThreadPoolExecutor with that poolSize but
supply a ThreadFactory that names threads like "sakai-scheduling-%d" (use
ThreadFactoryBuilder or a custom ThreadFactory) so the executor field uses the
named threads, and add a private static final Logger logger to the class to
record the configured pool size and any validation decision.

In
@samigo/samigo-app/src/java/org/sakaiproject/tool/assessment/ui/queue/delivery/TimedAssessmentQueue.java:
- Line 47: The static INSTANCE initializes TimedAssessmentQueue with
ComponentManager.get(SchedulingService.class) which can return null causing
schedulingService.scheduleAtFixedRate(...) in add() to NPE; update the
TimedAssessmentQueue constructor (and/or static initialization) to null-check
the result of ComponentManager.get(...) and either defer scheduling until
schedulingService is non-null or throw/log a clear error and skip scheduling
when schedulingService is null, and add a guard before calling
schedulingService.scheduleAtFixedRate(...) in add() to avoid NPE; also replace
the string-concatenation log calls around NEWTIMER SUCCESS/FAILURE with SLF4J
parameterized logging, e.g.,
log.info("SAMIGO_TIMED_ASSESSMENT:QUEUE:NEWTIMER:SUCCESS ID: {}",
timedAG.getAssessmentGradingId()) and similarly for the failure message.
🧹 Nitpick comments (9)
kernel/kernel-impl/src/main/webapp/WEB-INF/components.xml (1)

40-40: LGTM! Consider placement for better organization.

The import statement is syntactically correct and follows the established pattern. Since SchedulingService is a foundational service that other components may depend on (e.g., tasks, messaging), you might consider placing this import earlier in the list—perhaps after time-components.xml (line 34) or util-components.xml (line 33)—for better logical organization. However, Spring's dependency injection typically handles initialization order automatically, so the current placement should work correctly.

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

45-67: Consider defensive null checks on executor.

While Spring's lifecycle guarantees init() is called before these methods are invoked, adding null checks would make the code more defensive against unexpected initialization failures.

♻️ Optional defensive null checks
 @Override
 public ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit) {
-
+    if (executor == null) {
+        throw new IllegalStateException("SchedulingService not initialized");
+    }
     return executor.schedule(command, delay, unit);
 }

Apply similar checks to scheduleAtFixedRate() and scheduleWithFixedDelay().

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

56-58: Pass exceptions directly to SLF4J logger.

Using e.toString() loses the stack trace. SLF4J can handle exceptions directly as the last parameter.

♻️ Proposed fix
         } catch (Exception e) {
-            log.error("Failed to clear notification: {}", e.toString());
+            log.error("Failed to clear notification", e);
         }

71-73: Pass exceptions directly to SLF4J logger.

Using e.toString() loses the stack trace. SLF4J can handle exceptions directly as the last parameter.

♻️ Proposed fix
         } catch (Exception e) {
-            log.error("Failed to clear all notifications: {}", e.toString());
+            log.error("Failed to clear all notifications", e);
         }

86-88: Pass exceptions directly to SLF4J logger.

Using e.toString() loses the stack trace. SLF4J can handle exceptions directly as the last parameter.

♻️ Proposed fix
         } catch (Exception e) {
-            log.error("Failed to mark all notifications as viewed: {}", e.toString());
+            log.error("Failed to mark all notifications as viewed", e);
         }
assignment/api/src/java/org/sakaiproject/assignment/api/AddAssignmentUserNotificationHandler.java (2)

130-130: Consider whether new assignment notifications should have a TTL.

While false for broadcast is correct for individual user notifications, new assignment notifications are often time-sensitive (related to due dates). Consider whether a TTL should be set to automatically expire notifications after the assignment's due date has passed, rather than keeping them indefinitely with null.


167-167: Same TTL consideration for assignment access update notifications.

Assignment access change notifications share the same time-sensitive nature as new assignment notifications. Consider whether these should also have a TTL based on the assignment's due date.

Also applies to: 181-181

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

44-59: Consider adding null-safety checks in the factory method.

The of() method calls getters on UserNotification without null checks. If any getter returns null for String fields or if un itself is null, this could lead to unexpected behavior or NPEs during serialization.

🛡️ Add defensive null checks
 public static UserNotificationTransferBean of(UserNotification un) {
+    if (un == null) {
+        throw new IllegalArgumentException("UserNotification cannot be null");
+    }
 
     UserNotificationTransferBean bean = new UserNotificationTransferBean();
     bean.from = un.getFromUser();
kernel/api/src/main/java/org/sakaiproject/messaging/api/model/UserNotification.java (1)

90-91: Consider adding a database index for broadcast queries.

The broadcast field is used in repository queries (e.g., findByBroadcast(true)), but there's no index defined on this column. Given that broadcast queries will scan the table to find matching rows, adding an index would improve query performance.

📊 Add index for broadcast column
 @Table(name = "USER_NOTIFICATIONS", indexes = {
     @Index(name = "IDX_USER_NOTIFICATIONS_TO_USER", columnList = "TO_USER"),
-    @Index(name = "IDX_USER_NOTIFICATIONS_EVENT_REF", columnList = "EVENT, REF")
+    @Index(name = "IDX_USER_NOTIFICATIONS_EVENT_REF", columnList = "EVENT, REF"),
+    @Index(name = "IDX_USER_NOTIFICATIONS_BROADCAST", columnList = "BROADCAST")
 })

Note: A corresponding database migration script should be created to add this index to existing deployments.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e1b9e6e and 977b830.

📒 Files selected for processing (41)
  • 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/RuntimeRegistrationTest.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-app/src/java/org/sakaiproject/tool/assessment/ui/queue/delivery/TimedAssessmentQueue.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)
  • webcomponents/tool/src/main/frontend/packages/sakai-notifications/src/SakaiNotifications.js
  • microsoft-integration/impl/src/main/java/org/sakaiproject/microsoft/impl/MicrosoftCommonServiceImpl.java
  • kernel/kernel-impl/src/main/webapp/WEB-INF/event-components.xml
  • plus/impl/src/test/org/sakaiproject/plus/impl/PlusTestConfiguration.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/messaging/api/UserMessagingService.java
  • plus/impl/src/main/java/org/sakaiproject/plus/impl/PlusServiceImpl.java
  • kernel/kernel-impl/src/test/java/org/sakaiproject/messaging/impl/RuntimeRegistrationTest.java
  • kernel/kernel-impl/src/main/webapp/WEB-INF/configstore-components.xml
  • kernel/kernel-impl/src/main/webapp/WEB-INF/authz-components.xml
  • kernel/api/src/main/java/org/sakaiproject/messaging/api/repository/UserNotificationRepository.java
  • kernel/kernel-impl/src/main/java/org/sakaiproject/authz/impl/DbAuthzGroupService.java
  • kernel/kernel-impl/src/main/webapp/WEB-INF/scheduling-components.xml
  • config/configuration/bundles/src/bundle/org/sakaiproject/config/bundle/default.sakai.properties
🧰 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:

  • kernel/kernel-impl/src/test/java/org/sakaiproject/content/impl/test/FileConversionServiceTestConfiguration.java
  • webapi/src/main/java/org/sakaiproject/webapi/controllers/NotificationsController.java
  • kernel/api/src/main/java/org/sakaiproject/messaging/api/UserNotificationTransferBean.java
  • samigo/samigo-services/src/java/org/sakaiproject/tool/assessment/TestsAndQuizzesUserNotificationHandler.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/main/java/org/sakaiproject/content/impl/FileConversionServiceImpl.java
  • kernel/kernel-impl/src/main/java/org/sakaiproject/scheduling/impl/SchedulingServiceImpl.java
  • commons/api/src/java/org/sakaiproject/commons/api/CommonsCommentUserNotificationHandler.java
  • kernel/api/src/main/java/org/sakaiproject/messaging/api/UserNotificationData.java
  • announcement/announcement-api/api/src/java/org/sakaiproject/announcement/api/AnnouncementsUserNotificationHandler.java
  • assignment/api/src/java/org/sakaiproject/assignment/api/GradeAssignmentUserNotificationHandler.java
  • msgcntr/messageforums-api/src/java/org/sakaiproject/api/app/messageforums/PrivateMessageUserNotificationHandler.java
  • assignment/api/src/java/org/sakaiproject/assignment/api/AddAssignmentUserNotificationHandler.java
  • kernel/kernel-impl/src/main/java/org/sakaiproject/messaging/impl/UserMessagingServiceImpl.java
  • kernel/api/src/main/java/org/sakaiproject/scheduling/api/SchedulingService.java
  • lessonbuilder/components/src/java/org/sakaiproject/lessonbuildertool/impl/AddLessonsCommentUserNotificationHandlerImpl.java
  • kernel/kernel-impl/src/main/java/org/sakaiproject/event/impl/ClusterEventTracking.java
  • webapi/src/main/java/org/sakaiproject/webapi/controllers/SitesController.java
  • kernel/api/src/main/java/org/sakaiproject/messaging/api/model/UserNotification.java
  • kernel/kernel-impl/src/main/java/org/sakaiproject/config/impl/StoredConfigService.java
  • samigo/samigo-app/src/java/org/sakaiproject/tool/assessment/ui/queue/delivery/TimedAssessmentQueue.java
  • kernel/kernel-impl/src/test/java/org/sakaiproject/messaging/impl/UserMessagingServiceTests.java
  • kernel/kernel-impl/src/main/java/org/sakaiproject/event/impl/EventTrackingTest.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:

  • plus/impl/src/main/webapp/WEB-INF/components.xml
  • kernel/kernel-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:

  • plus/impl/src/main/webapp/WEB-INF/components.xml
  • kernel/kernel-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:

  • plus/impl/src/main/webapp/WEB-INF/components.xml
  • kernel/kernel-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:

  • plus/impl/src/main/webapp/WEB-INF/components.xml
  • kernel/kernel-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 (17)
📓 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/kernel-impl/src/test/java/org/sakaiproject/messaging/impl/UserMessagingServiceTests.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
📚 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/event/impl/test/LearningResourceTestConfiguration.java
  • kernel/kernel-impl/src/test/java/org/sakaiproject/messaging/impl/UserMessagingServiceTestConfiguration.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:

  • announcement/announcement-api/api/src/java/org/sakaiproject/announcement/api/AnnouncementsUserNotificationHandler.java
  • kernel/kernel-impl/src/main/java/org/sakaiproject/messaging/impl/UserMessagingServiceImpl.java
  • webapi/src/main/java/org/sakaiproject/webapi/controllers/SitesController.java
  • kernel/api/src/main/java/org/sakaiproject/messaging/api/model/UserNotification.java
  • kernel/kernel-impl/src/test/java/org/sakaiproject/messaging/impl/UserMessagingServiceTests.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
  • webapi/src/main/java/org/sakaiproject/webapi/controllers/SitesController.java
  • kernel/api/src/main/java/org/sakaiproject/messaging/api/model/UserNotification.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
  • webapi/src/main/java/org/sakaiproject/webapi/controllers/SitesController.java
  • kernel/kernel-impl/src/test/java/org/sakaiproject/messaging/impl/UserMessagingServiceTests.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
  • kernel/kernel-impl/src/test/java/org/sakaiproject/messaging/impl/UserMessagingServiceTests.java
📚 Learning: 2025-11-24T19:11:42.092Z
Learnt from: CR
Repo: sakaiproject/sakai PR: 0
File: .cursor/rules/sakai-rules.md:0-0
Timestamp: 2025-11-24T19:11:42.092Z
Learning: Applies to **/*.java : Treat any PR or suggestion containing Java `var` as non-compliant. Recommend replacing with explicit types before merge

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 **/*.java : 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`

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 **/*.java : Do not use local variable type inference (`var`) in Java code; always declare explicit types (e.g., `List<String> names = new ArrayList<>();`)

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 **/*.java : Do not use local variable type inference (var) in Java; always declare explicit types

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
  • webapi/src/main/java/org/sakaiproject/webapi/controllers/SitesController.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
  • webapi/src/main/java/org/sakaiproject/webapi/controllers/SitesController.java
📚 Learning: 2025-12-10T10:41:09.339Z
Learnt from: st-manu
Repo: sakaiproject/sakai PR: 14256
File: kernel/kernel-impl/src/main/sql/mysql/sakai_realm.sql:734-734
Timestamp: 2025-12-10T10:41:09.339Z
Learning: In Sakai, DB upgrade/backfill for new realm functions and grants belongs in the sakai-reference repo under docs/conversion (e.g., sakai_25_1-25_2_*_conversion.sql). For SAK-51713, assessment.all.groups is granted to maintain and Instructor and backfilled across existing realms via conversion scripts.

Applied to files:

  • kernel/api/src/main/java/org/sakaiproject/messaging/api/model/UserNotification.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
🧬 Code graph analysis (7)
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/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)
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)
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)
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)
lessonbuilder/components/src/java/org/sakaiproject/lessonbuildertool/impl/AddLessonsCommentUserNotificationHandlerImpl.java (1)
lessonbuilder/api/src/java/org/sakaiproject/lessonbuildertool/api/LessonBuilderConstants.java (1)
  • LessonBuilderConstants (18-21)
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)
⏰ 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 (37)
kernel/kernel-impl/src/main/java/org/sakaiproject/config/impl/StoredConfigService.java (3)

36-36: LGTM: Import changes support the migration to SchedulingService.

The addition of Lombok's @Setter and SchedulingService imports aligns with the PR's objective to centralize scheduling management.

Also applies to: 52-52


129-139: Scheduling migration to SchedulingService is correctly implemented.

The SchedulingService is properly wired in the Spring configuration (configstore-components.xml), and the scheduleWithFixedDelay call preserves the original behavior including the minimum 120-second initial delay. The implementation follows coding guidelines: all types are explicit, and logging uses parameterized format.


85-88: No changes needed. The @Setter annotation on package-private fields works correctly with Spring's XML property injection. Spring uses reflection to invoke setters, which bypasses visibility restrictions and can access package-private methods. This pattern is already used successfully throughout the codebase.

Likely an incorrect or invalid review comment.

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

200-203: LGTM! Mock bean follows established pattern.

The mock SchedulingService bean is appropriately added to the test configuration, consistent with other mock service beans in this class.

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

94-101: LGTM! Follows the established test pattern.

The schedulingService() method appropriately returns null, consistent with other dependency injector methods in this test class.

kernel/kernel-impl/src/test/java/org/sakaiproject/content/impl/test/FileConversionServiceTestConfiguration.java (1)

70-73: LGTM! Mock bean follows established pattern.

The mock SchedulingService bean is appropriately added to the test configuration, consistent with other mock service beans in this class.

kernel/kernel-impl/src/test/java/org/sakaiproject/messaging/impl/UserMessagingServiceTestConfiguration.java (2)

87-90: LGTM! Mock bean follows established pattern.

The mock SchedulingService bean is appropriately added to the test configuration, consistent with other mock service beans in this class.


95-95: Verify the thread pool size change from 20 to 10.

The default thread pool size for messaging.threadpool.size has been reduced from 20 to 10 in the test configuration. Please confirm this change is intentional and document the reason if it relates to the SchedulingService migration.

lessonbuilder/components/src/java/org/sakaiproject/lessonbuildertool/impl/AddLessonsCommentUserNotificationHandlerImpl.java (1)

75-75: LGTM: Correct usage of expanded UserNotificationData constructor.

The addition of false (non-broadcast) and null (no TTL) parameters is appropriate for lesson comment notifications, which are targeted user-to-user notifications rather than system-wide broadcasts.

Also applies to: 90-90

msgcntr/messageforums-api/src/java/org/sakaiproject/api/app/messageforums/PrivateMessageUserNotificationHandler.java (1)

97-97: LGTM: Correct usage of expanded UserNotificationData constructor.

The addition of false (non-broadcast) and null (no TTL) parameters is appropriate for private message read receipt notifications, which are user-to-user notifications.

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

44-44: LGTM: Migration to centralized SchedulingService.

The migration from a local ScheduledExecutorService to the centralized SchedulingService aligns with the PR objectives to consolidate scheduling across Sakai components. The lifecycle management is correctly implemented: SchedulingServiceImpl has its own destroy() method that Spring will automatically invoke, while FileConversionServiceImpl.destroy() properly manages only its local worker pool. This separation of concerns—where each component manages its own resources and the centralized service is framework-managed—is the correct pattern.

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

27-27: No breaking change verification needed—API already consistently uses the transfer bean type.

The endpoint /users/me/notifications already returns List<UserNotificationTransferBean>, and the UserMessagingService interface contract itself specifies this return type. Both internal consumers (SitesController, NotificationsController) and external consumers (JavaScript frontend) are already using the transfer bean type consistently. This follows the established DTO pattern and requires no action.

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

70-72: LGTM: Lifecycle management aligned with SchedulingService migration.

Removing the destroy-method attribute is correct. PlusServiceImpl no longer has a destroy() method, and the class now delegates scheduled task lifecycle management to the centralized SchedulingService via scheduleWithFixedDelay(), eliminating the need for manual cleanup on bean destruction.

kernel/kernel-impl/src/main/java/org/sakaiproject/event/impl/ClusterEventTracking.java (2)

105-106: LGTM - SchedulingService dependency added.

The abstract method correctly declares the SchedulingService dependency, following the same pattern as other collaborators in this class.


226-231: Lifecycle management is properly handled. SchedulingService has a destroy() method that calls executor.shutdownNow(), and the Spring bean configuration (scheduling-components.xml) explicitly defines destroy-method="destroy". ClusterEventTracking inherits a destroy() method from BaseEventTrackingService and delegates to SchedulingService via abstract method injection. When the application shuts down, Spring invokes the destroy methods on both services, ensuring all scheduled tasks are properly cancelled.

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

110-110: Same verification needed as Line 94.

Ensure the broadcast flag (false) and TTL (null) are appropriate for notifications sent to other commenters.


94-94: Parameters are appropriate and consistent with codebase patterns.

The broadcast flag (false) correctly indicates these are individual user notifications, not broadcasts. The TTL parameter (null) matches the established pattern used across all notification handlers in the codebase (samigo, msgcntr, lessonbuilder, announcement, assignment). No changes needed.

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

97-97: Verify the broadcast flag and TTL parameters for grade notifications.

The updated constructor adds false, null for broadcast and TTL parameters. While false is appropriate for individual grade notifications, consider whether grade notifications should have a TTL. Grade-related notifications might be important enough to persist until explicitly cleared by the user.

kernel/kernel-impl/src/test/java/org/sakaiproject/messaging/impl/UserMessagingServiceTests.java (1)

172-172: LGTM - Test updated to match new constructor signature.

The test correctly uses the extended 8-parameter constructor with false, null for the broadcast flag and TTL, matching the pattern used in production handlers.

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

168-168: LGTM: Constructor call updated correctly for broadcast notifications.

The addition of false and null parameters correctly indicates that Test & Quizzes notifications are not broadcast notifications and have no TTL expiry.


210-210: LGTM: Consistent update to constructor call.

The parameters match the constructor call at line 168, maintaining consistency across the add and update handlers.

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

18-18: LGTM: Lombok annotations updated to support enhanced data modeling.

The switch from @Getter to @Data (which includes getters, setters, toString, equals, and hashCode) plus @NoArgsConstructor provides better support for serialization frameworks and testing scenarios while maintaining the all-args constructor.

Also applies to: 21-26


35-36: LGTM: New fields extend notification capabilities appropriately.

The broadcast and ttl fields support the PR objectives:

  • broadcast enables single-entry system-wide notifications
  • ttl (Duration) allows notifications to expire automatically
webapi/src/main/java/org/sakaiproject/webapi/controllers/SitesController.java (2)

21-21: LGTM: Migration to transfer bean is consistent.

The change from UserNotification to UserNotificationTransferBean aligns with the new messaging API design, providing a cleaner separation between internal models and REST API responses.

Also applies to: 65-65


77-77: LGTM: Direct field access is consistent with transfer bean design.

The migration from getter methods (n.getSiteId(), n.getViewed(), n.getTool()) to direct field access (n.siteId, n.viewed, n.tool) is appropriate for the DTO pattern used in UserNotificationTransferBean.

Also applies to: 99-99

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

25-47: Lifecycle management is properly implemented via Spring configuration.

The SchedulingServiceImpl class includes init() and destroy() methods that are explicitly wired in the Spring XML configuration (init-method="init" and destroy-method="destroy" in scheduling-components.xml). The destroy() method properly calls executor.shutdownNow() for resource cleanup. The interface correctly does not expose lifecycle methods, as these are implementation details managed by Spring, not part of the public API contract.

samigo/samigo-app/src/java/org/sakaiproject/tool/assessment/ui/queue/delivery/TimedAssessmentQueue.java (1)

71-76: LGTM!

The migration from direct thread pool usage to SchedulingService is correctly implemented, maintaining the same scheduling parameters (0ms initial delay, 3000ms fixed rate).

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

91-94: LGTM!

Configuration initialization follows the standard pattern for reading server configuration with a sensible default of 24 hours for MOTD notification TTL.


205-217: LGTM - Role-based filtering enhances notification targeting.

The implementation correctly filters notification recipients based on the announcement's selected roles by checking site membership and matching role IDs. This prevents notifications from being sent to users who shouldn't receive them based on role restrictions.


240-249: LGTM - Helper method improves maintainability.

Extracting the predicate logic into a reusable buildWhere method eliminates duplication between the query and delete operations, reducing the risk of inconsistencies.

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

77-79: LGTM - endDate supports TTL-based notification expiry.

The nullable endDate field correctly models TTL semantics: notifications without a TTL have no expiration (null), while those with TTL have a computed end date for cleanup.


70-71: The nullable URL change appears intentional for broadcast notifications.

The removal of nullable = false supports the new broadcast notification pattern. All standard notification handlers continue to provide URLs (as shown in assignment, announcement, and other handlers). However, broadcast notifications (identified by setTo("BROADCAST")) use an empty constructor with individual field setters, allowing URLs to remain null for site-wide notifications that don't target specific users. This design accommodates both user-specific notifications with URLs and broadcast notifications without them.

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

154-160: LGTM - Thread pool size validation improves robustness.

Adding validation for the thread pool size with a fallback to the default prevents misconfiguration from causing runtime issues.


165-170: LGTM - Scheduled cleanup task prevents unbounded growth.

The periodic deletion of expired notifications ensures that the USER_NOTIFICATIONS table doesn't grow unbounded, which is essential for system health with TTL-based notifications.


439-441: LGTM - TTL correctly converts to endDate.

The endDate is computed by adding the TTL duration to the event date, which correctly implements notification expiration semantics.


655-672: LGTM - Enhanced error handling for push failures.

The switch statement provides appropriate responses to different HTTP status codes, including cleaning up invalid subscriptions (410, 404, 400) and logging actionable warnings for configuration issues (403, 429).


379-400: The thread safety concern raised does not apply to this code. The UserNotificationTransferBean instances are not shared across concurrent operations: each forEach iteration creates a fresh bean instance via the of() factory method. The push() method serializes the bean immediately and synchronously without retaining any reference. The pushToAllUsers() method executes asynchronously but creates a new bean instance internally and does not use the tool field that was set on line 391. No race condition exists here.

Likely an incorrect or invalid review comment.

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

Caution

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

⚠️ Outside diff range comments (3)
kernel/kernel-impl/src/main/java/org/sakaiproject/content/impl/FileConversionServiceImpl.java (1)

114-211: Store and cancel the scheduled task in destroy().

The scheduled task (line 114) is never explicitly cancelled; it continues running via SchedulingService until Spring shuts down that service. Meanwhile, destroy() (line 210) immediately shuts down the workers ExecutorService. This creates a race condition during shutdown: the scheduled task may attempt workers.submit() (line 133) after workers have been shut down, causing RejectedExecutionException.

Additionally, repository.findByStatus() at line 117 is outside the try-finally block (which starts at line 123), so exceptions there are not properly handled.

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

Example:
private ScheduledFuture<?> scheduledTask;

public void startIfEnabled() {
    // ...
    scheduledTask = schedulingService.scheduleWithFixedDelay(() -> {
        // ...
    }, 0, queueIntervalMinutes, TimeUnit.MINUTES);
}

public void destroy() {
    if (scheduledTask != null) {
        scheduledTask.cancel(false);
    }
    workers.shutdownNow();
}
kernel/kernel-impl/src/main/java/org/sakaiproject/messaging/impl/UserMessagingServiceImpl.java (2)

648-688: Avoid logging full push endpoints (likely sensitive identifiers).

pushEndpoint and subscription.getEndpoint() are typically tokenized URLs; logging them can leak sensitive per-user identifiers into logs. Prefer logging userId + status code and (if needed) a truncated/hashed endpoint.

Proposed fix
@@
-                if (statusCode >= 200 && statusCode < 300) {
-                    log.debug("Successfully sent push notification to {} with status {}", 
-                            pushEndpoint, statusCode);
+                if (statusCode >= 200 && statusCode < 300) {
+                    log.debug("Successfully sent push notification to user {} with status {}", un.to, statusCode);
                 } else {
                     String reason = pushResponse.getStatusLine().getReasonPhrase();
-                    log.warn("Push notification to {} failed with status {} and reason {}", 
-                            pushEndpoint, statusCode, reason);
+                    log.warn("Push notification to user {} failed with status {} and reason {}", un.to, statusCode, reason);
@@
-                            log.info("Removed invalid push subscription {} for user {}", subscription.getEndpoint(), subscription.getUserId());
+                            log.info("Removed invalid push subscription for user {}", subscription.getUserId());

109-171: Add validation for messaging.delete.expired.periodminutes and exception handling in the cleanup task.

The scheduled cleanup task lacks both input validation and error resilience:

  1. deleteExpiredPeriod is not validated (should match the pattern used for threadPoolSize at lines 56–59).
  2. Uncaught exceptions in the scheduled task will prevent it from running again. Wrap the cleanup in a try/catch block.
Proposed fix
            long deleteExpiredPeriod = serverConfigurationService.getLong("messaging.delete.expired.periodminutes", 30);
+           if (deleteExpiredPeriod <= 0) {
+               log.warn("Invalid messaging.delete.expired.periodminutes configured. Defaulting to {}", 30);
+               deleteExpiredPeriod = 30;
+           }

            // Clean up expired notifications every 30 minutes
            schedulingService.scheduleWithFixedDelay(() -> {
+               try {
                    userNotificationRepository.deleteExpiredNotifications();
+               } catch (Exception e) {
+                   log.warn("Failed to delete expired notifications", e);
+               }
            }, 0, deleteExpiredPeriod, TimeUnit.MINUTES);
🤖 Fix all issues with AI agents
In
@announcement/announcement-api/api/src/java/org/sakaiproject/announcement/api/AnnouncementsUserNotificationHandler.java:
- Around line 124-134: The code in AnnouncementsUserNotificationHandler sets the
to field to the literal "BROADCAST" for MOTD events which is incorrect; remove
the userNotificationData.setTo("BROADCAST") call and leave the to field
null/unset so broadcast handling relies solely on
userNotificationData.setBroadcast(true) (the notification pipeline uses
findByBroadcast(true) to retrieve broadcasts). Ensure only setBroadcast(true)
and ttl/title/from are set for AnnouncementService.EVENT_MOTD_NEW.

In
@kernel/kernel-impl/src/main/java/org/sakaiproject/messaging/impl/UserMessagingServiceImpl.java:
- Around line 561-574: Move the test_notification_title key (and its localized
variants) out of usermessagingservice.properties and into
sakai-notifications.properties so all notification strings are consolidated;
update the resource bundles by removing the key from
kernel/kernel-impl/src/main/resources/usermessagingservice.properties and adding
the key and locale-specific entries to sakai-notifications.properties (and its
locale variants), ensuring the same key name "test_notification_title" is used
so sendTestNotification (which calls
resourceLoader.getString("test_notification_title")) continues to resolve
correctly.
- Around line 379-400: doInsert(...) currently calls ba.setToUser(und.getTo())
unconditionally which will violate the non-null DB constraint for broadcast
notifications where und.getTo() is null; modify doInsert (and any helper
building UserNotification) to check und.isBroadcast() or null getTo() and set a
non-null sentinel (e.g., an empty string or a configured system/broadcast user
id) via ba.setToUser(...) before persisting, so broadcasts never write a null to
UserNotification.toUser; ensure the same sentinel is used consistently where
notifications are created and persisted.
- Around line 576-611: In pushToAllUsers create a fresh
UserNotificationTransferBean per target user (do not reuse the single 'un'),
copying all relevant fields from the original bean — e.g., from, event, ref,
title, eventDate, fromDisplayName, formattedEventDate, siteTitle (and any other
decorated fields) — then set un.to = u.getId(), call decorateNotification(un)
and push that result; this ensures each user gets a complete, independent bean
and avoids race conditions when decoration/push is asynchronous.

In
@samigo/samigo-app/src/java/org/sakaiproject/tool/assessment/ui/queue/delivery/TimedAssessmentQueue.java:
- Around line 31-33: The queue's task map and cancellation/logging need
hardening: make the tasks field a concurrent map (e.g., ConcurrentHashMap) or
otherwise guard all accesses to it so add/remove can be called safely from
different threads; before calling tasks.get(timedAG).cancel(true) null-check the
returned ScheduledFuture (or use tasks.remove(timedAG) and check that result) to
avoid NPEs; replace string-concatenation logging with parameterized logging
(e.g., logger.info("Cancelled task for {}: {}", timedAG, result)) in all
locations that access tasks (instances around the TimedAssessmentQueue class
where you call tasks.get(...).cancel(...) and where you log task operations).
🧹 Nitpick comments (4)
webcomponents/tool/src/main/frontend/packages/sakai-notifications/src/SakaiNotifications.js (1)

148-148: Consider explicit conditional for clarity.

The short-circuit pattern !this._filteredNotifications.has(toolEventPrefix) && this._filteredNotifications.set(toolEventPrefix, []); is functionally correct but may be less immediately readable than an explicit if statement for developers unfamiliar with this pattern.

♻️ Alternative: explicit conditional
-      !this._filteredNotifications.has(toolEventPrefix) && this._filteredNotifications.set(toolEventPrefix, []);
+      if (!this._filteredNotifications.has(toolEventPrefix)) {
+        this._filteredNotifications.set(toolEventPrefix, []);
+      }
kernel/kernel-impl/src/test/java/org/sakaiproject/messaging/impl/UserMessagingServiceTests.java (1)

172-172: Avoid magic false, null in the test setup.

Consider extracting named locals (or a helper factory) so it’s clear which new fields you’re setting (broadcast/ttl/extra/etc.) and the test won’t silently drift on future constructor changes.

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

21-22: Potential N×M stream cost per request; consider pre-grouping notifications.

Right now each site does a full scan of notifications and then each tool scans siteNotifications again. If notifications can be large, consider precomputing Map<String, List<UserNotificationTransferBean>> bySiteId and (optionally) Set<(siteId, toolId)> for unviewed alerts.

Also applies to: 65-66, 77-77, 99-100

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

311-353: Unregister should only remove mappings owned by the handler.

unregisterHandler() removes the mapping by key only; if the map ever supports replacement/overrides later, this can remove another handler unintentionally. Consider removing only when newMap.get(eventName) == handler.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 977b830 and a23f5d8.

📒 Files selected for processing (41)
  • 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/RuntimeRegistrationTest.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-app/src/java/org/sakaiproject/tool/assessment/ui/queue/delivery/TimedAssessmentQueue.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 with no reviewable changes (1)
  • kernel/kernel-impl/src/test/java/org/sakaiproject/messaging/impl/RuntimeRegistrationTest.java
🚧 Files skipped from review as they are similar to previous changes (21)
  • kernel/kernel-impl/src/main/webapp/WEB-INF/event-components.xml
  • plus/impl/src/test/org/sakaiproject/plus/impl/PlusTestConfiguration.java
  • samigo/samigo-services/src/java/org/sakaiproject/tool/assessment/TestsAndQuizzesUserNotificationHandler.java
  • kernel/kernel-impl/src/test/java/org/sakaiproject/content/impl/test/FileConversionServiceTestConfiguration.java
  • kernel/kernel-impl/src/main/webapp/WEB-INF/components.xml
  • kernel/kernel-impl/src/main/webapp/WEB-INF/authz-components.xml
  • kernel/kernel-impl/src/main/webapp/WEB-INF/configstore-components.xml
  • assignment/api/src/java/org/sakaiproject/assignment/api/AddAssignmentUserNotificationHandler.java
  • kernel/kernel-impl/src/test/java/org/sakaiproject/event/impl/test/LearningResourceTestConfiguration.java
  • commons/api/src/java/org/sakaiproject/commons/api/CommonsCommentUserNotificationHandler.java
  • msgcntr/messageforums-api/src/java/org/sakaiproject/api/app/messageforums/PrivateMessageUserNotificationHandler.java
  • kernel/kernel-impl/src/main/java/org/sakaiproject/messaging/impl/repository/UserNotificationRepositoryImpl.java
  • kernel/kernel-impl/src/test/java/org/sakaiproject/messaging/impl/UserMessagingServiceTestConfiguration.java
  • kernel/kernel-impl/src/main/java/org/sakaiproject/authz/impl/DbAuthzGroupService.java
  • kernel/api/src/main/java/org/sakaiproject/scheduling/api/SchedulingService.java
  • kernel/api/src/main/java/org/sakaiproject/messaging/api/UserNotificationData.java
  • kernel/kernel-impl/src/main/java/org/sakaiproject/authz/impl/AuthzGroupServiceTest.java
  • kernel/kernel-impl/src/main/java/org/sakaiproject/event/impl/EventTrackingTest.java
  • microsoft-integration/impl/src/main/java/org/sakaiproject/microsoft/impl/MicrosoftCommonServiceImpl.java
  • kernel/kernel-impl/src/main/java/org/sakaiproject/scheduling/impl/SchedulingServiceImpl.java
  • kernel/api/src/main/java/org/sakaiproject/messaging/api/UserNotificationTransferBean.java
🧰 Additional context used
📓 Path-based instructions (10)
**/*.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:

  • assignment/api/src/java/org/sakaiproject/assignment/api/GradeAssignmentUserNotificationHandler.java
  • webapi/src/main/java/org/sakaiproject/webapi/controllers/NotificationsController.java
  • announcement/announcement-api/api/src/java/org/sakaiproject/announcement/api/AnnouncementsUserNotificationHandler.java
  • kernel/kernel-impl/src/main/java/org/sakaiproject/event/impl/ClusterEventTracking.java
  • plus/impl/src/main/java/org/sakaiproject/plus/impl/PlusServiceImpl.java
  • kernel/api/src/main/java/org/sakaiproject/messaging/api/UserMessagingService.java
  • webapi/src/main/java/org/sakaiproject/webapi/controllers/SitesController.java
  • kernel/api/src/main/java/org/sakaiproject/messaging/api/model/UserNotification.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
  • kernel/api/src/main/java/org/sakaiproject/messaging/api/repository/UserNotificationRepository.java
  • lessonbuilder/components/src/java/org/sakaiproject/lessonbuildertool/impl/AddLessonsCommentUserNotificationHandlerImpl.java
  • kernel/kernel-impl/src/main/java/org/sakaiproject/config/impl/StoredConfigService.java
  • kernel/kernel-impl/src/test/java/org/sakaiproject/messaging/impl/UserMessagingServiceTests.java
  • samigo/samigo-app/src/java/org/sakaiproject/tool/assessment/ui/queue/delivery/TimedAssessmentQueue.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/scheduling-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/scheduling-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/scheduling-components.xml
  • plus/impl/src/main/webapp/WEB-INF/components.xml
  • webcomponents/tool/src/main/frontend/packages/sakai-notifications/src/SakaiNotifications.js
**/*.{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/scheduling-components.xml
  • plus/impl/src/main/webapp/WEB-INF/components.xml
  • webcomponents/tool/src/main/frontend/packages/sakai-notifications/src/SakaiNotifications.js
**/*.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 (16)
📓 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/kernel-impl/src/test/java/org/sakaiproject/messaging/impl/UserMessagingServiceTests.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
  • config/configuration/bundles/src/bundle/org/sakaiproject/config/bundle/default.sakai.properties
  • webcomponents/tool/src/main/frontend/packages/sakai-notifications/src/SakaiNotifications.js
  • webapi/src/main/java/org/sakaiproject/webapi/controllers/SitesController.java
  • kernel/api/src/main/java/org/sakaiproject/messaging/api/model/UserNotification.java
  • kernel/kernel-impl/src/main/java/org/sakaiproject/messaging/impl/UserMessagingServiceImpl.java
  • kernel/kernel-impl/src/test/java/org/sakaiproject/messaging/impl/UserMessagingServiceTests.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:

  • announcement/announcement-api/api/src/java/org/sakaiproject/announcement/api/AnnouncementsUserNotificationHandler.java
  • config/configuration/bundles/src/bundle/org/sakaiproject/config/bundle/default.sakai.properties
  • webcomponents/tool/src/main/frontend/packages/sakai-notifications/src/SakaiNotifications.js
  • webapi/src/main/java/org/sakaiproject/webapi/controllers/SitesController.java
  • kernel/api/src/main/java/org/sakaiproject/messaging/api/model/UserNotification.java
  • kernel/kernel-impl/src/main/java/org/sakaiproject/messaging/impl/UserMessagingServiceImpl.java
  • kernel/kernel-impl/src/test/java/org/sakaiproject/messaging/impl/UserMessagingServiceTests.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/api/src/main/java/org/sakaiproject/messaging/api/UserMessagingService.java
  • webapi/src/main/java/org/sakaiproject/webapi/controllers/SitesController.java
  • kernel/kernel-impl/src/main/java/org/sakaiproject/messaging/impl/UserMessagingServiceImpl.java
  • kernel/kernel-impl/src/test/java/org/sakaiproject/messaging/impl/UserMessagingServiceTests.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
  • kernel/kernel-impl/src/test/java/org/sakaiproject/messaging/impl/UserMessagingServiceTests.java
📚 Learning: 2025-11-24T19:11:42.092Z
Learnt from: CR
Repo: sakaiproject/sakai PR: 0
File: .cursor/rules/sakai-rules.md:0-0
Timestamp: 2025-11-24T19:11:42.092Z
Learning: Applies to **/*.java : Treat any PR or suggestion containing Java `var` as non-compliant. Recommend replacing with explicit types before merge

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 **/*.java : 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`

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 **/*.java : Do not use local variable type inference (`var`) in Java code; always declare explicit types (e.g., `List<String> names = new ArrayList<>();`)

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 **/*.java : Do not use local variable type inference (var) in Java; always declare explicit types

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
  • webapi/src/main/java/org/sakaiproject/webapi/controllers/SitesController.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:

  • config/configuration/bundles/src/bundle/org/sakaiproject/config/bundle/default.sakai.properties
  • webcomponents/tool/src/main/frontend/packages/sakai-notifications/src/SakaiNotifications.js
  • webapi/src/main/java/org/sakaiproject/webapi/controllers/SitesController.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 **/sakai-push-utils.js : Handle browser-specific Web Push logic in sakai-push-utils.js

Applied to files:

  • webcomponents/tool/src/main/frontend/packages/sakai-notifications/src/SakaiNotifications.js
📚 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-12-10T10:41:09.339Z
Learnt from: st-manu
Repo: sakaiproject/sakai PR: 14256
File: kernel/kernel-impl/src/main/sql/mysql/sakai_realm.sql:734-734
Timestamp: 2025-12-10T10:41:09.339Z
Learning: In Sakai, DB upgrade/backfill for new realm functions and grants belongs in the sakai-reference repo under docs/conversion (e.g., sakai_25_1-25_2_*_conversion.sql). For SAK-51713, assessment.all.groups is granted to maintain and Instructor and backfilled across existing realms via conversion scripts.

Applied to files:

  • kernel/api/src/main/java/org/sakaiproject/messaging/api/model/UserNotification.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/main/java/org/sakaiproject/messaging/impl/UserMessagingServiceImpl.java
  • kernel/kernel-impl/src/test/java/org/sakaiproject/messaging/impl/UserMessagingServiceTests.java
🧬 Code graph analysis (5)
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)
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)
kernel/api/src/main/java/org/sakaiproject/messaging/api/model/UserNotification.java (2)
webcomponents/tool/src/main/frontend/packages/sakai-notifications/src/utils.js (1)
  • url (3-3)
lessonbuilder/tool/src/webapp/js/lessons-calendar.js (1)
  • endDate (42-42)
lessonbuilder/components/src/java/org/sakaiproject/lessonbuildertool/impl/AddLessonsCommentUserNotificationHandlerImpl.java (1)
lessonbuilder/api/src/java/org/sakaiproject/lessonbuildertool/api/LessonBuilderConstants.java (1)
  • LessonBuilderConstants (18-21)
⏰ 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: maven-build
  • GitHub Check: sakai-deploy
🔇 Additional comments (28)
config/configuration/bundles/src/bundle/org/sakaiproject/config/bundle/default.sakai.properties (1)

2258-2261: No actionable items identified. The property announcement.motd.notification.ttlhours is correctly implemented with a default of 24 hours in the code, matching the documentation in the configuration file. The scope is appropriately limited to MOTD notifications (the handler processes AnnouncementService.EVENT_MOTD_NEW events only).

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

44-44: LGTM!

Import addition is correct for the new SchedulingService dependency.


58-58: LGTM!

Properly injected SchedulingService dependency aligns with the migration objective.

webcomponents/tool/src/main/frontend/packages/sakai-notifications/src/SakaiNotifications.js (2)

404-411: LGTM: Broadcast notifications correctly exclude close button.

Excluding MOTD notifications from having individual close buttons aligns with the broadcast notification model introduced in this PR. Like test notifications, broadcast notifications represent system-wide messages that shouldn't be dismissed individually.


87-90: Verify if credentials are required for the notifications endpoint.

The credentials: "include" option is missing from this GET fetch call. While other POST endpoints in this file (lines 245, 263) include it, the POST test notification endpoint (line 361-362) also lacks credentials. Without verifying whether the /api/users/me/notifications endpoint requires authentication, it's unclear if this is an intentional design choice or a bug. Check the backend API implementation to confirm if authenticated requests are required for fetching initial notifications.

kernel/kernel-impl/src/main/java/org/sakaiproject/event/impl/ClusterEventTracking.java (2)

226-231: Migration to SchedulingService is correct.

The scheduled task lifecycle is properly managed by the injected SchedulingService. The SchedulingService.destroy() method calls executor.shutdownNow() on its internal ScheduledThreadPoolExecutor, which automatically cancels all scheduled tasks when the service shuts down. The 60-second initial delay and m_period interval are appropriate for the event checking task.


44-44: LGTM! Dependency injection pattern follows established conventions.

The import and abstract method addition for SchedulingService follow the same dependency injection pattern used by other collaborators in this class (sqlService(), serverConfigurationService(), memoryService()). The Spring configuration in event-components.xml properly wires this dependency via lookup-method, and the SchedulingService interface has the correct scheduleWithFixedDelay() signature with the expected parameters.

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

91-94: LGTM! Configuration initialization follows best practices.

The @PostConstruct initialization properly reads the MOTD TTL configuration with a sensible default of 24 hours.


205-217: LGTM! Role-based filtering implementation is correct.

The role filtering logic properly checks the SELECTED_ROLES_PROPERTY and filters users based on their site membership roles.


240-249: LGTM! Helper method reduces duplication.

The buildWhere helper method appropriately extracts the common predicate logic used in both query and delete operations.


147-170: The commit message for SAK-51956 does not mention removing TransactionTemplate wrappers. The stated PR objectives are to replace MOTD notifications with a single database entry and create a SchedulingService. The TransactionTemplate usage at lines 151-167 is in a newly added file, not a modification that conflicts with stated objectives. No clarification needed.

Likely an incorrect or invalid review comment.

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

27-27: LGTM! Repository method follows Spring Data conventions.

The findByBroadcast method follows proper Spring Data JPA naming conventions.


29-29: Implementation is correct and properly handles expired notification deletion.

The implementation uses @Transactional for transaction handling and correctly queries notifications where endDate is before the current time using CriteriaDelete with a lessThan comparison against Instant.now(). The executeUpdate() call properly executes the bulk delete operation.

kernel/kernel-impl/src/main/webapp/WEB-INF/scheduling-components.xml (2)

1-12: No action needed. The scheduling-components.xml configuration file is already properly imported in the main Spring configuration (components.xml).


6-10: SchedulingServiceImpl lifecycle methods are correctly implemented.

The init() method (lines 33-38) properly initializes a ScheduledThreadPoolExecutor with a configurable pool size from the server configuration service and sets the RemoveOnCancelPolicy. The destroy() method (lines 40-43) correctly shuts down the executor with shutdownNow(). The bean configuration's lifecycle method references are appropriately matched to the implementation.

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

41-46: No action required - API backward compatibility is maintained.

The transfer bean includes all required fields and they are properly populated. The decorateNotification() method enriches each notification with the display fields (fromDisplayName, formattedEventDate, siteTitle) before returning to clients. All fields from the original entity are preserved in the transfer bean, with appropriate error handling to gracefully fall back when user or site lookups fail. JSON serialization remains compatible with existing clients.

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

90-91: Database migration not required for BROADCAST column.

The new BROADCAST column is defined in the UserNotification JPA entity and will be automatically created by Hibernate's schema generation (controlled by the auto.ddl property). The messaging module uses a modern Hibernate DDL approach rather than traditional SQL migration scripts, so no additional database migration script is needed.

Likely an incorrect or invalid review comment.


70-71: No database migration script required.

The URL column is nullable by default in JPA when @Column lacks an explicit nullable = false constraint. There is no evidence that the column constraint was modified from non-nullable to nullable. If USER_NOTIFICATIONS is a new table, schema creation is handled automatically by Hibernate and does not require manual migration scripts.

Likely an incorrect or invalid review comment.

lessonbuilder/components/src/java/org/sakaiproject/lessonbuildertool/impl/AddLessonsCommentUserNotificationHandlerImpl.java (1)

75-75: LGTM! Constructor calls updated correctly.

Both UserNotificationData constructor calls have been properly updated to include the two new parameters (false for the broadcast flag and null for the TTL/payload). The false value correctly indicates these are user-specific notifications rather than broadcast notifications, which aligns with the lesson comment notification use case.

Also applies to: 90-90

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

105-105: LGTM! SchedulingService import added.

The import for SchedulingService is correctly added to support the migration from local ScheduledExecutorService.


149-149: LGTM! SchedulingService properly injected.

The SchedulingService is correctly autowired and will replace the previously used private ScheduledExecutorService.


163-168: LGTM! Clean migration to SchedulingService.

The scheduled task initialization has been properly migrated to use the centralized SchedulingService. The scheduling parameters (120s initial delay, 60s fixed delay) are preserved, and TimeUnit.SECONDS is now explicitly specified for clarity. The removal of the destroy() method (referenced in the PR description) is appropriate since SchedulingService handles lifecycle management centrally.

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

97-97: LGTM! Constructor call updated correctly.

The UserNotificationData constructor call has been properly updated to include the two new parameters (false for the broadcast flag and null for the TTL/payload). The false value is appropriate since grade notifications are user-specific rather than broadcast notifications.

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

71-72: Remove this review comment — the premise is incorrect.

PlusServiceImpl does not have a destroy() method, and the destroy-method attribute was never present on the PlusService bean. The class uses SchedulingService to schedule background tasks in its init() method, and SchedulingService automatically handles cleanup of scheduled tasks on application shutdown. No additional destroy logic is needed or missing.

Likely an incorrect or invalid review comment.

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

65-65: All callers of getNotifications() already handle the new return type List<UserNotificationTransferBean> correctly. No breaking changes remain.

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

488-498: LGTM on merging personal + broadcast into a single DTO list, but watch NPEs in decoration.

The bean-based flow looks consistent with the new API. Just ensure notification.eventDate is always non-null before calling userTimeService.dateTimeFormat(...) (broadcast fanout currently violates this—see below).

Also applies to: 520-541


226-237: Destroy should also stop observer and wait for executor termination.

The review identifies potential resource leaks: destroy() calls executor.shutdown() but lacks awaitTermination(), and may not properly clean up observers initialized in init(). This requires verification against the actual implementation to confirm the observer exists, how it's managed, and whether proper termination semantics are necessary for this executor's usage pattern.

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

36-38: Make Lombok-set fields private and guard null SchedulingService before scheduling.

Right now those fields are package-private; please keep them private (with setters) and fail fast (or skip scheduling) if schedulingService is missing when polling is enabled. Fields with only @Setter (without an explicit private modifier) become package-private, which is an encapsulation regression and may break expectations. Additionally, if schedulingService isn't injected and polling is enabled, this will cause a NullPointerException.

Proposed fix
@@
-    @Setter SchedulingService schedulingService;
-    @Setter ServerConfigurationService serverConfigurationService;
-    @Setter HibernateConfigItemDao dao;
-    @Setter PBEStringEncryptor textEncryptor;
+    @Setter private SchedulingService schedulingService;
+    @Setter private ServerConfigurationService serverConfigurationService;
+    @Setter private HibernateConfigItemDao dao;
+    @Setter private PBEStringEncryptor textEncryptor;
@@
         if (serverConfigurationService.getBoolean(SAKAI_CONFIG_POLL_ENABLE, false)) {
+            if (schedulingService == null) {
+                log.error("{} is enabled but schedulingService is not configured; skipping poll scheduling", SAKAI_CONFIG_POLL_ENABLE);
+                return;
+            }
             final int pollDelaySeconds = serverConfigurationService.getInt(SAKAI_CONFIG_POLL_SECONDS, 60);
             // schedule task for every pollDelaySeconds
             schedulingService.scheduleWithFixedDelay(

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

Caution

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

⚠️ Outside diff range comments (2)
kernel/kernel-impl/src/main/java/org/sakaiproject/messaging/impl/UserMessagingServiceImpl.java (2)

341-360: unregisterHandler(...) can remove the wrong handler.
It removes by event name if present, without checking that the mapped handler is the one being unregistered.

Proposed fix
         for (String eventName : handler.getHandledEvents()) {
-            if (newMap.containsKey(eventName)) {
-                newMap.remove(eventName);
+            if (newMap.get(eventName) == handler) {
+                newMap.remove(eventName);
                 mapChanged = true;
                 log.debug("Unregistered bullhorn handler {} for event: {}", handler.getClass().getName(), eventName);
             }
         }

647-683: Fix critical connection leak and improve logging for security.

  • HttpResponse from pushService.send() is never closed or consumed, causing Apache HttpClient connection pool exhaustion under sustained notification traffic. Add EntityUtils.consumeQuietly(pushResponse.getEntity()); after reading the status code, or wrap in try-with-resources.
  • The exception logging pattern e.toString() doesn't align with SLF4J best practices; pass the exception directly to the logger: log.error("Failed to serialize notification for push", e) to ensure proper formatting and context.
  • subscription.getEndpoint() is a sensitive push authentication token; log it only at debug level in clearPushSubscription to reduce exposure in production logs.
🤖 Fix all issues with AI agents
In
@kernel/api/src/main/java/org/sakaiproject/messaging/api/UserNotificationTransferBean.java:
- Around line 40-54: In of(UserNotification un) guard against null auto-unboxing
for the boxed Boolean fields by replacing direct assignments to the primitive
fields with null-safe checks; e.g., set bean.viewed =
Boolean.TRUE.equals(un.getViewed()) and bean.broadcast =
Boolean.TRUE.equals(un.getBroadcast()) (or use Objects.requireNonNullElse/unbox
with a default) inside the of(UserNotification) method to avoid NPEs when
un.getViewed() or un.getBroadcast() is null.

In
@kernel/kernel-impl/src/main/java/org/sakaiproject/messaging/impl/UserMessagingServiceImpl.java:
- Around line 158-178: The scheduled cleanup Runnable passed to
schedulingService.scheduleWithFixedDelay lacks exception handling and its
ScheduledFuture is not stored, and destroy() only calls executor.shutdown()
without cancelling the scheduled task or awaiting termination; fix by wrapping
the call to userNotificationRepository.deleteExpiredNotifications() inside a
try/catch that logs exceptions, assign the result of
schedulingService.scheduleWithFixedDelay(...) to a ScheduledFuture field (e.g.,
cleanupFuture) so you can cancel it on shutdown, and update destroy() to cancel
cleanupFuture (with mayInterruptIfRunning=true), call executor.shutdown(), await
termination with a timeout, and call executor.shutdownNow() if it doesn’t
terminate in time to ensure proper shutdown.
- Around line 495-505: getNotifications() returns both per-user
(findByToUser(userId)) and broadcast (findByBroadcast(true)) notifications but
clearNotification() and markAllNotificationsViewed() only operate when
un.getToUser().equals(userId), so broadcasts (toUser == "BROADCAST") are never
markable/clearable. Fix by updating clearNotification(...) and
markAllNotificationsViewed() to treat broadcast notifications as addressable by
the current user: change the ownership checks to allow either
un.getToUser().equals(userId) OR un.isBroadcast() / "BROADCAST". Ensure you
reference the same UserNotification methods/fields used elsewhere (e.g.,
UserNotification.getToUser(), UserNotification.isBroadcast(),
clearNotification(), markAllNotificationsViewed(), and
decorateNotification()/UserNotificationTransferBean) so broadcasts can be marked
viewed/cleared for the current user while preserving the broadcast flag for
others.
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a23f5d8 and 2a4d6ae.

📒 Files selected for processing (2)
  • kernel/api/src/main/java/org/sakaiproject/messaging/api/UserNotificationTransferBean.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/api/src/main/java/org/sakaiproject/messaging/api/UserNotificationTransferBean.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
📚 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
📚 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
📚 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
📚 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 **/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-service-worker.js : Handle background push 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: Applies to **/sakai-push-utils.js : Handle browser-specific Web Push logic in sakai-push-utils.js

Applied to files:

  • kernel/kernel-impl/src/main/java/org/sakaiproject/messaging/impl/UserMessagingServiceImpl.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: maven-build
  • GitHub Check: maven-build
  • GitHub Check: sakai-deploy
🔇 Additional comments (1)
kernel/kernel-impl/src/main/java/org/sakaiproject/messaging/impl/UserMessagingServiceImpl.java (1)

568-581: Ensure new i18n keys are in sakai-notifications.properties.
test_notification_title is fetched from resourceLoader; make sure it’s present (and localized) in sakai-notifications.properties. Based on learnings, keep localization strings there.

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 (3)
microsoft-integration/impl/src/main/java/org/sakaiproject/microsoft/impl/MicrosoftCommonServiceImpl.java (1)

946-994: Remove unused executor - resource leak.

Line 947 creates a ScheduledExecutorService that is never used or shut down, causing a resource and thread leak. The method correctly uses schedulingService.schedule() at lines 981 and 993, making the local executor unnecessary.

🔧 Proposed fix

Remove the unused executor declaration:

 public void createTeamFromGroupAsync(String groupId) throws MicrosoftCredentialsException {
-    ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);
-    
     AtomicInteger counter = new AtomicInteger(1);
     final int MAX_RETRY = 5;
kernel/kernel-impl/src/main/java/org/sakaiproject/authz/impl/DbAuthzGroupService.java (1)

289-334: Cancel the scheduled refresh task on destroy() (don’t ignore the returned ScheduledFuture).
Right now the periodic task can keep running after destroy() (and after caches are closed), since there’s no local executor to shut down anymore and the returned handle is dropped.

Proposed fix
+import java.util.concurrent.ScheduledFuture;
 import java.util.concurrent.TimeUnit;
@@
 	/** Queue of authzgroups to refresh used by refreshAuthzGroupTask */
 	private Map<String, AuthzGroup> refreshQueue;
+
+	private ScheduledFuture<?> refreshAuthzGroupTaskFuture;
@@
-            schedulingService().scheduleWithFixedDelay(
+            refreshAuthzGroupTaskFuture = schedulingService().scheduleWithFixedDelay(
                 new RefreshAuthzGroupTask(),
                 120, // minimally wait 2 mins for sakai to start
                 refreshTaskInterval, // delay before running again
                 TimeUnit.SECONDS
             );
@@
 	public void destroy()
 	{
+		if (refreshAuthzGroupTaskFuture != null) {
+			refreshAuthzGroupTaskFuture.cancel(false);
+			refreshAuthzGroupTaskFuture = null;
+		}
+
 		// done with event watching
 		eventTrackingService().deleteObserver(this);
samigo/samigo-app/src/java/org/sakaiproject/tool/assessment/ui/queue/delivery/TimedAssessmentQueue.java (1)

91-98: Potential NullPointerException if task not found and logging uses string concatenation.

  1. tasks.get(timedAG) may return null if the task doesn't exist, causing NPE on .cancel(true).
  2. Log message uses string concatenation instead of parameterized logging.
🐛 Proposed fix
   // Remove a timed assessment from the queue
   public void remove(long timedAG){
-    log.info("SAMIGO_TIMED_ASSESSMENT:QUEUE:REMOVE ID:" + timedAG);
+    log.info("SAMIGO_TIMED_ASSESSMENT:QUEUE:REMOVE ID:{}", timedAG);
     // Stop the task and remove it
-    tasks.get(timedAG).cancel(true);
-    tasks.remove(timedAG);
+    ScheduledFuture<?> task = tasks.remove(timedAG);
+    if (task != null) {
+        task.cancel(true);
+    }
     // Remove the grading data from the queue
     queue.remove(timedAG);
   }
🤖 Fix all issues with AI agents
In
@announcement/announcement-api/api/src/java/org/sakaiproject/announcement/api/AnnouncementsUserNotificationHandler.java:
- Around line 205-217: In AnnouncementsUserNotificationHandler update the
role-based filter that builds filteredUsersToNotify to guard against a null
role: when streaming usersToNotify and calling site.getMember(u), ensure you
also check member.getRole() != null before invoking getId() (i.e., include
member.getRole() != null in the predicate that checks
selectedRoles.contains(member.getRole().getId())); this prevents a possible NPE
when member.getRole() can be null.

In
@kernel/kernel-impl/src/main/java/org/sakaiproject/messaging/impl/repository/UserNotificationRepositoryImpl.java:
- Around line 52-63: The findByBroadcast method in
UserNotificationRepositoryImpl is missing a transactional annotation and also
excludes rows with null endDate due to the greaterThan predicate; add
@Transactional(readOnly = true) to the findByBroadcast method for read-only
consistency (matching other repo methods like
PushSubscriptionRepositoryImpl.findByUser) and either update the criteria to
include null endDate values (e.g., combine cb.isNull(un.get("endDate")) OR
cb.greaterThan(un.get("endDate"), now)) or add a comment/documentation on the
method explaining that excluding null endDate rows is intentional.

In
@kernel/kernel-impl/src/main/java/org/sakaiproject/messaging/impl/UserMessagingServiceImpl.java:
- Around line 586-610: The pagination loop in pushToAllUsers uses
page.getTotalElements() (causing an infinite loop) and pageSize=1 (inefficient),
and it mutates the shared UserNotificationTransferBean.bean.to inside the lambda
(not thread-safe). Change the loop to page =
pushSubscriptionRepository.findAll(PageRequest.of(pageNumber, pageSize)); while
(page.hasContent()) or while (!page.getContent().isEmpty()) and increment
pageNumber each iteration; increase pageSize to a reasonable batch (e.g., 100);
replace Map<String,String> done with a Set<String> done = new HashSet<>(); and
avoid mutating the shared bean by creating a per-user copy (e.g., construct a
new UserNotificationTransferBean or add a copyWithTo(userId) helper) and call
push(copy) or set a local final variable inside the forEach rather than
modifying the original bean.
🧹 Nitpick comments (12)
announcement/announcement-api/api/src/java/org/sakaiproject/announcement/api/AnnouncementsUserNotificationHandler.java (2)

91-94: LGTM!

The initialization method properly loads the MOTD TTL configuration with a reasonable default of 24 hours.

Optional: Consider a more conventional configuration key name

The configuration key announcement.motd.notification.ttlhours could use a separator for better readability:

-        motdTTLHours = serverConfigurationService.getInt("announcement.motd.notification.ttlhours", 24);
+        motdTTLHours = serverConfigurationService.getInt("announcement.motd.notification.ttl.hours", 24);

However, this is a minor style preference and the current naming is acceptable.


124-134: Extract magic string "BROADCAST" as a constant.

The broadcast recipient identifier is hardcoded as a string literal. Define it as a private static final constant for maintainability and consistency.

♻️ Proposed refactor to extract constant

Add a constant at the class level:

private static final String BROADCAST_RECIPIENT = "BROADCAST";

Then update line 129:

-                userNotificationData.setTo("BROADCAST");
+                userNotificationData.setTo(BROADCAST_RECIPIENT);
kernel/kernel-impl/src/main/java/org/sakaiproject/scheduling/impl/SchedulingServiceImpl.java (2)

40-43: Consider graceful shutdown with timeout.

shutdownNow() immediately interrupts running tasks. For cleaner shutdown semantics, consider waiting briefly for tasks to complete before forcing termination.

♻️ Proposed graceful shutdown
 public void destroy() {
-
-    executor.shutdownNow();
+    executor.shutdown();
+    try {
+        if (!executor.awaitTermination(5, TimeUnit.SECONDS)) {
+            executor.shutdownNow();
+        }
+    } catch (InterruptedException e) {
+        executor.shutdownNow();
+        Thread.currentThread().interrupt();
+    }
 }

27-38: Add logging for observability.

The service lacks any logging. Consider adding SLF4J logging for lifecycle events and potential scheduling errors to aid debugging and monitoring.

♻️ Add logging
+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
 public class SchedulingServiceImpl implements SchedulingService {

     @Autowired private ServerConfigurationService serverConfigurationService;

     private ScheduledThreadPoolExecutor executor;

     public void init() {
-
         int poolSize = serverConfigurationService.getInt("schedulingservice.poolsize", 8);
         executor = new ScheduledThreadPoolExecutor(poolSize);
         executor.setRemoveOnCancelPolicy(true);
+        log.info("SchedulingService initialized with pool size: {}", poolSize);
     }
kernel/kernel-impl/src/test/java/org/sakaiproject/event/impl/test/LearningResourceTestConfiguration.java (1)

45-46: SchedulingService test bean looks correct for the new wiring.
Only caveat: if this mock is needed by just one test class (vs. broadly across the learning-resource test slice), consider keeping it in that test instead of central config. Based on learnings, test-specific mock configuration should live in the test unless reused.

Also applies to: 200-203

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

22-55: Add a null-guard in UserNotificationTransferBean.of(...) to avoid edge NPEs.

Proposed fix
+import java.util.Objects;
@@
     public static UserNotificationTransferBean of(UserNotification un) {
+        Objects.requireNonNull(un, "userNotification");
 
         UserNotificationTransferBean bean = new UserNotificationTransferBean();
kernel/api/src/main/java/org/sakaiproject/messaging/api/UserNotificationData.java (1)

18-37: Double-check the semantic impact of switching to Lombok @Data (setters + equals/hashCode) and introducing Duration ttl.
If callers rely on immutability or identity semantics, @Data can be surprising; also confirm how Duration is meant to be persisted/serialized (and whether null means “no expiry”).

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

21-47: Consider renaming scheduleAtFixedRate(..., long delay, ...) param to period (matches JDK) and clarifying cancellation expectations in Javadoc.
Since this is a brand-new API, tightening names/docs now reduces long-term confusion.

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

90-91: Consider adding a database index on the BROADCAST column.

Since UserNotificationRepository.findByBroadcast(boolean) will query by this column, an index would improve query performance, especially as the notification table grows.

💡 Suggested index addition
 @Table(name = "USER_NOTIFICATIONS", indexes = {
     @Index(name = "IDX_USER_NOTIFICATIONS_TO_USER", columnList = "TO_USER"),
-    @Index(name = "IDX_USER_NOTIFICATIONS_EVENT_REF", columnList = "EVENT, REF")
+    @Index(name = "IDX_USER_NOTIFICATIONS_EVENT_REF", columnList = "EVENT, REF"),
+    @Index(name = "IDX_USER_NOTIFICATIONS_BROADCAST", columnList = "BROADCAST")
 })
samigo/samigo-app/src/java/org/sakaiproject/tool/assessment/ui/queue/delivery/TimedAssessmentQueue.java (2)

77-79: Use SLF4J parameterized logging instead of string concatenation.

Per coding guidelines, log messages should use parameterized logging for better performance and readability.

♻️ Proposed fix
-              log.info( "SAMIGO_TIMED_ASSESSMENT:QUEUE:NEWTIMER:SUCCESS ID:" + timedAG.getAssessmentGradingId());
+              log.info("SAMIGO_TIMED_ASSESSMENT:QUEUE:NEWTIMER:SUCCESS ID:{}", timedAG.getAssessmentGradingId());
          } catch (Exception ex) {
-              log.error("SAMIGO_TIMED_ASSESSMENT:QUEUE:NEWTIMER:FAILED ID:" + timedAG.getAssessmentGradingId() + " Exception:" + ex);
+              log.error("SAMIGO_TIMED_ASSESSMENT:QUEUE:NEWTIMER:FAILED ID:{}", timedAG.getAssessmentGradingId(), ex);
          }

42-42: Use parameterized types instead of raw ConcurrentHashMap.

Raw types should be avoided for type safety.

♻️ Proposed fix
-  private ConcurrentHashMap queue;
+  private ConcurrentHashMap<Long, TimedAssessmentGradingModel> queue;

And update the constructor accordingly:

-    queue = new ConcurrentHashMap();
+    queue = new ConcurrentHashMap<>();
kernel/kernel-impl/src/main/java/org/sakaiproject/messaging/impl/UserMessagingServiceImpl.java (1)

500-504: Consider performance of fetching broadcasts on every getNotifications() call.

findByBroadcast(true) is called for every user request. For high-traffic systems, consider caching active broadcasts with a short TTL, especially since they rarely change.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2a4d6ae and f5458b2.

📒 Files selected for processing (41)
  • 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/RuntimeRegistrationTest.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-app/src/java/org/sakaiproject/tool/assessment/ui/queue/delivery/TimedAssessmentQueue.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 with no reviewable changes (1)
  • kernel/kernel-impl/src/test/java/org/sakaiproject/messaging/impl/RuntimeRegistrationTest.java
🚧 Files skipped from review as they are similar to previous changes (17)
  • webapi/src/main/java/org/sakaiproject/webapi/controllers/NotificationsController.java
  • kernel/kernel-impl/src/main/webapp/WEB-INF/components.xml
  • lessonbuilder/components/src/java/org/sakaiproject/lessonbuildertool/impl/AddLessonsCommentUserNotificationHandlerImpl.java
  • plus/impl/src/test/org/sakaiproject/plus/impl/PlusTestConfiguration.java
  • webcomponents/tool/src/main/frontend/packages/sakai-notifications/src/SakaiNotifications.js
  • kernel/kernel-impl/src/test/java/org/sakaiproject/messaging/impl/UserMessagingServiceTestConfiguration.java
  • plus/impl/src/main/java/org/sakaiproject/plus/impl/PlusServiceImpl.java
  • kernel/kernel-impl/src/main/java/org/sakaiproject/authz/impl/AuthzGroupServiceTest.java
  • kernel/kernel-impl/src/main/webapp/WEB-INF/event-components.xml
  • assignment/api/src/java/org/sakaiproject/assignment/api/AddAssignmentUserNotificationHandler.java
  • kernel/api/src/main/java/org/sakaiproject/messaging/api/repository/UserNotificationRepository.java
  • config/configuration/bundles/src/bundle/org/sakaiproject/config/bundle/default.sakai.properties
  • kernel/kernel-impl/src/main/webapp/WEB-INF/scheduling-components.xml
  • kernel/api/src/main/java/org/sakaiproject/messaging/api/UserMessagingService.java
  • commons/api/src/java/org/sakaiproject/commons/api/CommonsCommentUserNotificationHandler.java
  • webapi/src/main/java/org/sakaiproject/webapi/controllers/SitesController.java
  • msgcntr/messageforums-api/src/java/org/sakaiproject/api/app/messageforums/PrivateMessageUserNotificationHandler.java
🧰 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:

  • kernel/kernel-impl/src/main/java/org/sakaiproject/event/impl/EventTrackingTest.java
  • kernel/kernel-impl/src/main/java/org/sakaiproject/scheduling/impl/SchedulingServiceImpl.java
  • kernel/kernel-impl/src/test/java/org/sakaiproject/messaging/impl/UserMessagingServiceTests.java
  • kernel/kernel-impl/src/main/java/org/sakaiproject/event/impl/ClusterEventTracking.java
  • samigo/samigo-services/src/java/org/sakaiproject/tool/assessment/TestsAndQuizzesUserNotificationHandler.java
  • kernel/kernel-impl/src/test/java/org/sakaiproject/event/impl/test/LearningResourceTestConfiguration.java
  • kernel/kernel-impl/src/main/java/org/sakaiproject/messaging/impl/repository/UserNotificationRepositoryImpl.java
  • microsoft-integration/impl/src/main/java/org/sakaiproject/microsoft/impl/MicrosoftCommonServiceImpl.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
  • samigo/samigo-app/src/java/org/sakaiproject/tool/assessment/ui/queue/delivery/TimedAssessmentQueue.java
  • kernel/kernel-impl/src/main/java/org/sakaiproject/messaging/impl/UserMessagingServiceImpl.java
  • kernel/api/src/main/java/org/sakaiproject/messaging/api/UserNotificationData.java
  • kernel/api/src/main/java/org/sakaiproject/messaging/api/model/UserNotification.java
  • kernel/api/src/main/java/org/sakaiproject/scheduling/api/SchedulingService.java
  • kernel/kernel-impl/src/main/java/org/sakaiproject/content/impl/FileConversionServiceImpl.java
  • kernel/kernel-impl/src/main/java/org/sakaiproject/authz/impl/DbAuthzGroupService.java
  • kernel/kernel-impl/src/test/java/org/sakaiproject/content/impl/test/FileConversionServiceTestConfiguration.java
  • assignment/api/src/java/org/sakaiproject/assignment/api/GradeAssignmentUserNotificationHandler.java
  • kernel/api/src/main/java/org/sakaiproject/messaging/api/UserNotificationTransferBean.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/authz-components.xml
  • plus/impl/src/main/webapp/WEB-INF/components.xml
  • kernel/kernel-impl/src/main/webapp/WEB-INF/configstore-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/authz-components.xml
  • plus/impl/src/main/webapp/WEB-INF/components.xml
  • kernel/kernel-impl/src/main/webapp/WEB-INF/configstore-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/authz-components.xml
  • plus/impl/src/main/webapp/WEB-INF/components.xml
  • kernel/kernel-impl/src/main/webapp/WEB-INF/configstore-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/authz-components.xml
  • plus/impl/src/main/webapp/WEB-INF/components.xml
  • kernel/kernel-impl/src/main/webapp/WEB-INF/configstore-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 (18)
📓 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`
📚 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/test/java/org/sakaiproject/messaging/impl/UserMessagingServiceTests.java
  • 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:

  • kernel/kernel-impl/src/test/java/org/sakaiproject/messaging/impl/UserMessagingServiceTests.java
  • 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-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
  • 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 **/*.{java,properties} : Reconcile push notification subscriptions proactively and keep localization strings in `sakai-notifications.properties`

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/repository/UserNotificationRepositoryImpl.java
  • announcement/announcement-api/api/src/java/org/sakaiproject/announcement/api/AnnouncementsUserNotificationHandler.java
  • samigo/samigo-app/src/java/org/sakaiproject/tool/assessment/ui/queue/delivery/TimedAssessmentQueue.java
  • kernel/kernel-impl/src/main/java/org/sakaiproject/messaging/impl/UserMessagingServiceImpl.java
  • kernel/api/src/main/java/org/sakaiproject/messaging/api/model/UserNotification.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
  • 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: 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/authz-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
📚 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/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
  • announcement/announcement-api/api/src/java/org/sakaiproject/announcement/api/AnnouncementsUserNotificationHandler.java
  • samigo/samigo-app/src/java/org/sakaiproject/tool/assessment/ui/queue/delivery/TimedAssessmentQueue.java
  • kernel/kernel-impl/src/main/java/org/sakaiproject/messaging/impl/UserMessagingServiceImpl.java
  • kernel/api/src/main/java/org/sakaiproject/messaging/api/model/UserNotification.java
📚 Learning: 2025-11-24T19:11:42.092Z
Learnt from: CR
Repo: sakaiproject/sakai PR: 0
File: .cursor/rules/sakai-rules.md:0-0
Timestamp: 2025-11-24T19:11:42.092Z
Learning: Applies to **/*.java : Treat any PR or suggestion containing Java `var` as non-compliant. Recommend replacing with explicit types before merge

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 **/*.java : 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`

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 **/*.java : Do not use local variable type inference (`var`) in Java code; always declare explicit types (e.g., `List<String> names = new ArrayList<>();`)

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 **/*.java : Do not use local variable type inference (var) in Java; always declare explicit types

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
📚 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: Applies to **/sakai-push-utils.js : Handle browser-specific Web Push logic in sakai-push-utils.js

Applied to files:

  • kernel/kernel-impl/src/main/java/org/sakaiproject/messaging/impl/UserMessagingServiceImpl.java
📚 Learning: 2025-12-10T10:41:09.339Z
Learnt from: st-manu
Repo: sakaiproject/sakai PR: 14256
File: kernel/kernel-impl/src/main/sql/mysql/sakai_realm.sql:734-734
Timestamp: 2025-12-10T10:41:09.339Z
Learning: In Sakai, DB upgrade/backfill for new realm functions and grants belongs in the sakai-reference repo under docs/conversion (e.g., sakai_25_1-25_2_*_conversion.sql). For SAK-51713, assessment.all.groups is granted to maintain and Instructor and backfilled across existing realms via conversion scripts.

Applied to files:

  • kernel/api/src/main/java/org/sakaiproject/messaging/api/model/UserNotification.java
🧬 Code graph analysis (4)
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)
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)
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 (22-56)
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)
⏰ 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: maven-build
  • GitHub Check: sakai-deploy
🔇 Additional comments (26)
plus/impl/src/main/webapp/WEB-INF/components.xml (1)

70-74: Verify PlusServiceImpl.init() lifecycle (idempotency + teardown), especially if it schedules work.

Adding init-method="init" is fine, but please ensure init() can’t accidentally double-register observers / double-schedule jobs (e.g., on context refresh), and that any registrations/scheduled tasks are properly cleaned up on shutdown (either via a destroy() hook or whatever lifecycle SchedulingService expects). If PlusServiceImpl has teardown logic, consider wiring it here too.

Optional wiring (only if `PlusServiceImpl.destroy()` exists and is required)
     <bean id="org.sakaiproject.plus.api.PlusService"
         class="org.sakaiproject.plus.impl.PlusServiceImpl"
-        init-method="init">
+        init-method="init"
+        destroy-method="destroy">
         <!-- all the properties are autowired - Yay -->
     </bean>
announcement/announcement-api/api/src/java/org/sakaiproject/announcement/api/AnnouncementsUserNotificationHandler.java (4)

24-24: LGTM!

The new imports are appropriate for the broadcast notification and role filtering functionality.

Also applies to: 31-31, 36-36, 40-40


87-89: LGTM!

The constant and field declarations are appropriate for the new role filtering and MOTD TTL functionality.


146-170: LGTM!

The transaction handling correctly uses the query-detach-then-bulk-delete pattern to avoid persistence context issues. The error handling appropriately logs warnings without disrupting the event processing chain.


240-249: LGTM!

The buildWhere() helper method is a well-designed refactoring that eliminates code duplication and ensures consistency between query and delete operations. The predicate logic correctly identifies notifications to remove.

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

65-77: LGTM!

The deleteExpiredNotifications method is correctly annotated with @Transactional for the delete operation. The implicit exclusion of records with null endDate is appropriate behavior (notifications without an end date should not expire).

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

45-67: LGTM!

The scheduling methods properly delegate to the underlying executor and return the ScheduledFuture<?> for callers to manage their scheduled tasks.

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

168-168: LGTM!

The UserNotificationData constructor is correctly updated to include the new broadcast (false) and ttl (null) parameters, consistent with assessment notifications being user-specific rather than system-wide broadcasts.


210-210: LGTM!

Consistent with the handleAdd method update.

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

94-101: LGTM!

The schedulingService() method follows the established pattern in this test class, providing a null stub for the dependency injector.

kernel/kernel-impl/src/test/java/org/sakaiproject/messaging/impl/UserMessagingServiceTests.java (1)

172-172: LGTM!

The test correctly uses the updated UserNotificationData constructor with the new broadcast (false) and ttl (null) parameters, consistent with testing non-broadcast notifications.

kernel/kernel-impl/src/test/java/org/sakaiproject/content/impl/test/FileConversionServiceTestConfiguration.java (1)

70-73: LGTM!

The mock SchedulingService bean follows the established pattern in this test configuration and uses the correct bean naming convention.

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

22-22: The lookup-method wiring for schedulingService is correctly configured.

The abstract method exists in DbAuthzGroupService (line 129) with the correct return type, the bean reference follows established naming conventions, and the method is actively used in the implementation. The placement among other lookup-method declarations is consistent with the existing pattern.

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

43-46: LGTM: Clean migration to SchedulingService.

The removal of destroy-method and addition of the schedulingService property correctly reflects the migration from locally-managed executors to centralized scheduling. The SchedulingService now handles lifecycle management, eliminating the need for component-level cleanup.

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

113-114: LGTM: SchedulingService dependency properly added.

The SchedulingService import and field declaration are correctly added with appropriate Lombok annotations for dependency injection.

Also applies to: 194-194

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

44-44: LGTM: SchedulingService properly integrated.

The SchedulingService dependency is correctly imported and autowired, following standard Spring patterns.

Also applies to: 58-58


114-206: LGTM: Clean migration to SchedulingService.

The periodic task scheduling has been successfully migrated from a local master executor to the centralized SchedulingService. The scheduling parameters and logic remain unchanged, and lifecycle management is now properly delegated. The workers executor is correctly retained for task execution.

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

44-44: LGTM: Clean migration to SchedulingService with abstract dependency pattern.

The migration successfully replaces local scheduler management with the centralized SchedulingService. The abstract method pattern (schedulingService()) allows concrete implementations to provide the dependency while preserving the scheduling logic and parameters. Lifecycle management is now properly delegated to the SchedulingService.

Also applies to: 105-106, 226-231

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

97-97: The hardcoded parameters (false for broadcast, null for ttl) are correct and consistent with the codebase. Every notification handler across all modules uses this exact pattern: new UserNotificationData(..., false, null). The false appropriately indicates an individual notification (not broadcast), and null indicates no expiration, which is the standard for personal notifications. No changes needed.

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

129-130: The only concrete subclass of DbAuthzGroupService in the codebase is AuthzGroupServiceTest, which properly implements the required schedulingService() method (returning null for testing). No compilation or runtime failures would occur from the new abstract method declaration.

Likely an incorrect or invalid review comment.

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

77-79: New endDate field looks good for TTL support.

The field is correctly annotated with @Type(type = "org.hibernate.type.InstantType") for proper Instant persistence. This enables the scheduled cleanup of expired notifications via deleteExpiredNotifications().

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

85-88: Clean migration to Lombok setters for dependency injection.

Using @Setter for Spring-managed dependencies is consistent with the project's patterns and reduces boilerplate.


129-139: Proper migration to SchedulingService.

The scheduling logic is correctly delegated to the centralized SchedulingService, which aligns with the PR objective of providing a facade over ScheduledExecutorService.

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

175-177: Good addition of scheduled cleanup for expired notifications.

This correctly implements the TTL functionality by periodically removing expired notifications.


422-455: Clean refactoring of doInsert to use UserNotificationData.

The method properly handles TTL-to-endDate conversion and populates the broadcast flag. The transactional boundary is correctly maintained.


527-547: Proper decoration of UserNotificationTransferBean.

The method correctly populates display fields (fromDisplayName, formattedEventDate, siteTitle) and handles exceptions gracefully with fallback values.

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

Caution

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

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

946-994: Thread leak + scheduler thread blocking in createTeamFromGroupAsync after moving to SchedulingService.

  • Executors.newScheduledThreadPool(1) (Line 947) appears unused now and (if per-call) will leak threads.
  • Thread.sleep(30000) (Line 971) will block a scheduler thread; with a shared SchedulingService this can degrade unrelated tasks.
  • Also fix string-concatenation logging to SLF4J parameterized logging per guidelines.
Proposed fix (remove executor, avoid sleep, fix logging)
@@
 	@Override
 	public void createTeamFromGroupAsync(String groupId) throws MicrosoftCredentialsException {
-		ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);
-		
 		AtomicInteger counter = new AtomicInteger(1);
 		final int MAX_RETRY = 5;
@@
 					try {
 						log.debug("Attempt number: {}", counter.get());
@@
-						//send message to (ignite) MicrosoftMessagingService (wait 30 sec before sending the message)
-						Thread.sleep(30000);
-						microsoftMessagingService.send(MicrosoftMessage.Topic.TEAM_CREATION, builder.status(1).build());
+						// send message to (ignite) MicrosoftMessagingService (wait 30 sec before sending the message)
+						schedulingService.schedule(
+							() -> microsoftMessagingService.send(MicrosoftMessage.Topic.TEAM_CREATION, builder.status(1).build()),
+							30,
+							TimeUnit.SECONDS
+						);
 					} catch(MicrosoftCredentialsException e) {
-						log.error("Error creating Team (credentials): " + e.getMessage());
+						log.error("Error creating Team (credentials): {}", e.getMessage());
 						//send message to (ignite) MicrosoftMessagingService
 						microsoftMessagingService.send(MicrosoftMessage.Topic.TEAM_CREATION, builder.status(0).build());
@@
 						else {
-							log.error("Error creating Team: " + e.getMessage());
+							log.error("Error creating Team: {}", e.getMessage());
 							//send message to (ignite) MicrosoftMessagingService
 							microsoftMessagingService.send(MicrosoftMessage.Topic.TEAM_CREATION, builder.status(0).build());
 						}

188-196: Add SchedulingService validation to init() method to fail fast on missing dependency.

The field uses setter injection without null checks. Both calls to schedulingService.schedule() (lines 981, 993) will NPE if Spring wiring misses the setter. Validate in init() before the class is used in production.

Additionally, fix logging violations on lines 974 and 984 to use parameterized logging instead of string concatenation:

  • Line 974: log.error("Error creating Team (credentials): {}", e.getMessage());
  • Line 984: log.error("Error creating Team: {}", e.getMessage());
webapi/src/main/java/org/sakaiproject/webapi/controllers/NotificationsController.java (1)

26-46: Backend response shape is incompatible with frontend—missing id field and field name mismatch will break clear and photo functionality.

The UserNotificationTransferBean does not provide the required fields:

  1. Missing id field — Frontend requires notification.id (line 249) to match and remove notifications on clear; transfer bean has no id.
  2. Field name mismatch — Frontend expects fromUser (line 401 for user photo), but bean provides from.
  3. Missing body field — Frontend uses noti.body for message display, not present in transfer bean.

Ensure UserNotificationTransferBean includes:

  • A stable id field for clear endpoint matching
  • fromUser field (or expose from with correct field name) for the photo component
  • body field if displayed notifications need message content
webcomponents/tool/src/main/frontend/packages/sakai-notifications/src/SakaiNotifications.js (1)

142-151: API contract mismatch: transfer bean uses from (not fromUser) and omits id entirely.

The UserNotificationTransferBean provides a from field (not fromUser) and lacks an id field entirely. The frontend will fail at line 401 when trying to access noti.fromUser for the user photo, and at lines 249 and 408 when attempting to use noti.id to clear notifications. The backend entity has the id, but UserNotificationTransferBean.of() never maps it. Update the transfer bean to include the id field and verify the frontend uses the correct field names (from instead of fromUser, and the newly added id).

kernel/kernel-impl/src/main/java/org/sakaiproject/authz/impl/AuthzGroupServiceTest.java (1)

31-59: Replace null return with a no-op SchedulingService implementation to prevent NPE in tests.

DbAuthzGroupService.init() calls schedulingService().scheduleWithFixedDelay(). If init() is invoked during testing, the null return from AuthzGroupServiceTest.schedulingService() will cause a NullPointerException. Either provide a minimal no-op mock or a fake implementation that safely ignores scheduling calls.

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

114-205: Fix SecurityAdvisor push/pop pairing in worker threads (can pop without push).

Right now, if anything throws before Line 164 (e.g., converter returns null at Line 157-159), the finally still calls popAdvisor, which can corrupt the advisor stack for that thread.

Proposed fix (pair push/pop at the top of the worker task)
                     try {
                         final ContentResource source = contentHostingService.getResource(ref);

                         workers.submit(() -> {
+                            securityService.pushAdvisor(securityAdvisor);
+                            try {

                             // This should update the item to IN_PROGRESS and flush the change so
                             // that other workers can see it. This is the mutex, in effect.
                             transactionTemplate.executeWithoutResult(status -> {
                                 log.debug("Setting item with ref {} to IN_PROGRESS ...", ref);

                                 repository.findById(item.getId()).ifPresent(inProgressItem -> {
                                     inProgressItem.setStatus(FileConversionQueueItem.Status.IN_PROGRESS);
                                     inProgressItem.setAttempts(inProgressItem.getAttempts() + 1);
                                     inProgressItem.setLastAttemptStarted(Instant.now());
                                     repository.save(inProgressItem);
                                 });
                             });

                             try {
                                 String convertedFileName = FilenameUtils.getBaseName(ref) + ".pdf";
                                 String fullPath = FilenameUtils.getPath(ref);
                                 String[] splitPath = fullPath.split("/");
                                 String siteId = splitPath[1];
                                 log.debug("Converted file id: {}, site id: {} ", convertedFileName, siteId);

                                 byte[] convertedFileBytes = LoolFileConverter.convert(converterBaseUrl, source.streamContent());
                                 
                                 if (convertedFileBytes == null) {
                                     throw new RuntimeException("File conversion failed - no bytes returned from converter");
                                 }

                                 ResourcePropertiesEdit properties = contentHostingService.newResourceProperties();
                                 properties.addProperty(ResourceProperties.PROP_DISPLAY_NAME, convertedFileName);

-                                securityService.pushAdvisor(securityAdvisor);
                                 ContentResource previewResource
                                         = contentHostingService.addAttachmentResource(convertedFileName, siteId, ContentHostingService.PREVIEW, ContentHostingService.PDF_MIMETYPE, convertedFileBytes, properties);

                                 contentHostingService.addProperty(ref, ContentHostingService.PREVIEW, previewResource.getId());

                                 log.debug("Deleting item with ref {}. It's been successfully converted.", ref);

                                 transactionTemplate.executeWithoutResult(status -> repository.deleteById(item.getId()));
                             } catch (Exception e) {
                                 transactionTemplate.executeWithoutResult(status -> {
                                     repository.findById(item.getId()).ifPresent(failedItem -> {
                                         if (failedItem.getAttempts() > maxAttemptsAllowed) {
                                             // Too many attempts. Do not try again.
                                             failedItem.setStatus(FileConversionQueueItem.Status.FAILED);
                                         } else {
                                             failedItem.setStatus(FileConversionQueueItem.Status.NOT_STARTED);
                                         }
                                         repository.save(failedItem);
                                     });
                                 });
                                 log.error("Call to conversion service failed", e);
-                            } finally {
-                                securityService.popAdvisor(securityAdvisor);
-                            }
+                            } finally {
+                                securityService.popAdvisor(securityAdvisor);
+                            }
+                            } catch (Exception e) {
+                                log.error("Worker thread failed unexpectedly for ref {}", ref, e);
+                            } finally {
+                                securityService.popAdvisor(securityAdvisor);
+                            }
                         });
                     } catch (IdUnusedException iue) {
                         log.error("No resource found for ref {}", ref);

172-183: Off-by-one on max attempts (> should likely be >=).

Because attempts is incremented when setting IN_PROGRESS (Line 142), > permits one extra try beyond maxAttemptsAllowed.

Proposed fix
-                                        if (failedItem.getAttempts() > maxAttemptsAllowed) {
+                                        if (failedItem.getAttempts() >= maxAttemptsAllowed) {
                                             // Too many attempts. Do not try again.
                                             failedItem.setStatus(FileConversionQueueItem.Status.FAILED);
                                         } else {
                                             failedItem.setStatus(FileConversionQueueItem.Status.NOT_STARTED);
                                         }

208-211: Guard destroy() against workers == null (conversion disabled).

If fileconversion.conversion.enabled=false, workers is never created and shutdown will NPE during container teardown.

Proposed fix
     public void destroy() {
-
-        workers.shutdownNow();
+        if (workers != null) {
+            workers.shutdownNow();
+        }
     }
samigo/samigo-app/src/java/org/sakaiproject/tool/assessment/ui/queue/delivery/TimedAssessmentQueue.java (1)

91-98: Potential NullPointerException in remove() method.

If remove() is called with an ID that doesn't exist in tasks, tasks.get(timedAG) returns null, and calling .cancel(true) will throw an NPE.

🐛 Proposed null-safe fix
   public void remove(long timedAG){
     log.info("SAMIGO_TIMED_ASSESSMENT:QUEUE:REMOVE ID:" + timedAG);
     // Stop the task and remove it
-    tasks.get(timedAG).cancel(true);
-    tasks.remove(timedAG);
+    ScheduledFuture<?> task = tasks.remove(timedAG);
+    if (task != null) {
+      task.cancel(true);
+    }
     // Remove the grading data from the queue
     queue.remove(timedAG);
   }
kernel/api/src/main/java/org/sakaiproject/messaging/api/model/UserNotification.java (1)

70-91: Database migration scripts are missing for new schema columns.

New columns END_DATE and BROADCAST have been added to the USER_NOTIFICATIONS table, and the URL column is now nullable. These changes require database migration scripts for existing deployments. Per Sakai conventions (as noted in SAK-51713), database upgrade scripts should be placed in the sakai-reference repository under docs/conversion, following the pattern of other version migration scripts (e.g., sakai_25_1-25_2_mysql_conversion.sql).

🤖 Fix all issues with AI agents
In
@announcement/announcement-api/api/src/java/org/sakaiproject/announcement/api/AnnouncementsUserNotificationHandler.java:
- Around line 124-134: In the AnnouncementsUserNotificationHandler MOTD_NEW
branch, add a null check for the local variable message before calling
message.getHeader() to avoid a NullPointerException; if message is null, return
Optional.empty() (or handle as other branches do) and only construct
UserNotificationData when message != null (this affects the block guarded by
eventName.equals(AnnouncementService.EVENT_MOTD_NEW) and the use of
((AnnouncementMessageHeader) message.getHeader())).

In
@kernel/kernel-impl/src/main/java/org/sakaiproject/messaging/impl/UserMessagingServiceImpl.java:
- Around line 586-610: The pushToAllUsers method uses inefficient paging and
wrong termination logic; fix it by using a larger pageSize (e.g., 50–200)
instead of 1, replace the while (page.getTotalElements() > 0) check with a
proper pagination loop that breaks when page.getContent() is empty or uses
page.hasNext(), and iterate pages via PageRequest.of(pageNumber, pageSize)
updating pageNumber each loop; also replace the Map done with a Set<String>
doneUsers and check/set doneUsers.contains(userId) before calling push(bean) so
users with multiple subscriptions across pages are de-duplicated.

In @plus/impl/src/main/java/org/sakaiproject/plus/impl/PlusServiceImpl.java:
- Around line 105-106: The iteration over refreshQueue in PlusServiceImpl is not
synchronized, risking ConcurrentModificationException that can abort the
scheduled refresh task; wrap the iteration and removal logic in a
synchronized(refreshQueue) block and consume entries one-at-a-time (use an
iterator over refreshQueue.values() and call iterator.remove() after processing
each entry) instead of copying the collection, ensuring all accesses that
iterate or modify refreshQueue are protected by the same lock so the scheduled
membership refresh task cannot be terminated by concurrent put/remove.

In
@samigo/samigo-app/src/java/org/sakaiproject/tool/assessment/ui/queue/delivery/TimedAssessmentQueue.java:
- Around line 44-54: The field tasks is a non-thread-safe HashMap but is
accessed concurrently; change its declaration to a ConcurrentHashMap with
generics (private ConcurrentHashMap<Long, ScheduledFuture<?>> tasks) and
initialize it in the TimedAssessmentQueue constructor with new
ConcurrentHashMap<>(); also replace the raw ConcurrentHashMap usage for queue
with a properly parameterized type (use the actual key/value types used by queue
elsewhere, e.g. ConcurrentHashMap<Long, <Type>> queue = new
ConcurrentHashMap<>()) so both fields are thread-safe and type-safe for add()
and remove() operations.
🧹 Nitpick comments (10)
webcomponents/tool/src/main/frontend/packages/sakai-notifications/src/SakaiNotifications.js (2)

148-150: Prefer an if over && for Map initialization readability.

The short-circuit form is correct, but it’s a bit opaque in review/maintenance.

Proposed tweak
-      !this._filteredNotifications.has(toolEventPrefix) && this._filteredNotifications.set(toolEventPrefix, []);
+      if (!this._filteredNotifications.has(toolEventPrefix)) {
+        this._filteredNotifications.set(toolEventPrefix, []);
+      }

404-422: Close button suppression for MOTD looks consistent; confirm intended behavior for broadcasts too.

Hiding the close/link affordances for prefix === "motd" is coherent. If “broadcast” notifications are also meant to be non-dismissable, consider gating on noti.broadcast rather than (or in addition to) the prefix.

config/configuration/bundles/src/bundle/org/sakaiproject/config/bundle/default.sakai.properties (1)

2258-2261: Tighten the TTL comment wording to match behavior (expiry vs cleanup).

“lives … before being cleaned up” implies immediate deletion at TTL; if actual behavior is “expires at TTL and is later deleted by a scheduled cleanup”, consider reflecting that in the comment to reduce operator confusion.

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

110-137: Consider extracting a factory method to centralize UserNotificationData defaults.

The pattern new UserNotificationData(..., false, null) appears 10 times across 6 handler classes in the codebase (including 3 instances in this file). A static factory method like UserNotificationData.createStandardNotification(from, to, siteId, title, url, toolId) would reduce duplication, improve readability, and serve as a single point of maintenance for these defaults.

msgcntr/messageforums-api/src/java/org/sakaiproject/api/app/messageforums/PrivateMessageUserNotificationHandler.java (1)

84-106: Use a factory method or overload constructor to clarify the broadcast and ttl parameters.

The false, null at the end of the constructor call are not self-documenting. Checking the class definition shows these correspond to broadcast=false and ttl=null, but this is not obvious at the call site. All handler implementations repeat the same pattern, making it error-prone.

Consider adding a factory method such as:

public static UserNotificationData createNotification(String from, String to, String siteId, String title, String url, String commonToolId) {
    return new UserNotificationData(from, to, siteId, title, url, commonToolId, false, null);
}

Or an overloaded constructor with defaults, to eliminate the ambiguous positional parameters.

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

61-112: Consistent false, null pattern across all notification handlers suggests using a static factory method or builder for clarity.

Every handler (AnnouncementsUserNotificationHandler, AddAssignmentUserNotificationHandler, CommonsCommentUserNotificationHandler, and others) uses the same pattern: new UserNotificationData(..., false, null) where false is the broadcast flag and null is the TTL duration. A static factory method like UserNotificationData.createUserNotification(from, to, siteId, title, url, toolId) would eliminate these magic values and make the intent self-documenting.

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

22-55: Consider adding Lombok annotations for consistency.

The implementation is functionally correct with proper null-safe Boolean handling. However, consider adding @Data or at minimum @ToString and @EqualsAndHashCode for debugging and potential use in collections, which aligns with other beans in the codebase (e.g., UserNotificationData).

♻️ Optional Lombok enhancement
 package org.sakaiproject.messaging.api;
 
 import org.sakaiproject.messaging.api.model.UserNotification;
 
 import java.time.Instant;
 
+import lombok.Data;
+
+@Data
 public class UserNotificationTransferBean {
kernel/kernel-impl/src/main/java/org/sakaiproject/scheduling/impl/SchedulingServiceImpl.java (1)

33-43: Add pool size validation and lifecycle logging for observability.

The implementation is functional, but consider:

  1. Validating pool size to avoid IllegalArgumentException for non-positive values
  2. Adding logging for lifecycle events to aid troubleshooting
♻️ Proposed enhancements
+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
 public class SchedulingServiceImpl implements SchedulingService {
 
     @Autowired private ServerConfigurationService serverConfigurationService;
 
     private ScheduledThreadPoolExecutor executor;
 
     public void init() {
 
         int poolSize = serverConfigurationService.getInt("schedulingservice.poolsize", 8);
+        if (poolSize <= 0) {
+            log.warn("Invalid schedulingservice.poolsize: {}. Defaulting to 8", poolSize);
+            poolSize = 8;
+        }
         executor = new ScheduledThreadPoolExecutor(poolSize);
         executor.setRemoveOnCancelPolicy(true);
+        log.info("SchedulingService initialized with pool size: {}", poolSize);
     }
 
     public void destroy() {
-
+        log.info("SchedulingService shutting down");
         executor.shutdownNow();
     }
kernel/kernel-impl/src/main/java/org/sakaiproject/messaging/impl/UserMessagingServiceImpl.java (1)

500-502: Expired broadcasts may be returned between cleanup cycles.

findByBroadcast(true) may return broadcasts where endDate < now since deleteExpiredNotifications only runs every 30 minutes. Consider filtering in the query or adding a stream filter.

♻️ Optional: Add expiry filter
         List<UserNotificationTransferBean> beans = Stream.concat(userNotificationRepository.findByToUser(userId).stream(),
-                userNotificationRepository.findByBroadcast(true).stream())
+                userNotificationRepository.findByBroadcast(true).stream()
+                    .filter(un -> un.getEndDate() == null || un.getEndDate().isAfter(Instant.now())))
             .map(UserNotificationTransferBean::of).map(this::decorateNotification).collect(Collectors.toList());

Alternatively, add a repository method findByBroadcastAndEndDateAfterOrEndDateIsNull(true, Instant.now()).

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

85-88: Consider adding explicit private modifier for clarity.

The fields lack explicit access modifiers, defaulting to package-private visibility. While the generated public setters (via @Setter) are appropriate for dependency injection, explicitly declaring the fields as private would better align with Java encapsulation conventions and improve code clarity.

♻️ Proposed refactor
-    @Setter SchedulingService schedulingService;
-    @Setter ServerConfigurationService serverConfigurationService;
-    @Setter HibernateConfigItemDao dao;
-    @Setter PBEStringEncryptor textEncryptor;
+    @Setter private SchedulingService schedulingService;
+    @Setter private ServerConfigurationService serverConfigurationService;
+    @Setter private HibernateConfigItemDao dao;
+    @Setter private PBEStringEncryptor textEncryptor;
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2a4d6ae and f5458b2.

📒 Files selected for processing (41)
  • 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/RuntimeRegistrationTest.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-app/src/java/org/sakaiproject/tool/assessment/ui/queue/delivery/TimedAssessmentQueue.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 with no reviewable changes (1)
  • kernel/kernel-impl/src/test/java/org/sakaiproject/messaging/impl/RuntimeRegistrationTest.java
🚧 Files skipped from review as they are similar to previous changes (14)
  • kernel/kernel-impl/src/main/java/org/sakaiproject/event/impl/EventTrackingTest.java
  • kernel/kernel-impl/src/main/webapp/WEB-INF/event-components.xml
  • kernel/kernel-impl/src/test/java/org/sakaiproject/event/impl/test/LearningResourceTestConfiguration.java
  • kernel/api/src/main/java/org/sakaiproject/messaging/api/repository/UserNotificationRepository.java
  • kernel/kernel-impl/src/main/webapp/WEB-INF/authz-components.xml
  • plus/impl/src/main/webapp/WEB-INF/components.xml
  • commons/api/src/java/org/sakaiproject/commons/api/CommonsCommentUserNotificationHandler.java
  • 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/test/java/org/sakaiproject/content/impl/test/FileConversionServiceTestConfiguration.java
  • plus/impl/src/test/org/sakaiproject/plus/impl/PlusTestConfiguration.java
  • kernel/api/src/main/java/org/sakaiproject/scheduling/api/SchedulingService.java
  • lessonbuilder/components/src/java/org/sakaiproject/lessonbuildertool/impl/AddLessonsCommentUserNotificationHandlerImpl.java
  • samigo/samigo-services/src/java/org/sakaiproject/tool/assessment/TestsAndQuizzesUserNotificationHandler.java
🧰 Additional context used
📓 Path-based instructions (10)
**/*.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/authz/impl/AuthzGroupServiceTest.java
  • kernel/kernel-impl/src/test/java/org/sakaiproject/messaging/impl/UserMessagingServiceTests.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
  • announcement/announcement-api/api/src/java/org/sakaiproject/announcement/api/AnnouncementsUserNotificationHandler.java
  • kernel/api/src/main/java/org/sakaiproject/messaging/api/UserNotificationData.java
  • samigo/samigo-app/src/java/org/sakaiproject/tool/assessment/ui/queue/delivery/TimedAssessmentQueue.java
  • kernel/kernel-impl/src/main/java/org/sakaiproject/scheduling/impl/SchedulingServiceImpl.java
  • kernel/kernel-impl/src/test/java/org/sakaiproject/messaging/impl/UserMessagingServiceTestConfiguration.java
  • webapi/src/main/java/org/sakaiproject/webapi/controllers/NotificationsController.java
  • assignment/api/src/java/org/sakaiproject/assignment/api/AddAssignmentUserNotificationHandler.java
  • kernel/kernel-impl/src/main/java/org/sakaiproject/messaging/impl/UserMessagingServiceImpl.java
  • kernel/kernel-impl/src/main/java/org/sakaiproject/content/impl/FileConversionServiceImpl.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
  • assignment/api/src/java/org/sakaiproject/assignment/api/GradeAssignmentUserNotificationHandler.java
  • kernel/api/src/main/java/org/sakaiproject/messaging/api/model/UserNotification.java
  • kernel/api/src/main/java/org/sakaiproject/messaging/api/UserNotificationTransferBean.java
  • kernel/api/src/main/java/org/sakaiproject/messaging/api/UserMessagingService.java
  • msgcntr/messageforums-api/src/java/org/sakaiproject/api/app/messageforums/PrivateMessageUserNotificationHandler.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/configstore-components.xml
  • kernel/kernel-impl/src/main/webapp/WEB-INF/components.xml
  • kernel/kernel-impl/src/main/webapp/WEB-INF/scheduling-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/configstore-components.xml
  • kernel/kernel-impl/src/main/webapp/WEB-INF/components.xml
  • kernel/kernel-impl/src/main/webapp/WEB-INF/scheduling-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/configstore-components.xml
  • kernel/kernel-impl/src/main/webapp/WEB-INF/components.xml
  • kernel/kernel-impl/src/main/webapp/WEB-INF/scheduling-components.xml
  • webcomponents/tool/src/main/frontend/packages/sakai-notifications/src/SakaiNotifications.js
**/*.{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/configstore-components.xml
  • kernel/kernel-impl/src/main/webapp/WEB-INF/components.xml
  • kernel/kernel-impl/src/main/webapp/WEB-INF/scheduling-components.xml
  • 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
**/*.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
🧠 Learnings (17)
📓 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/test/java/org/sakaiproject/messaging/impl/UserMessagingServiceTests.java
  • announcement/announcement-api/api/src/java/org/sakaiproject/announcement/api/AnnouncementsUserNotificationHandler.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-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
  • 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 **/*.{java,properties} : Reconcile push notification subscriptions proactively and keep localization strings in `sakai-notifications.properties`

Applied to files:

  • kernel/kernel-impl/src/test/java/org/sakaiproject/messaging/impl/UserMessagingServiceTests.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
  • samigo/samigo-app/src/java/org/sakaiproject/tool/assessment/ui/queue/delivery/TimedAssessmentQueue.java
  • kernel/kernel-impl/src/main/java/org/sakaiproject/messaging/impl/UserMessagingServiceImpl.java
  • webcomponents/tool/src/main/frontend/packages/sakai-notifications/src/SakaiNotifications.js
  • kernel/api/src/main/java/org/sakaiproject/messaging/api/model/UserNotification.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 **/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
📚 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/test/java/org/sakaiproject/messaging/impl/UserMessagingServiceTests.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
  • samigo/samigo-app/src/java/org/sakaiproject/tool/assessment/ui/queue/delivery/TimedAssessmentQueue.java
  • kernel/kernel-impl/src/main/java/org/sakaiproject/messaging/impl/UserMessagingServiceImpl.java
  • webcomponents/tool/src/main/frontend/packages/sakai-notifications/src/SakaiNotifications.js
  • kernel/api/src/main/java/org/sakaiproject/messaging/api/model/UserNotification.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
  • 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:

  • config/configuration/bundles/src/bundle/org/sakaiproject/config/bundle/default.sakai.properties
  • kernel/kernel-impl/src/main/java/org/sakaiproject/messaging/impl/UserMessagingServiceImpl.java
  • webcomponents/tool/src/main/frontend/packages/sakai-notifications/src/SakaiNotifications.js
📚 Learning: 2025-11-24T19:11:42.092Z
Learnt from: CR
Repo: sakaiproject/sakai PR: 0
File: .cursor/rules/sakai-rules.md:0-0
Timestamp: 2025-11-24T19:11:42.092Z
Learning: Applies to **/*.java : Treat any PR or suggestion containing Java `var` as non-compliant. Recommend replacing with explicit types before merge

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 **/*.java : 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`

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 **/*.java : Do not use local variable type inference (`var`) in Java code; always declare explicit types (e.g., `List<String> names = new ArrayList<>();`)

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 **/*.java : Do not use local variable type inference (var) in Java; always declare explicit types

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
📚 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
📚 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-push-utils.js : Handle browser-specific Web Push logic in sakai-push-utils.js

Applied to files:

  • kernel/kernel-impl/src/main/java/org/sakaiproject/messaging/impl/UserMessagingServiceImpl.java
  • webcomponents/tool/src/main/frontend/packages/sakai-notifications/src/SakaiNotifications.js
📚 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-12-10T10:41:09.339Z
Learnt from: st-manu
Repo: sakaiproject/sakai PR: 14256
File: kernel/kernel-impl/src/main/sql/mysql/sakai_realm.sql:734-734
Timestamp: 2025-12-10T10:41:09.339Z
Learning: In Sakai, DB upgrade/backfill for new realm functions and grants belongs in the sakai-reference repo under docs/conversion (e.g., sakai_25_1-25_2_*_conversion.sql). For SAK-51713, assessment.all.groups is granted to maintain and Instructor and backfilled across existing realms via conversion scripts.

Applied to files:

  • kernel/api/src/main/java/org/sakaiproject/messaging/api/model/UserNotification.java
🧬 Code graph analysis (2)
kernel/api/src/main/java/org/sakaiproject/messaging/api/UserNotificationData.java (1)
kernel/kernel-impl/src/main/java/org/sakaiproject/authz/impl/DbAuthzGroupService.java (1)
  • Data (3382-3396)
webapi/src/main/java/org/sakaiproject/webapi/controllers/NotificationsController.java (1)
kernel/api/src/main/java/org/sakaiproject/messaging/api/UserNotificationTransferBean.java (1)
  • UserNotificationTransferBean (22-56)
🔇 Additional comments (19)
announcement/announcement-api/api/src/java/org/sakaiproject/announcement/api/AnnouncementsUserNotificationHandler.java (5)

18-60: Imports are appropriate for the new functionality.

The added imports (Duration, PostConstruct, Predicate, Member) are all utilized by the new MOTD broadcast handling, role-based filtering, and the buildWhere helper method.


87-94: Initialization looks good.

The configuration property announcement.motd.notification.ttlhours with a sensible default of 24 hours is properly loaded via @PostConstruct. The constant SELECTED_ROLES_PROPERTY improves readability for role-based filtering.


144-170: Transaction and bulk delete handling looks correct.

The pattern of querying first to flush the persistence context, detaching the results, and then performing a bulk delete is appropriate to avoid stale entity issues. The TransactionTemplate ensures atomicity.


205-224: Role-based filtering and notification creation are well-implemented.

The new role-filtering logic correctly respects SELECTED_ROLES_PROPERTY when defined, and the final stream properly excludes the event originator and superusers. The UserNotificationData constructor call with explicit false for broadcast and null for TTL correctly distinguishes these from the MOTD broadcast path.


240-249: Good extraction of the query predicate logic.

The buildWhere helper improves maintainability by centralizing the criteria logic used in both the query and delete operations, ensuring consistency and reducing duplication.

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

37-41: LGTM: scheduling components imported into kernel context.

This is the expected wiring hook for the new SchedulingService.

kernel/kernel-impl/src/test/java/org/sakaiproject/messaging/impl/UserMessagingServiceTestConfiguration.java (2)

87-90: Good: test-time SchedulingService bean unblocks Spring wiring.

This keeps the unit test context from needing a real scheduler.


92-97: No action needed. The UserMessagingServiceTestConfiguration is shared across multiple test classes (RuntimeRegistrationTest and UserMessagingServiceTests), so the mock setup is correctly placed in the configuration class per project guidelines.

config/configuration/bundles/src/bundle/org/sakaiproject/config/bundle/default.sakai.properties (1)

5774-5785: Clarify the "expired notifications" comment to specify it refers to cleanup deletion cadence.

The current phrasing "delay period between expired notifications" is ambiguous. Consider: "Delay between cleanup runs that delete expired notifications (minutes)" for clarity. Verify that the declared defaults in this file (e.g., messaging.delete.expired.periodminutes=30) match the actual code defaults to prevent configuration drift.

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

58-60: SchedulingService bean is always available—the original concern is unfounded.

The scheduling-components.xml file is unconditionally imported in the main components.xml (line 40), making the SchedulingService bean always present in the Spring context. There is no deploy profile scenario where this wiring would be missing.

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

63-66: API change successfully propagated across all callers.

The migration from List<UserNotification> to List<UserNotificationTransferBean> is complete:

  • Single implementation (UserMessagingServiceImpl) updated
  • REST controllers (NotificationsController, SitesController) updated
  • Tests passing with correct return type
  • No remaining compilation breaks

No further action needed.

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

41-49: No action required. The StoredConfigService bean configuration is correct:

  1. Setter exists: The @Setter Lombok annotation on line 85 of StoredConfigService.java generates the setSchedulingService() method at compile-time. Spring will successfully inject the SchedulingService dependency.

  2. Cleanup is handled: StoredConfigService has no cleanup responsibilities because it only uses the injected SchedulingService to schedule tasks—it does not own the resource. The SchedulingService bean itself defines a destroy() method in SchedulingServiceImpl (line 40), which Spring will invoke automatically during shutdown. Removing destroy-method from StoredConfigService is correct.

Likely an incorrect or invalid review comment.

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

1-12: SchedulingService bean wiring looks correct.

Interface-as-bean-id with init/destroy hooks matches the intended container-managed scheduler approach.

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

44-45: Restore original thread name in finally block to avoid polluting shared scheduler pool thread names.

The shared ScheduledThreadPoolExecutor (pool size 8 by default) reuses threads across multiple scheduled tasks. Setting Thread.currentThread().setName(this.getClass().getName()) at line 518 without restoration causes the thread name "org.sakaiproject.event.impl.ClusterEventTracking" to persist for all subsequent tasks executed on that thread, degrading observability across unrelated scheduled work.

Proposed fix
 public void run()
 {
+	String originalThreadName = Thread.currentThread().getName();
 	try
 	{
 		Thread.currentThread().setName(this.getClass().getName());
 
 		// wait for sakai's ComponentManager to finish starting before processing events
 		ComponentManager.waitTillConfigured();
 		log.debug("checking for events > {}", m_lastEventSeq);
 		// ... event processing logic ...
 	}
-	catch (Throwable t)
-	{
+	catch (Throwable t) {
 		log.error("{}error during execution {}", m_logId, t.getMessage(), t);
+	} finally {
+		Thread.currentThread().setName(originalThreadName);
 	}
 }

Applies to lines 514–646 (run method) and the thread naming pattern throughout the scheduled execution lifecycle.

Likely an incorrect or invalid review comment.

kernel/kernel-impl/src/test/java/org/sakaiproject/messaging/impl/UserMessagingServiceTests.java (1)

172-172: LGTM!

The constructor call correctly passes the two new parameters (broadcast = false, ttl = null) matching the updated UserNotificationData signature.

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

18-36: LGTM!

Clean additions for broadcast notification support:

  • Duration ttl is appropriate for time-to-live representation
  • Lombok annotations (@Data, @NoArgsConstructor, @AllArgsConstructor) correctly provide the necessary boilerplate
kernel/kernel-impl/src/main/java/org/sakaiproject/messaging/impl/UserMessagingServiceImpl.java (2)

158-178: LGTM!

Good implementation:

  • Thread pool size is validated with a sensible default
  • Scheduled cleanup of expired notifications is a good reliability pattern
  • Proper lifecycle management with configurable period

655-672: Good HTTP status code handling.

The switch-case properly categorizes push failures:

  • 400/404/410: Invalid subscription cleanup
  • 403: VAPID configuration issues
  • 429: Rate limiting awareness

This improves reliability by proactively cleaning up stale subscriptions.

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

129-139: LGTM! SchedulingService wiring and cleanup are properly configured.

The migration from a local ScheduledExecutorService to SchedulingService is correctly implemented. The anonymous Runnable pattern safely maintains state via the pollDate instance field, which is updated on each invocation.

Verified:

  1. The SchedulingService bean is properly wired in configstore-components.xml (line 46)
  2. The SchedulingService handles task cleanup on shutdown via the destroy() method in SchedulingServiceImpl, which is invoked automatically by Spring

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

Caution

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

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

947-947: Remove dead code that creates an unused thread pool.

Line 947 creates a ScheduledExecutorService that is never used after the migration to SchedulingService. This is dead code and represents a potential resource leak if the code path is executed.

🗑️ Remove unused executor creation
-        ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);
-        
         AtomicInteger counter = new AtomicInteger(1);
kernel/kernel-impl/src/main/java/org/sakaiproject/content/impl/FileConversionServiceImpl.java (1)

208-211: Potential NullPointerException if conversion is disabled.

If conversionEnabled is false in init(), the workers field will be null, and destroy() will throw an NPE when calling workers.shutdownNow().

Suggested fix
 public void destroy() {
 
-    workers.shutdownNow();
+    if (workers != null) {
+        workers.shutdownNow();
+    }
 }
🤖 Fix all issues with AI agents
In
@kernel/kernel-impl/src/main/java/org/sakaiproject/messaging/impl/repository/UserNotificationRepositoryImpl.java:
- Around line 52-77: findByBroadcast and deleteExpiredNotifications currently
assume endDate is non-null; change both to explicitly handle nullable endDate by
using CriteriaBuilder predicates: in findByBroadcast (method findByBroadcast)
replace the single cb.greaterThan(un.get("endDate"), now) with a predicate that
treats null as "no expiry" (e.g., cb.or(un.get("endDate").isNull(),
cb.greaterThan(un.get("endDate"), now))) so null endDate rows are included, and
in deleteExpiredNotifications (method deleteExpiredNotifications) ensure the
delete only matches non-null expired rows by adding
cb.isNotNull(un.get("endDate")) together with cb.lessThan(un.get("endDate"),
now); use the existing CriteriaBuilder, Root<UserNotification> un and Instant
now variables to build those combined predicates.

In
@kernel/kernel-impl/src/main/java/org/sakaiproject/messaging/impl/UserMessagingServiceImpl.java:
- Around line 584-612: In pushToAllUsers, remove the System.out.println(userId)
debug output, replace the Map<String,String> done with a Set<String> done = new
HashSet<>(), and fix pagination by using a larger pageSize (e.g., 100 or
configurable) and iterate pages using page =
pushSubscriptionRepository.findAll(PageRequest.of(current, pageSize)) while
checking page.isEmpty() (or page.hasContent()) rather than
page.getTotalElements() to avoid repeated total queries; keep setting bean.to =
ps.getUserId() and calling push(bean) only when done.add(userId) returns true.

In
@kernel/kernel-impl/src/main/java/org/sakaiproject/scheduling/impl/SchedulingServiceImpl.java:
- Around line 29-43: The init/destroy lifecycle is unsafe:
ScheduledThreadPoolExecutor will throw if given pool size ≤ 0 and destroy() will
NPE if init() never ran; validate and coerce the configured pool size to a
minimum of 1 in init() before calling new ScheduledThreadPoolExecutor(poolSize),
and in destroy() add a null-check on the executor (shutdownNow only if executor
!= null); additionally add defensive checks in public scheduling methods to
throw a clear IllegalStateException if executor is null (refer to init(),
destroy(), and the executor field) so callers fail fast with a helpful message
rather than NPEs.

In
@samigo/samigo-app/src/java/org/sakaiproject/tool/assessment/ui/queue/delivery/TimedAssessmentQueue.java:
- Around line 42-54: The tasks map is a plain HashMap and remove(long) assumes
presence, risking concurrency issues and NPEs; change tasks to a thread-safe Map
type (e.g., ConcurrentHashMap<Long, ScheduledFuture<?>>) in the
TimedAssessmentQueue constructor and field declaration, and update add/remove
code paths to handle absent returns safely (check the result of
tasks.remove(key) for null before calling cancel() or similar). Also replace
non-parameterized log calls with SLF4J parameterized logging and pass exceptions
as the throwable argument (e.g., logger.error("msg {}", id, ex)). Ensure all
references to tasks, the TimedAssessmentQueue constructor, and the
remove/removeTask methods are updated accordingly.
🧹 Nitpick comments (10)
kernel/kernel-impl/src/test/java/org/sakaiproject/event/impl/test/LearningResourceTestConfiguration.java (1)

45-46: Mock SchedulingService may need default stubbing to avoid null-return NPEs

Adding the bean is fine to satisfy the new dependency, but a plain Mockito mock will return null for schedule*() calls, which can blow up if production code stores/inspects the returned ScheduledFuture. Consider stubbing to return a mock ScheduledFuture<?> (or making it a no-op facade) if tests trigger scheduling. Based on learnings, also sanity-check that this mock is genuinely shared across multiple tests; otherwise keep it in the specific test class.

Also applies to: 200-203

kernel/kernel-impl/src/test/java/org/sakaiproject/content/impl/test/FileConversionServiceTestConfiguration.java (1)

29-30: Consider stubbing SchedulingService in tests if init() schedules work

If FileConversionServiceImpl.init() (Line 89-90) schedules anything via SchedulingService, the returned ScheduledFuture<?> being null can cause hard-to-diagnose failures. Consider stubbing schedule methods to return a mock future or using a lightweight in-memory scheduler for deterministic tests. Based on learnings, confirm this belongs in shared config vs the specific test(s).

Also applies to: 70-73

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

45-67: Fail fast (or synchronize) if scheduling is invoked before initialization

All schedule*() methods assume executor is non-null. If Spring wiring ever skips init() (or a consumer calls this too early), you’ll get a late NPE. Consider an explicit guard (IllegalStateException with a clear message) and optionally a named ThreadFactory to make thread dumps actionable.

Example guard pattern
     @Override
     public ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit) {
-
-        return executor.schedule(command, delay, unit);
+        ScheduledThreadPoolExecutor localExecutor = executor;
+        if (localExecutor == null) {
+            throw new IllegalStateException("SchedulingServiceImpl has not been initialized (executor is null)");
+        }
+        return localExecutor.schedule(command, delay, unit);
     }
kernel/api/src/main/java/org/sakaiproject/messaging/api/UserNotificationData.java (1)

18-37: Consider removing @ToString from the proposed change or exclude sensitive fields

The concern about @Data including user identifiers in generated methods is valid from a design perspective. However, the proposed replacement still uses @ToString, which will generate toString() including all fields—not actually addressing the core concern about accidentally logging identifiers.

Prefer one of these approaches:

  • @Getter @Setter @AllArgsConstructor @NoArgsConstructor (omit @ToString entirely)
  • Or explicitly exclude sensitive fields: @Getter @Setter @ToString(exclude = {"from", "to", "siteId", "url"}) @AllArgsConstructor @NoArgsConstructor

More targeted annotations improve clarity and are preferred over @Data, but be intentional about what @ToString exposes.

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

124-134: Consider using constructor for consistency and verify required fields.

Two observations:

  1. Inconsistent object construction: This code uses the empty constructor + setters pattern, while line 223 uses the full constructor. For consistency and immutability, consider using the constructor pattern here as well.

  2. Missing fields: The MOTD notification doesn't set context, url, or toolId. Please verify this is intentional. If these fields are required for proper notification display or routing, they should be populated.

♻️ Proposed refactor for consistency
-                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));
+                String title = ((AnnouncementMessageHeader) message.getHeader()).getSubject();
+                UserNotificationData userNotificationData = new UserNotificationData(
+                    event.getUserId(),
+                    "BROADCAST",
+                    null, // context - verify if needed
+                    title,
+                    null, // url - verify if needed
+                    null, // toolId - verify if needed
+                    true, // isBroadcast
+                    Duration.ofHours(motdTTLHours) // ttl
+                );
+                return Optional.of(List.of(userNotificationData));
microsoft-integration/impl/src/main/java/org/sakaiproject/microsoft/impl/MicrosoftCommonServiceImpl.java (1)

38-39: Remove unused imports.

After migrating to SchedulingService, the imports for Executors and ScheduledExecutorService are no longer used and should be removed.

♻️ Remove unused imports
-import java.util.concurrent.Executors;
-import java.util.concurrent.ScheduledExecutorService;
 import java.util.concurrent.TimeUnit;
plus/impl/src/main/java/org/sakaiproject/plus/impl/PlusServiceImpl.java (1)

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

As per the coding guidelines, use parameterized logging for log messages.

Suggested fix
-					log.error("refreshContextMembershipsTask.run() Problem refreshing context: " + contextGuid, e);
+					log.error("refreshContextMembershipsTask.run() Problem refreshing context: {}", contextGuid, e);
assignment/api/src/java/org/sakaiproject/assignment/api/AddAssignmentUserNotificationHandler.java (1)

129-131: Avoid repeated magic (false, null) in UserNotificationData construction

The three callsites hardcode the new trailing args, which obscures intent and invites accidental arg-order bugs.

Proposed refactor (local helper)
@@
 public class AddAssignmentUserNotificationHandler extends AbstractUserNotificationHandler {
 
+    private static UserNotificationData userNotification(String from, String to, String siteId, String title, String url) {
+        // Trailing args: keep named here so callsites stay readable if signature evolves again.
+        return new UserNotificationData(from, to, siteId, title, url, AssignmentConstants.TOOL_ID, false, null);
+    }
@@
-                        bhEvents.add(new UserNotificationData(from, to, siteId, title, url, AssignmentConstants.TOOL_ID, false, null));
+                        bhEvents.add(userNotification(from, to, siteId, title, url));
@@
-                    bhEvents.add(new UserNotificationData(from, to, siteId, title, url, AssignmentConstants.TOOL_ID, false, null));
+                    bhEvents.add(userNotification(from, to, siteId, title, url));
@@
-                    bhEvents.add(new UserNotificationData(from, to, siteId, title, url, AssignmentConstants.TOOL_ID, false, null));
+                    bhEvents.add(userNotification(from, to, siteId, title, url));

Also applies to: 166-168, 180-182

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

22-55: Clarify/contain API surface: public mutable fields + partially-populated factory

Since this lives in kernel/api, consider either:

  1. documenting that fromDisplayName/siteTitle/formattedEventDate are populated later (and by whom), or
  2. moving those decorated fields to a different bean to keep the transfer type stable.

Also consider null-safety in of(UserNotification un).

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

386-408: Remove redundant tool ID assignment.

The bean.tool assignment at line 399 is redundant and can be safely removed.

Here's why: doInsert() sets the tool on the persisted entity via ba.setTool(und.getCommonToolId()), and UserNotificationTransferBean.of() immediately copies that value into the bean via bean.tool = un.getTool(). The manual assignment at line 399 simply overwrites the bean's tool field with the same value it already has.

♻️ Suggested simplification
                                    = UserNotificationTransferBean.of(doInsert(from,
                                        und,
                                        event,
                                        ref,
                                        e.getEventTime(),
                                        finalDeferred)
                                    );

                                if (!finalDeferred) {
-                                    bean.tool = und.getCommonToolId();
-
                                    if (und.isBroadcast()) {
                                        pushToAllUsers(decorateNotification(bean));
                                    } else {
                                        push(decorateNotification(bean));
                                    }
                                }
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f5458b2 and bf13c22.

📒 Files selected for processing (41)
  • 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/RuntimeRegistrationTest.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-app/src/java/org/sakaiproject/tool/assessment/ui/queue/delivery/TimedAssessmentQueue.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 with no reviewable changes (1)
  • kernel/kernel-impl/src/test/java/org/sakaiproject/messaging/impl/RuntimeRegistrationTest.java
🚧 Files skipped from review as they are similar to previous changes (13)
  • kernel/api/src/main/java/org/sakaiproject/messaging/api/model/UserNotification.java
  • plus/impl/src/main/webapp/WEB-INF/components.xml
  • kernel/kernel-impl/src/main/webapp/WEB-INF/components.xml
  • kernel/kernel-impl/src/main/java/org/sakaiproject/event/impl/EventTrackingTest.java
  • kernel/kernel-impl/src/main/java/org/sakaiproject/event/impl/ClusterEventTracking.java
  • kernel/kernel-impl/src/main/webapp/WEB-INF/authz-components.xml
  • plus/impl/src/test/org/sakaiproject/plus/impl/PlusTestConfiguration.java
  • kernel/api/src/main/java/org/sakaiproject/scheduling/api/SchedulingService.java
  • webapi/src/main/java/org/sakaiproject/webapi/controllers/SitesController.java
  • commons/api/src/java/org/sakaiproject/commons/api/CommonsCommentUserNotificationHandler.java
  • kernel/kernel-impl/src/main/java/org/sakaiproject/authz/impl/AuthzGroupServiceTest.java
  • kernel/kernel-impl/src/main/webapp/WEB-INF/scheduling-components.xml
  • lessonbuilder/components/src/java/org/sakaiproject/lessonbuildertool/impl/AddLessonsCommentUserNotificationHandlerImpl.java
🧰 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/event-components.xml
  • kernel/kernel-impl/src/main/webapp/WEB-INF/configstore-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
**/*.{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
  • webcomponents/tool/src/main/frontend/packages/sakai-notifications/src/SakaiNotifications.js
**/*.{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
  • webcomponents/tool/src/main/frontend/packages/sakai-notifications/src/SakaiNotifications.js
**/*.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
  • microsoft-integration/impl/src/main/java/org/sakaiproject/microsoft/impl/MicrosoftCommonServiceImpl.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/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/scheduling/impl/SchedulingServiceImpl.java
  • assignment/api/src/java/org/sakaiproject/assignment/api/AddAssignmentUserNotificationHandler.java
  • kernel/kernel-impl/src/test/java/org/sakaiproject/messaging/impl/UserMessagingServiceTestConfiguration.java
  • kernel/kernel-impl/src/main/java/org/sakaiproject/messaging/impl/repository/UserNotificationRepositoryImpl.java
  • kernel/api/src/main/java/org/sakaiproject/messaging/api/UserNotificationTransferBean.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
  • kernel/kernel-impl/src/main/java/org/sakaiproject/content/impl/FileConversionServiceImpl.java
  • kernel/api/src/main/java/org/sakaiproject/messaging/api/UserNotificationData.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/main/java/org/sakaiproject/authz/impl/DbAuthzGroupService.java
  • announcement/announcement-api/api/src/java/org/sakaiproject/announcement/api/AnnouncementsUserNotificationHandler.java
  • msgcntr/messageforums-api/src/java/org/sakaiproject/api/app/messageforums/PrivateMessageUserNotificationHandler.java
  • samigo/samigo-services/src/java/org/sakaiproject/tool/assessment/TestsAndQuizzesUserNotificationHandler.java
  • samigo/samigo-app/src/java/org/sakaiproject/tool/assessment/ui/queue/delivery/TimedAssessmentQueue.java
  • plus/impl/src/main/java/org/sakaiproject/plus/impl/PlusServiceImpl.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
**/*.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
🧠 Learnings (18)
📓 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: 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 **/sakai-notifications.properties : Reconcile subscriptions proactively and keep localization strings in `sakai-notifications.properties`

Applied to files:

  • config/configuration/bundles/src/bundle/org/sakaiproject/config/bundle/default.sakai.properties
  • 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/repository/UserNotificationRepositoryImpl.java
  • kernel/kernel-impl/src/main/java/org/sakaiproject/messaging/impl/UserMessagingServiceImpl.java
  • webcomponents/tool/src/main/frontend/packages/sakai-notifications/src/SakaiNotifications.js
  • announcement/announcement-api/api/src/java/org/sakaiproject/announcement/api/AnnouncementsUserNotificationHandler.java
  • samigo/samigo-app/src/java/org/sakaiproject/tool/assessment/ui/queue/delivery/TimedAssessmentQueue.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:

  • config/configuration/bundles/src/bundle/org/sakaiproject/config/bundle/default.sakai.properties
  • 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/repository/UserNotificationRepositoryImpl.java
  • kernel/kernel-impl/src/main/java/org/sakaiproject/messaging/impl/UserMessagingServiceImpl.java
  • webcomponents/tool/src/main/frontend/packages/sakai-notifications/src/SakaiNotifications.js
  • announcement/announcement-api/api/src/java/org/sakaiproject/announcement/api/AnnouncementsUserNotificationHandler.java
  • samigo/samigo-app/src/java/org/sakaiproject/tool/assessment/ui/queue/delivery/TimedAssessmentQueue.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:

  • config/configuration/bundles/src/bundle/org/sakaiproject/config/bundle/default.sakai.properties
  • kernel/kernel-impl/src/main/java/org/sakaiproject/messaging/impl/UserMessagingServiceImpl.java
  • webcomponents/tool/src/main/frontend/packages/sakai-notifications/src/SakaiNotifications.js
  • announcement/announcement-api/api/src/java/org/sakaiproject/announcement/api/AnnouncementsUserNotificationHandler.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/event/impl/test/LearningResourceTestConfiguration.java
  • kernel/kernel-impl/src/test/java/org/sakaiproject/messaging/impl/UserMessagingServiceTestConfiguration.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:

  • kernel/api/src/main/java/org/sakaiproject/messaging/api/UserMessagingService.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
  • 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:

  • kernel/kernel-impl/src/test/java/org/sakaiproject/messaging/impl/UserMessagingServiceTests.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-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
📚 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: Applies to **/sakai-push-utils.js : Handle browser-specific Web Push logic in sakai-push-utils.js

Applied to files:

  • kernel/kernel-impl/src/main/java/org/sakaiproject/messaging/impl/UserMessagingServiceImpl.java
  • webcomponents/tool/src/main/frontend/packages/sakai-notifications/src/SakaiNotifications.js
📚 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:11:42.092Z
Learnt from: CR
Repo: sakaiproject/sakai PR: 0
File: .cursor/rules/sakai-rules.md:0-0
Timestamp: 2025-11-24T19:11:42.092Z
Learning: Applies to **/*.java : Treat any PR or suggestion containing Java `var` as non-compliant. Recommend replacing with explicit types before merge

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 **/*.java : 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`

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 **/*.java : Do not use local variable type inference (`var`) in Java code; always declare explicit types (e.g., `List<String> names = new ArrayList<>();`)

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 **/*.java : Do not use local variable type inference (var) in Java; always declare explicit types

Applied to files:

  • announcement/announcement-api/api/src/java/org/sakaiproject/announcement/api/AnnouncementsUserNotificationHandler.java
📚 Learning: 2025-11-24T19:11:42.092Z
Learnt from: CR
Repo: sakaiproject/sakai PR: 0
File: .cursor/rules/sakai-rules.md:0-0
Timestamp: 2025-11-24T19:11:42.092Z
Learning: Applies to **/*.java : When proposing Java code, spell out full types in local variable declarations, `for` loops, and try-with-resources

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
🧬 Code graph analysis (4)
webapi/src/main/java/org/sakaiproject/webapi/controllers/NotificationsController.java (1)
kernel/api/src/main/java/org/sakaiproject/messaging/api/UserNotificationTransferBean.java (1)
  • UserNotificationTransferBean (22-56)
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)
kernel/kernel-impl/src/main/java/org/sakaiproject/messaging/impl/UserMessagingServiceImpl.java (2)
webapi/src/main/java/org/sakaiproject/webapi/controllers/SitesController.java (1)
  • Slf4j (44-112)
kernel/api/src/main/java/org/sakaiproject/messaging/api/UserNotificationTransferBean.java (1)
  • UserNotificationTransferBean (22-56)
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). (3)
  • GitHub Check: maven-build
  • GitHub Check: sakai-deploy
  • GitHub Check: maven-build
🔇 Additional comments (33)
webcomponents/tool/src/main/frontend/packages/sakai-notifications/src/SakaiNotifications.js (3)

148-148: LGTM: Clean refactor using short-circuit evaluation.

The short-circuit assignment is a concise, idiomatic way to initialize the Map entry when the key doesn't exist. Functionally equivalent to an if-statement but more concise.


404-411: LGTM: Aligns with broadcast notification design.

Excluding the close button for "motd" prefix is consistent with the PR objective to implement broadcast-type notifications. Since broadcast notifications are shared rather than per-user, they shouldn't be individually clearable. This change correctly reflects that design.


87-90: Remove credentials from all fetch calls or explain the inconsistency.

The credentials: "include" option was removed from the fetch call at lines 87-90, but the review claim about lines 245 and 263 having credentials could not be verified in the current file state. If other API calls in this component do use credentials and this endpoint also requires authentication, the removal creates an inconsistency. Verify whether this endpoint is intentionally being called without credentials (e.g., for broadcast notifications that don't require authentication) or if this was an oversight. Ensure consistency across all fetch calls in the component.

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

27-40: No changes needed—Spring lookup-method wiring is correct.

The schedulingService() method is properly defined as an abstract method in ClusterEventTracking, and the org.sakaiproject.scheduling.api.SchedulingService bean is defined in scheduling-components.xml. This lookup-method injection pattern is consistent with the existing service injection approach in the same bean and across the codebase (e.g., authz-components.xml).

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

46-46: StoredConfigService has the required schedulingService setter via Lombok's @Setter annotation.

The property injection in the Spring bean configuration is correct. The StoredConfigService class declares @Setter SchedulingService schedulingService; at line 85, which generates the setSchedulingService() method at compile time. The bean reference org.sakaiproject.scheduling.api.SchedulingService in line 46 is properly defined, and the service is actively used in the init() method for scheduling polling tasks. No issues found.


43-43: The review comment is based on incorrect assumptions. An examination of StoredConfigService.java reveals:

  1. No destroy() method exists in StoredConfigService
  2. No destroy-method attribute appears in git history for this file
  3. No resource leak concern is valid since no cleanup logic was removed

The only lifecycle method in StoredConfigService is public void init(), and the current bean configuration correctly specifies only init-method="init". Per Sakai's infrastructure patterns, services should not declare destroy-method when no destroy() method exists, as this would cause configuration errors.

The actual change in this PR is the addition of the schedulingService dependency (line 46), which correctly aligns with the SchedulingService integration mentioned in the PR objectives.

Likely an incorrect or invalid review comment.

config/configuration/bundles/src/bundle/org/sakaiproject/config/bundle/default.sakai.properties (2)

2258-2261: Property key and default are correct; no issues found with the configuration.

Verification confirms that announcement.motd.notification.ttlhours is correctly named and implemented. The property is read at line 93 of AnnouncementsUserNotificationHandler.java with the exact default of 24 hours, matching the documentation in the properties file. The units are accurate (hours), and the TTL is applied as a Duration to MOTD broadcast notifications.

The optional suggestion to document disable behavior (setting to 0) cannot be verified without examining the messaging service's TTL cleanup implementation, so this remains an unconfirmed enhancement rather than a documented requirement.


5774-5785: Defaults are accurate; no config drift detected.

Runtime defaults in the implementation match all three "DEFAULT:" comments:

  • messaging.delete.expired.periodminutes defaults to 30 (verified in UserMessagingServiceImpl.java)
  • messaging.threadpool.size defaults to 10 (verified: DEFAULT_THREADPOOL_SIZE constant)
  • schedulingservice.poolsize defaults to 8 (verified in SchedulingServiceImpl.java)

The related properties are already grouped together logically as executor/thread pool configuration.

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

27-30: broadcast field is nullable; if legacy rows exist with NULL values, consider explicit handling

The broadcast field is a nullable Boolean with a default of FALSE in code, but the database column has no NOT NULL constraint. If legacy rows exist where broadcast is NULL, findByBroadcast(false) will not return those rows due to SQL equality semantics (NULL ≠ FALSE). However, deleteExpiredNotifications() is already fully implemented and transactional.

If legacy NULL broadcast values are not a concern (e.g., this is a new feature), no action is needed.

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

91-94: LGTM!

The @PostConstruct initialization pattern is appropriate for reading configuration values. The default TTL of 24 hours for MOTD notifications is reasonable.


240-249: LGTM!

Good refactoring to extract the common query predicate logic into a helper method. This reduces code duplication and improves maintainability.


205-224: LGTM!

The role-based filtering logic is well-implemented and correctly integrated with the updated 8-argument UserNotificationData constructor. The filtering chain (role filter → exclude creator → exclude superuser) is appropriate.

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

85-88: LGTM!

The Lombok @Setter annotations are appropriate for Spring dependency injection. The generated public setters allow for proper bean wiring via Spring configuration.


129-141: LGTM!

The migration from a local ScheduledExecutorService to the centralized SchedulingService is implemented correctly. The method signature and parameters remain the same, ensuring functional equivalence.

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

981-993: LGTM!

The migration from executor.schedule() to schedulingService.schedule() is implemented correctly. The delay values (10 seconds for retry, 30 seconds for initial delay) are reasonable.

kernel/kernel-impl/src/test/java/org/sakaiproject/messaging/impl/UserMessagingServiceTestConfiguration.java (2)

87-90: LGTM!

The SchedulingService mock bean follows the standard testing pattern used throughout the configuration class.


95-95: Verify if the threadpool size reduction is intentional.

The default threadpool size has been changed from 20 to 10. Please confirm this change is intentional and related to the SchedulingService migration, or if it should be reverted to maintain the original test behavior.

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

65-65: Breaking API change properly addressed.

The return type change from List<UserNotification> to List<UserNotificationTransferBean> has been correctly implemented. The single implementation in UserMessagingServiceImpl returns the correct type, and all callers have been updated: SitesController explicitly receives the UserNotificationTransferBean list, NotificationsController returns it directly, and all test calls work with the new type.

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

105-106: LGTM: Migration to SchedulingService is correct.

The migration from a local ScheduledExecutorService to the centralized SchedulingService is properly implemented. The scheduling parameters (initial delay, interval, time unit) are preserved correctly.

Also applies to: 149-149, 163-168

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

168-168: LGTM: Constructor updates align with the extended API.

The additional parameters (false, null) correctly indicate these are not broadcast notifications and have no TTL, which is appropriate for assessment-related user notifications.

Also applies to: 210-210

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

44-44: LGTM: Migration to SchedulingService is correctly implemented.

The scheduling mechanism is properly migrated to use SchedulingService. The worker pool is correctly retained for parallel file conversion tasks, as it serves a different purpose (parallel execution) than the scheduler (periodic triggering).

Also applies to: 58-58, 114-205

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

63-63: LGTM: Abstract accessor for SchedulingService follows the existing pattern.

The abstract schedulingService() method follows the same pattern as sqlService() and other abstract accessors in this class, maintaining consistency with the existing design.

Also applies to: 128-130


291-296: LGTM: Scheduling migration is correct.

The RefreshAuthzGroupTask scheduling is properly migrated to use the centralized SchedulingService with the same timing parameters (120s initial delay, configurable interval).

msgcntr/messageforums-api/src/java/org/sakaiproject/api/app/messageforums/PrivateMessageUserNotificationHandler.java (1)

97-97: LGTM: Constructor update aligns with the extended API.

The additional parameters correctly indicate this is a standard per-user notification (not broadcast, no TTL).

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

27-27: LGTM: Using transfer beans for API responses is a good practice.

Returning UserNotificationTransferBean instead of the domain entity UserNotification provides better API boundary separation and avoids exposing internal persistence details to clients.

Also applies to: 40-46

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

97-97: LGTM: Constructor update aligns with the extended API.

The additional parameters (false, null) correctly indicate this is a standard per-user notification for grade releases, not a broadcast notification.

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

117-117: LGTM! Good defensive validation.

The thread pool size configuration properly validates the config value and defaults to a safe value with appropriate logging if invalid.

Also applies to: 162-168


125-125: LGTM! SchedulingService integration aligns with PR objectives.

The scheduled cleanup of expired notifications uses the new SchedulingService facade as intended, with a configurable period.

Also applies to: 173-178


423-456: LGTM! doInsert() correctly handles extended notification data model.

The method signature and implementation have been properly updated to support:

  • TTL-based expiration (lines 447-449)
  • Broadcast notifications (line 452)
  • All fields from the extended UserNotificationData model

528-548: LGTM! decorateNotification() correctly updated for transfer bean.

The decoration logic properly populates derived fields (display name, formatted date, site title) and handles exceptions appropriately.


569-582: LGTM! Test notification updated to use transfer bean.

The test notification correctly creates and pushes a UserNotificationTransferBean instance.


619-681: LGTM! Enhanced push error handling improves resilience.

The push notification method has been properly updated to:

  • Accept UserNotificationTransferBean instead of UserNotification
  • Handle various HTTP error codes appropriately (410/404/400 remove invalid subscriptions, 403/429 provide diagnostic warnings)
  • Use parameterized SLF4J logging throughout

The error handling follows best practices for Web Push and will help maintain subscription hygiene.

Also applies to: 686-690


463-471: LGTM! Good refactoring to extract common user ID retrieval.

The getCurrentUserId() helper method reduces code duplication and provides consistent null-checking across multiple methods.

@adrianfish adrianfish force-pushed the SAK-51956 branch 2 times, most recently from 524dda6 to a8d177b Compare January 10, 2026 11:30
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: 5

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: Restore credentials: "include" for authenticated requests.

Removing credentials: "include" from the initial notifications fetch is inconsistent with other authenticated API calls in this file (lines 245, 263) and likely breaks authentication. The /api/users/me/notifications endpoint requires user authentication via cookies.

🔧 Proposed fix
 fetch(this.url, {
   cache: "no-cache",
   headers: { "Content-Type": "application/json" },
+  credentials: "include",
 })
samigo/samigo-app/src/java/org/sakaiproject/tool/assessment/ui/queue/delivery/TimedAssessmentQueue.java (1)

91-98: Potential NullPointerException in remove(long timedAG).

If tasks.get(timedAG) returns null (task not found), calling cancel(true) will throw a NullPointerException. Consider adding a null check.

🐛 Proposed fix
   public void remove(long timedAG){
     log.info("SAMIGO_TIMED_ASSESSMENT:QUEUE:REMOVE ID:" + timedAG);
     // Stop the task and remove it
-    tasks.get(timedAG).cancel(true);
-    tasks.remove(timedAG);
+    ScheduledFuture<?> task = tasks.remove(timedAG);
+    if (task != null) {
+        task.cancel(true);
+    }
     // Remove the grading data from the queue
     queue.remove(timedAG);
   }
assignment/api/src/java/org/sakaiproject/assignment/api/GradeAssignmentUserNotificationHandler.java (1)

96-101: Constructor call is now “magic-args”; prefer self-documenting intent (or a named constant for the payload).

new UserNotificationData(..., false, null) is easy to misuse at call sites as the API evolves. If null is the “no payload” sentinel, consider a small wrapper/factory (or at least a local Object payload = null;) to make the meaning explicit and reduce future call-site bugs.

🤖 Fix all issues with AI agents
In
@announcement/announcement-api/api/src/java/org/sakaiproject/announcement/api/AnnouncementsUserNotificationHandler.java:
- Around line 89-94: The init method assigns motdTTLHours from
serverConfigurationService.getInt without validation, allowing zero/negative
values to produce immediate/negative Duration.ofHours expiry; validate and clamp
motdTTLHours to a sane positive default (e.g., minimum 1) in init() after
calling serverConfigurationService.getInt; replace the hard-coded string
"BROADCAST" with a shared constant (e.g., ANNOUNCEMENT_BROADCAST or a new
constant in AnnouncementsUserNotificationHandler) used by handlers, persistence
and query logic to avoid mismatches; and ensure the MOTD payload that currently
sets title/broadcast/ttl also includes siteId, tool and url fields (or
explicitly document why they are omitted) so clients can perform UX filtering
and hasAlerts logic—update code paths that build the MOTD notification to
include those fields or add a comment/flag confirming omission is intentional.

In
@kernel/kernel-impl/src/main/java/org/sakaiproject/messaging/impl/UserMessagingServiceImpl.java:
- Around line 584-612: pushToAllUsers currently pages all PushSubscription rows
even when push is disabled, dedupes in-memory and calls push(bean) (which causes
per-user findByUser DB queries) while mutating a shared bean.to; fix by
short-circuiting early if push is disabled (check whatever flag push()
inspects), iterate pages and collect unique userIds or pick one PushSubscription
per user from Page<PushSubscription> without storing all rows in a large map
(use a local Set<String> seen per task or process each page immediately), and
avoid calling push(bean) that re-queries by instead invoking a lower-level
push-to-subscription method or creating a new UserNotificationTransferBean per
user and calling a new pushToSubscription(PushSubscription,
UserNotificationTransferBean) (or modify push(...) to accept a
userId/subscription to prevent findByUser). Also stop mutating the shared
bean.to by constructing a per-user bean before calling the push helper.
🧹 Nitpick comments (5)
plus/impl/src/main/java/org/sakaiproject/plus/impl/PlusServiceImpl.java (1)

163-168: Consider capturing the ScheduledFuture return value.

The scheduled task cannot be cancelled since the ScheduledFuture<?> return value is not captured. While the SchedulingService should handle executor lifecycle, capturing the return value would allow graceful task cancellation if needed during component reconfiguration or testing.

💡 Optional enhancement
+    private ScheduledFuture<?> refreshTask;
+
     public void init()
     {
         try {
             refreshQueue = Collections.synchronizedMap(new LinkedHashMap<>());
 
             long refreshTaskInterval = 60;
 
-            schedulingService.scheduleWithFixedDelay(
+            refreshTask = schedulingService.scheduleWithFixedDelay(
                 new refreshContextMembershipsTask(),
                 120, // minimally wait 2 mins for sakai to start
                 refreshTaskInterval, // delay before running again
                 TimeUnit.SECONDS
             );
         } catch (Exception t) {
             log.warn("init(): ", t);
         }
     }
kernel/kernel-impl/src/main/java/org/sakaiproject/messaging/impl/repository/UserNotificationRepositoryImpl.java (2)

41-50: Consider adding @Transactional(readOnly = true) to read-only methods.

The findByToUser method had @Transactional(readOnly = true) removed. For consistency and performance optimization (read-only transactions can enable optimizations), consider keeping this annotation on read-only methods.

♻️ Suggested annotation
+    @Transactional(readOnly = true)
     public List<UserNotification> findByToUser(String userId) {

52-63: Add @Transactional(readOnly = true) for consistency.

The new findByBroadcast method performs a read-only query but lacks a @Transactional annotation. Other read methods in the repository should ideally have @Transactional(readOnly = true) for consistency and potential performance benefits.

♻️ Suggested annotation
+    @Transactional(readOnly = true)
     public List<UserNotification> findByBroadcast(boolean broadcast) {
samigo/samigo-app/src/java/org/sakaiproject/tool/assessment/ui/queue/delivery/TimedAssessmentQueue.java (1)

42-44: Consider using generic types for type safety.

The queue and tasks collections use raw types or could benefit from consistent generics. While tasks has proper generics, queue uses raw ConcurrentHashMap.

♻️ Suggested improvement
-  private ConcurrentHashMap queue;
+  private ConcurrentHashMap<Long, TimedAssessmentGradingModel> queue;
   private SchedulingService schedulingService;
   private HashMap<Long, ScheduledFuture<?>> tasks;
...
-    queue = new ConcurrentHashMap();
+    queue = new ConcurrentHashMap<>();

Also applies to: 52-53

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

22-55: Add a null-guard (or Objects.requireNonNull) in of(...) to fail fast and clarify contract.

Right now of(null) will NPE deep in mapping; making the contract explicit improves diagnosability.

Proposed patch
 import org.sakaiproject.messaging.api.model.UserNotification;

 import java.time.Instant;
+import java.util.Objects;

 public class UserNotificationTransferBean {
@@
     public String formattedEventDate;

     public static UserNotificationTransferBean of(UserNotification un) {
+        Objects.requireNonNull(un, "UserNotification must not be null");

         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;
     }
 }
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between bf13c22 and a8d177b.

📒 Files selected for processing (41)
  • 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/RuntimeRegistrationTest.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-app/src/java/org/sakaiproject/tool/assessment/ui/queue/delivery/TimedAssessmentQueue.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 with no reviewable changes (1)
  • kernel/kernel-impl/src/test/java/org/sakaiproject/messaging/impl/RuntimeRegistrationTest.java
🚧 Files skipped from review as they are similar to previous changes (15)
  • kernel/kernel-impl/src/main/java/org/sakaiproject/event/impl/EventTrackingTest.java
  • lessonbuilder/components/src/java/org/sakaiproject/lessonbuildertool/impl/AddLessonsCommentUserNotificationHandlerImpl.java
  • kernel/kernel-impl/src/test/java/org/sakaiproject/content/impl/test/FileConversionServiceTestConfiguration.java
  • plus/impl/src/test/org/sakaiproject/plus/impl/PlusTestConfiguration.java
  • kernel/kernel-impl/src/main/webapp/WEB-INF/event-components.xml
  • webapi/src/main/java/org/sakaiproject/webapi/controllers/SitesController.java
  • kernel/kernel-impl/src/main/java/org/sakaiproject/authz/impl/AuthzGroupServiceTest.java
  • kernel/kernel-impl/src/main/webapp/WEB-INF/configstore-components.xml
  • config/configuration/bundles/src/bundle/org/sakaiproject/config/bundle/default.sakai.properties
  • kernel/kernel-impl/src/main/webapp/WEB-INF/scheduling-components.xml
  • plus/impl/src/main/webapp/WEB-INF/components.xml
  • kernel/kernel-impl/src/test/java/org/sakaiproject/event/impl/test/LearningResourceTestConfiguration.java
  • microsoft-integration/impl/src/main/java/org/sakaiproject/microsoft/impl/MicrosoftCommonServiceImpl.java
  • kernel/api/src/main/java/org/sakaiproject/messaging/api/model/UserNotification.java
  • kernel/api/src/main/java/org/sakaiproject/messaging/api/UserMessagingService.java
🧰 Additional context used
📓 Path-based instructions (10)
**/*.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
**/*.{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:

  • webcomponents/tool/src/main/frontend/packages/sakai-notifications/src/SakaiNotifications.js
  • kernel/kernel-impl/src/main/webapp/WEB-INF/components.xml
  • kernel/kernel-impl/src/main/webapp/WEB-INF/authz-components.xml
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
**/*.{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:

  • webcomponents/tool/src/main/frontend/packages/sakai-notifications/src/SakaiNotifications.js
  • kernel/kernel-impl/src/main/webapp/WEB-INF/components.xml
  • kernel/kernel-impl/src/main/webapp/WEB-INF/authz-components.xml
**/*.{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/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/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:

  • webapi/src/main/java/org/sakaiproject/webapi/controllers/NotificationsController.java
  • plus/impl/src/main/java/org/sakaiproject/plus/impl/PlusServiceImpl.java
  • kernel/kernel-impl/src/test/java/org/sakaiproject/messaging/impl/UserMessagingServiceTestConfiguration.java
  • kernel/api/src/main/java/org/sakaiproject/messaging/api/repository/UserNotificationRepository.java
  • kernel/api/src/main/java/org/sakaiproject/messaging/api/UserNotificationData.java
  • kernel/kernel-impl/src/main/java/org/sakaiproject/authz/impl/DbAuthzGroupService.java
  • commons/api/src/java/org/sakaiproject/commons/api/CommonsCommentUserNotificationHandler.java
  • kernel/kernel-impl/src/main/java/org/sakaiproject/scheduling/impl/SchedulingServiceImpl.java
  • 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
  • assignment/api/src/java/org/sakaiproject/assignment/api/GradeAssignmentUserNotificationHandler.java
  • samigo/samigo-app/src/java/org/sakaiproject/tool/assessment/ui/queue/delivery/TimedAssessmentQueue.java
  • kernel/kernel-impl/src/main/java/org/sakaiproject/event/impl/ClusterEventTracking.java
  • samigo/samigo-services/src/java/org/sakaiproject/tool/assessment/TestsAndQuizzesUserNotificationHandler.java
  • kernel/api/src/main/java/org/sakaiproject/messaging/api/UserNotificationTransferBean.java
  • kernel/kernel-impl/src/test/java/org/sakaiproject/messaging/impl/UserMessagingServiceTests.java
  • msgcntr/messageforums-api/src/java/org/sakaiproject/api/app/messageforums/PrivateMessageUserNotificationHandler.java
  • assignment/api/src/java/org/sakaiproject/assignment/api/AddAssignmentUserNotificationHandler.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/api/src/main/java/org/sakaiproject/scheduling/api/SchedulingService.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 (19)
📓 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
  • webapi/src/main/java/org/sakaiproject/webapi/controllers/NotificationsController.java
  • 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
  • samigo/samigo-app/src/java/org/sakaiproject/tool/assessment/ui/queue/delivery/TimedAssessmentQueue.java
  • 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: 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:

  • webapi/src/main/java/org/sakaiproject/webapi/controllers/NotificationsController.java
  • 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
  • samigo/samigo-app/src/java/org/sakaiproject/tool/assessment/ui/queue/delivery/TimedAssessmentQueue.java
  • 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: 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/kernel-impl/src/test/java/org/sakaiproject/messaging/impl/UserMessagingServiceTests.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
📚 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/authz-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
  • announcement/announcement-api/api/src/java/org/sakaiproject/announcement/api/AnnouncementsUserNotificationHandler.java
  • kernel/kernel-impl/src/test/java/org/sakaiproject/messaging/impl/UserMessagingServiceTests.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
  • 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: 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: Applies to **/sakai-push-utils.js : Handle browser-specific Web Push logic in sakai-push-utils.js

Applied to files:

  • kernel/kernel-impl/src/main/java/org/sakaiproject/messaging/impl/UserMessagingServiceImpl.java
📚 Learning: 2025-11-24T19:11:42.092Z
Learnt from: CR
Repo: sakaiproject/sakai PR: 0
File: .cursor/rules/sakai-rules.md:0-0
Timestamp: 2025-11-24T19:11:42.092Z
Learning: Applies to **/*.java : Treat any PR or suggestion containing Java `var` as non-compliant. Recommend replacing with explicit types before merge

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 **/*.java : 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`

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 **/*.java : Do not use local variable type inference (`var`) in Java code; always declare explicit types (e.g., `List<String> names = new ArrayList<>();`)

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 **/*.java : Do not use local variable type inference (var) in Java; always declare explicit types

Applied to files:

  • announcement/announcement-api/api/src/java/org/sakaiproject/announcement/api/AnnouncementsUserNotificationHandler.java
📚 Learning: 2025-11-24T19:11:42.092Z
Learnt from: CR
Repo: sakaiproject/sakai PR: 0
File: .cursor/rules/sakai-rules.md:0-0
Timestamp: 2025-11-24T19:11:42.092Z
Learning: Applies to **/*.java : When proposing Java code, spell out full types in local variable declarations, `for` loops, and try-with-resources

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
📚 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
📚 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
🧬 Code graph analysis (6)
webapi/src/main/java/org/sakaiproject/webapi/controllers/NotificationsController.java (1)
kernel/api/src/main/java/org/sakaiproject/messaging/api/UserNotificationTransferBean.java (1)
  • UserNotificationTransferBean (22-56)
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)
kernel/kernel-impl/src/main/java/org/sakaiproject/messaging/impl/UserMessagingServiceImpl.java (2)
webapi/src/main/java/org/sakaiproject/webapi/controllers/SitesController.java (1)
  • Slf4j (44-112)
kernel/api/src/main/java/org/sakaiproject/messaging/api/UserNotificationTransferBean.java (1)
  • UserNotificationTransferBean (22-56)
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)
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)
assignment/api/src/java/org/sakaiproject/assignment/api/AddAssignmentUserNotificationHandler.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)
⏰ 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 (31)
webcomponents/tool/src/main/frontend/packages/sakai-notifications/src/SakaiNotifications.js (1)

404-411: LGTM! Appropriate exclusion for broadcast notifications.

Excluding the close button for MOTD notifications aligns with the broadcast notification design, where MOTD messages are stored centrally and shouldn't be dismissed individually by users.

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

105-105: LGTM! Clean migration to SchedulingService.

The import and autowired field follow standard Spring dependency injection patterns.

Also applies to: 149-149

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

1-49: LGTM! Well-designed facade interface.

The SchedulingService provides a clean abstraction over Java's ScheduledExecutorService with clear Javadoc references and configurable thread pool sizing.

kernel/kernel-impl/src/test/java/org/sakaiproject/messaging/impl/UserMessagingServiceTestConfiguration.java (2)

28-28: LGTM! Mock SchedulingService bean added correctly.

The mock SchedulingService bean follows the project's test configuration patterns and aligns with the new scheduling abstraction.

Based on learnings, this test-specific mock configuration correctly remains in the test configuration class.

Also applies to: 87-90


95-95: Thread pool size change is correctly aligned with production configuration.

The test configuration correctly mocks the thread pool size as 10, which matches the DEFAULT_THREADPOOL_SIZE constant in UserMessagingServiceImpl.java and aligns with the documented default in default.sakai.properties. No misalignment detected.

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

18-18: LGTM! New fields support broadcast notification feature.

The broadcast and ttl fields directly support the PR's objective to add broadcast-type notifications with time-to-live semantics.

Also applies to: 35-36


20-26: All UserNotificationData constructor call sites have been properly updated to the new 8-parameter signature.

The constructor signature change from 6 to 8 parameters has been consistently addressed across all call sites in the codebase (TestsAndQuizzesUserNotificationHandler, PrivateMessageUserNotificationHandler, UserMessagingServiceTests, AddLessonsCommentUserNotificationHandlerImpl, CommonsCommentUserNotificationHandler, GradeAssignmentUserNotificationHandler, AddAssignmentUserNotificationHandler, and AnnouncementsUserNotificationHandler).

The change from @Getter to @Data (adding mutability via setters) is intentional and safe. AnnouncementsUserNotificationHandler demonstrates the intended usage pattern: objects are constructed with the no-arg constructor, immediately populated via setters, and then returned—not mutated after creation. Other call sites use the all-args constructor and pass instances directly to collections. No thread-safety issues identified.

msgcntr/messageforums-api/src/java/org/sakaiproject/api/app/messageforums/PrivateMessageUserNotificationHandler.java (1)

97-97: LGTM! Constructor call correctly updated.

The constructor call correctly passes false for broadcast (private messages are user-specific) and null for ttl (no expiration), aligning with the updated UserNotificationData API.

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

85-88: LGTM: Field injection via Lombok setters.

The use of @Setter for dependency injection is clean and consistent with the broader PR changes introducing SchedulingService.


129-139: SchedulingService lifecycle is properly managed.

Verified that SchedulingServiceImpl's destroy() method calls executor.shutdownNow() and the Spring bean configuration in scheduling-components.xml explicitly registers init-method="init" and destroy-method="destroy", ensuring the executor is properly shut down on application context destruction. No resource leak risk.

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

22-22: LGTM: SchedulingService lookup wiring.

The lookup-method wiring is consistent with the existing pattern for other service dependencies in this bean and correctly references the SchedulingService bean.

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

40-40: LGTM: Import for scheduling-components.xml.

The import correctly adds the SchedulingService bean definitions to the kernel component context.

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

168-168: LGTM: Updated UserNotificationData constructor call.

The additional parameters (false, null) correctly indicate this is a non-broadcast notification with no TTL, which is appropriate for individual user notifications.


210-210: LGTM: Consistent constructor update in handleUpdate.

Same pattern as handleAdd - correctly marks this as a non-broadcast notification.

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

128-130: LGTM: Abstract accessor for SchedulingService.

The abstract method pattern enables Spring lookup-method injection, consistent with other service dependencies in this class (e.g., sqlService(), serverConfigurationService()).


291-296: LGTM: Scheduling refactored to use SchedulingService.

The scheduling parameters are preserved correctly:

  • Initial delay: 120 seconds (wait for Sakai startup)
  • Period: refreshTaskInterval (configurable)
  • TimeUnit: SECONDS

This centralizes executor management and eliminates the need for local shutdown handling.

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

130-130: LGTM: Updated constructor call in handleAdd.

Correctly marks this assignment notification as non-broadcast with no TTL.


167-167: LGTM: Updated constructor call for site-wide access notifications.

Consistent with the non-broadcast notification pattern.


181-181: LGTM: Updated constructor call for group access notifications.

All three call sites in this handler are now consistent with the extended UserNotificationData constructor signature.

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

27-29: LGTM: New repository methods for broadcast and TTL support.

The method signatures follow Spring Data conventions. The deleteExpiredNotifications() method is properly annotated with @Transactional and correctly implements the delete operation using Criteria API.

kernel/kernel-impl/src/main/java/org/sakaiproject/event/impl/ClusterEventTracking.java (2)

44-44: LGTM: SchedulingService dependency follows existing patterns.

The new abstract method schedulingService() follows the same pattern as other collaborator methods (sqlService(), memoryService(), etc.), maintaining consistency with the existing codebase architecture.

Also applies to: 105-106


226-231: Cleanup of scheduled tasks is properly handled centrally by SchedulingService.

The migration correctly delegates lifecycle management to the SchedulingService. The SchedulingServiceImpl's destroy() method (Spring lifecycle hook) calls executor.shutdownNow(), which cancels all pending scheduled tasks during application shutdown. ClusterEventTracking appropriately does not require local shutdown logic—no need to store or manage the ScheduledFuture returned by scheduleWithFixedDelay(). This is the correct pattern.

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

27-27: LGTM: Proper use of transfer bean for API response.

Using UserNotificationTransferBean instead of exposing the JPA entity directly is a good practice. The transfer bean includes the new broadcast field and provides proper separation between the persistence layer and the API contract.

Also applies to: 41-41

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

65-77: LGTM: Properly transactional delete operation.

The deleteExpiredNotifications() method is correctly annotated with @Transactional for the write operation and uses Instant.now() appropriately for time-based filtering.

samigo/samigo-app/src/java/org/sakaiproject/tool/assessment/ui/queue/delivery/TimedAssessmentQueue.java (1)

71-76: LGTM: Proper migration to SchedulingService.

The scheduling logic correctly migrates from the local thread pool to SchedulingService.scheduleAtFixedRate(), preserving the original timing semantics (0 initial delay, 3000ms period).

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

94-94: LGTM: Correctly updated for new UserNotificationData constructor.

The new parameters (false, null) are appropriate here since comment notifications are individual (not broadcasts) and don't require a TTL. This matches the pattern used across other notification handlers in this PR.

Also applies to: 110-110

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

44-44: LGTM: Clean migration to SchedulingService.

The SchedulingService injection replaces the local ScheduledExecutorService for scheduling periodic tasks, while the worker ExecutorService (line 84) is correctly retained for parallel file conversion work.

Also applies to: 58-58


114-205: LGTM: Scheduling logic correctly migrated.

The periodic queue processing is now handled by schedulingService.scheduleWithFixedDelay(). The conversion logic within the lambda remains unchanged, and the workers pool is still properly managed for parallel processing.

kernel/kernel-impl/src/test/java/org/sakaiproject/messaging/impl/UserMessagingServiceTests.java (1)

172-172: LGTM: Test correctly updated for new constructor signature.

The UserNotificationData instantiation is properly updated with the new parameters (false, null), maintaining test validity for the notification handling flow.

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

240-249: buildWhere(...) extraction looks good (reduces duplicated criteria logic).

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

387-406: Broadcast routing in update(...) looks consistent with the new broadcast flag.

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 (2)
plus/impl/src/main/java/org/sakaiproject/plus/impl/PlusServiceImpl.java (2)

163-168: Add top-level exception handling in the scheduled task.

The commit message indicates scheduled tasks should be guarded with try/catch to prevent unchecked exceptions from suppressing future executions. However, the refreshContextMembershipsTask.run() method (lines 560-604) lacks a top-level try-catch block. If an exception escapes from outside the existing try-catch at lines 585-593 (e.g., from the queue iteration logic or final logging), it could suppress all future scheduled executions.

🛡️ Recommended fix: Wrap run() method body in try-catch
 @Override
 public void run() {
+    try {
         if ( refreshQueue.size() < 1 ) return;
         log.debug("RefreshContextMembershipsTask size={}", refreshQueue.size());
 
         // ... rest of the method body ...
 
         log.info("Refreshed {} contexts in {} seconds, longest context was {} at {} seconds",
             numberRefreshed, timeRefreshed/1e3, longestName, longestRefreshed/1e3);
+    } catch (Exception e) {
+        log.error("Unexpected error in refreshContextMembershipsTask", e);
+    }
 }

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

Line 588 violates the coding guideline by using string concatenation in the log statement. As per coding guidelines: Use SLF4J parameterized logging instead of string concatenation.

📝 Recommended fix: Replace string concatenation with parameterized logging
-                log.error("refreshContextMembershipsTask.run() Problem refreshing context: " + contextGuid, e);
+                log.error("refreshContextMembershipsTask.run() Problem refreshing context: {}", contextGuid, e);
🤖 Fix all issues with AI agents
In
@kernel/kernel-impl/src/main/java/org/sakaiproject/messaging/impl/UserMessagingServiceImpl.java:
- Around line 159-183: In the init() method of UserMessagingServiceImpl, update
the log message inside the catch block of the
schedulingService.scheduleWithFixedDelay task to correct the typo: change the
log.warn invocation that currently reads "Exception whiles deleting expired
notifications" to use "while" instead of "whiles" so it becomes "Exception while
deleting expired notifications" (locate the catch block that logs exceptions
from userNotificationRepository.deleteExpiredNotifications()).
- Around line 391-413: Remove the redundant manual assignment to bean.tool after
constructing the transfer bean: the call to UserNotificationTransferBean.of(...)
already maps the tool set by doInsert(...), so delete the line "bean.tool =
und.getCommonToolId();" inside the notification handling block (the code that
builds the bean from doInsert and then conditionally pushes via
pushToAllUsers/decorateNotification or push/decorateNotification).

In @plus/impl/src/main/java/org/sakaiproject/plus/impl/PlusServiceImpl.java:
- Around line 569-578: The loop removes contextGuid from refreshQueue inside the
synchronized block (in PlusServiceImpl while loop) and then again in the finally
block; remove the redundant removal in the finally block (the
refreshQueue.remove(contextGuid) at the end of the processing) so each entry is
removed only once while still ensuring the initial removal remains synchronized;
update any duplicated removal in the surrounding try/finally areas (including
the similar occurrence around lines 585-593) to avoid unsynchronized
double-removal.
🧹 Nitpick comments (2)
announcement/announcement-api/api/src/java/org/sakaiproject/announcement/api/AnnouncementsUserNotificationHandler.java (1)

240-249: LGTM! Good refactoring to extract common query logic.

The buildWhere helper eliminates duplication between the query and delete operations, improving maintainability.

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

589-616: Consider optimizing broadcast push to avoid redundant subscription lookups.

The pushToAllUsers method pages through all push subscriptions but then calls push(bean) for each user, which internally calls pushSubscriptionRepository.findByUser(un.to) (line 635) to look up the same subscriptions again. This results in N additional database queries where N is the number of unique users.

Potential optimization approaches

Option 1: Pass subscriptions directly to avoid re-querying:

private void pushToAllUsers(UserNotificationTransferBean bean) {
    executor.execute(() -> {
        int current = 0;
        int pageSize = 100;
        Map<String, List<PushSubscription>> userSubscriptions = new HashMap<>();
        Page<PushSubscription> page = pushSubscriptionRepository.findAll(PageRequest.of(current, pageSize));
        
        while (page.getNumberOfElements() > 0) {
            page.getContent().forEach(ps -> {
                userSubscriptions.computeIfAbsent(ps.getUserId(), k -> new ArrayList<>()).add(ps);
            });
            current++;
            page = pushSubscriptionRepository.findAll(PageRequest.of(current, pageSize));
        }
        
        userSubscriptions.forEach((userId, subscriptions) -> {
            bean.to = userId;
            pushToSubscriptions(bean, subscriptions);
        });
    });
}

private void pushToSubscriptions(UserNotificationTransferBean un, List<PushSubscription> subscriptions) {
    // Push logic from existing push() method, but iterate over provided subscriptions
}

Option 2: If the current structure must be preserved, document why the re-query is necessary (e.g., for transaction isolation or data freshness guarantees).

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a8d177b and e8efb61.

📒 Files selected for processing (4)
  • announcement/announcement-api/api/src/java/org/sakaiproject/announcement/api/AnnouncementsUserNotificationHandler.java
  • kernel/api/src/main/java/org/sakaiproject/messaging/api/UserNotificationData.java
  • kernel/kernel-impl/src/main/java/org/sakaiproject/messaging/impl/UserMessagingServiceImpl.java
  • plus/impl/src/main/java/org/sakaiproject/plus/impl/PlusServiceImpl.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:

  • plus/impl/src/main/java/org/sakaiproject/plus/impl/PlusServiceImpl.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
  • kernel/api/src/main/java/org/sakaiproject/messaging/api/UserNotificationData.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 (13)
📓 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-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-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: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: Applies to **/sakai-push-utils.js : Handle browser-specific Web Push logic in sakai-push-utils.js

Applied to files:

  • kernel/kernel-impl/src/main/java/org/sakaiproject/messaging/impl/UserMessagingServiceImpl.java
📚 Learning: 2025-11-24T19:11:42.092Z
Learnt from: CR
Repo: sakaiproject/sakai PR: 0
File: .cursor/rules/sakai-rules.md:0-0
Timestamp: 2025-11-24T19:11:42.092Z
Learning: Applies to **/*.java : Treat any PR or suggestion containing Java `var` as non-compliant. Recommend replacing with explicit types before merge

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 **/*.java : 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`

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 **/*.java : Do not use local variable type inference (`var`) in Java code; always declare explicit types (e.g., `List<String> names = new ArrayList<>();`)

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 **/*.java : Do not use local variable type inference (var) in Java; always declare explicit types

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
🧬 Code graph analysis (1)
kernel/kernel-impl/src/main/java/org/sakaiproject/messaging/impl/UserMessagingServiceImpl.java (1)
webapi/src/main/java/org/sakaiproject/webapi/controllers/SitesController.java (1)
  • Slf4j (44-112)
⏰ 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 (6)
plus/impl/src/main/java/org/sakaiproject/plus/impl/PlusServiceImpl.java (1)

105-105: LGTM: SchedulingService dependency injection.

The import and autowired field correctly introduce the SchedulingService dependency to replace direct ScheduledExecutorService usage.

Also applies to: 149-149

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

18-38: LGTM! Clean data model enhancement.

The additions are well-structured:

  • Lombok @Data annotation provides complete POJO support (getters, setters, equals, hashCode, toString)
  • @NoArgsConstructor enables flexible construction patterns used elsewhere in the codebase
  • BROADCAST constant provides a type-safe sentinel value for broadcast notifications
  • broadcast and ttl fields enable the new TTL-based broadcast notification feature
announcement/announcement-api/api/src/java/org/sakaiproject/announcement/api/AnnouncementsUserNotificationHandler.java (3)

87-94: LGTM! Configuration-driven TTL initialization.

The @PostConstruct pattern correctly initializes the MOTD TTL from server configuration with a sensible default of 24 hours.


124-134: Verify that null fields are intentional for MOTD broadcast notifications.

The MOTD notification construction sets from, to, title, broadcast, and ttl, but leaves siteId, url, and commonToolId unset. This differs from the non-MOTD path (line 223), which explicitly provides all 8 constructor arguments including these fields.

For broadcast MOTD notifications:

  • Is it intentional that siteId, url, and commonToolId remain null/default?
  • If broadcasts aren't associated with a specific site or tool, this may be correct, but please confirm this aligns with how broadcast notifications are displayed and processed downstream (e.g., in UserMessagingServiceImpl and the frontend).
Optional: Consider using the all-args constructor for consistency

If null/default values are intentional, you could make this explicit by using the all-args constructor:

-                String title = ((AnnouncementMessageHeader) message.getHeader()).getSubject();
-                UserNotificationData userNotificationData = new UserNotificationData();
-                userNotificationData.setFrom(event.getUserId());
-                userNotificationData.setTo(UserNotificationData.BROADCAST);
-                userNotificationData.setTitle(title);
-                userNotificationData.setBroadcast(true);
-                userNotificationData.setTtl(Duration.ofHours(motdTTLHours));
-                return Optional.of(List.of(userNotificationData));
+                String title = ((AnnouncementMessageHeader) message.getHeader()).getSubject();
+                UserNotificationData userNotificationData = new UserNotificationData(
+                    event.getUserId(),           // from
+                    UserNotificationData.BROADCAST, // to
+                    null,                        // siteId (not applicable for broadcasts)
+                    title,                       // title
+                    null,                        // url (not applicable for broadcasts)
+                    null,                        // commonToolId (not applicable for broadcasts)
+                    true,                        // broadcast
+                    Duration.ofHours(motdTTLHours) // ttl
+                );
+                return Optional.of(List.of(userNotificationData));

This would align with the pattern used at line 223 and make the intent clearer.


207-217: LGTM! Correct role-based filtering.

The role filtering logic properly:

  • Retrieves selected roles from message properties
  • Filters users by checking site membership and role ID
  • Falls back to the full user set when no roles are specified
kernel/kernel-impl/src/main/java/org/sakaiproject/messaging/impl/UserMessagingServiceImpl.java (1)

661-678: LGTM! Improved error handling for push notification failures.

The enhanced switch statement provides specific handling and logging for different HTTP status codes (410, 404, 400, 403, 429), making it easier to diagnose push notification issues. The automatic cleanup of invalid subscriptions (410/404/400) is especially valuable for maintaining subscription hygiene.

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

🧹 Nitpick comments (1)
plus/impl/src/main/java/org/sakaiproject/plus/impl/PlusServiceImpl.java (1)

569-601: Optional cleanup: duplicate queue removal.

The refreshQueue.remove(contextGuid) call at line 591 is redundant since the entry is already removed at line 578 within the synchronized block. While this doesn't cause incorrect behavior (the second remove is a no-op), removing line 591 would improve code clarity.

♻️ Suggested cleanup
                 } catch ( LTIException e ) {
                     log.error("refreshContextMembershipsTask.run() Problem refreshing context: " + contextGuid, e);
                 } finally {
                     time = (System.currentTimeMillis() - start);
-                    refreshQueue.remove(contextGuid);
                     log.debug("Refresh of context: {} took {} seconds", contextGuid, time/1e3);
                 }
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a8d177b and e8efb61.

📒 Files selected for processing (4)
  • announcement/announcement-api/api/src/java/org/sakaiproject/announcement/api/AnnouncementsUserNotificationHandler.java
  • kernel/api/src/main/java/org/sakaiproject/messaging/api/UserNotificationData.java
  • kernel/kernel-impl/src/main/java/org/sakaiproject/messaging/impl/UserMessagingServiceImpl.java
  • plus/impl/src/main/java/org/sakaiproject/plus/impl/PlusServiceImpl.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:

  • plus/impl/src/main/java/org/sakaiproject/plus/impl/PlusServiceImpl.java
  • announcement/announcement-api/api/src/java/org/sakaiproject/announcement/api/AnnouncementsUserNotificationHandler.java
  • kernel/kernel-impl/src/main/java/org/sakaiproject/messaging/impl/UserMessagingServiceImpl.java
  • kernel/api/src/main/java/org/sakaiproject/messaging/api/UserNotificationData.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 (13)
📓 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:

  • 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 **/*.{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 {**/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-11-24T19:11:42.092Z
Learnt from: CR
Repo: sakaiproject/sakai PR: 0
File: .cursor/rules/sakai-rules.md:0-0
Timestamp: 2025-11-24T19:11:42.092Z
Learning: Applies to **/*.java : Treat any PR or suggestion containing Java `var` as non-compliant. Recommend replacing with explicit types before merge

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 **/*.java : 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`

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 **/*.java : Do not use local variable type inference (`var`) in Java code; always declare explicit types (e.g., `List<String> names = new ArrayList<>();`)

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 **/*.java : Do not use local variable type inference (var) in Java; always declare explicit types

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
📚 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: Applies to **/sakai-push-utils.js : Handle browser-specific Web Push logic in sakai-push-utils.js

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 (22-56)
⏰ 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 (13)
kernel/api/src/main/java/org/sakaiproject/messaging/api/UserNotificationData.java (1)

18-39: LGTM! API extended to support broadcast notifications and TTL.

The addition of broadcast and ttl fields, along with the BROADCAST constant, aligns well with the PR objectives. The Lombok annotation change from @Getter to @Data expands the generated methods to include setters, equals, hashCode, and toString, which is appropriate for this data transfer object.

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

91-94: LGTM! Configuration loading properly implemented.

The @PostConstruct initialization method correctly loads the MOTD TTL configuration with a sensible default of 24 hours.


124-134: LGTM! Broadcast notification handling correctly implemented.

The MOTD handling creates a single broadcast notification with appropriate TTL configuration. The use of UserNotificationData.BROADCAST for the to field correctly signals broadcast delivery.


205-217: LGTM! Role-based filtering properly implemented.

The role-based filtering correctly uses the SELECTED_ROLES_PROPERTY to filter recipients based on their site membership roles, ensuring notifications are only sent to users with the appropriate roles.


240-249: LGTM! Helper method reduces duplication effectively.

The buildWhere method properly encapsulates the common query predicate logic used for announcement notification cleanup, improving code maintainability.

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

163-168: LGTM! Migration to SchedulingService correctly implemented.

The scheduling call properly uses the new SchedulingService with appropriate initial delay (120 seconds) and fixed delay interval (60 seconds).

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

162-168: LGTM! Threadpool size validation implemented correctly.

The validation ensures the threadpool size is positive and falls back to a sensible default if misconfigured, with appropriate warning logging.


176-183: Guard scheduled task execution with try/catch.

The scheduled cleanup task correctly wraps the deleteExpiredNotifications() call in a try-catch block to prevent uncaught exceptions from stopping the scheduled task. This is a critical reliability pattern for periodic tasks.

Based on learnings, this pattern ensures scheduled tasks continue running even if individual executions fail.


394-411: LGTM! Broadcast notification routing correctly implemented.

The event handling properly routes notifications through the new data flow: persist via doInsert, convert to transfer bean, decorate, and push either to all users (broadcast) or to specific recipients. The conditional routing based on und.isBroadcast() aligns with the PR objectives.


428-461: LGTM! Parameter consolidation and TTL handling implemented correctly.

The refactored method signature accepting UserNotificationData improves clarity and reduces parameter count. The TTL handling correctly calculates the endDate by adding the duration to the event date.


533-553: LGTM! Decoration logic properly adapted to transfer bean.

The decorateNotification method correctly operates on UserNotificationTransferBean, enriching it with display names, formatted dates, and site titles, with appropriate fallbacks for lookup failures.


661-678: LGTM! Comprehensive push notification error handling.

The enhanced error handling properly categorizes response status codes and takes appropriate action:

  • Removes invalid subscriptions (410, 404, 400)
  • Logs actionable warnings for VAPID configuration issues (403)
  • Logs rate limiting concerns (429)

This improves reliability by cleaning up stale subscriptions and providing diagnostic information for operational issues.


506-508: Consider using Stream.distinct() to prevent duplicate notifications.

The getNotifications() method combines user-specific notifications with broadcast notifications using Stream.concat(). Since the UserNotification entity requires a toUser value (NOT NULL column) and the findByBroadcast(true) query does not filter by user, a notification with broadcast=true and a toUser matching the requested userId would appear in both result sets, causing a duplicate in the final list. Apply distinct() to the combined stream to ensure each notification appears only once.

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

🤖 Fix all issues with AI agents
In
@kernel/kernel-impl/src/main/java/org/sakaiproject/messaging/impl/UserMessagingServiceImpl.java:
- Around line 597-612: The code reuses and mutates the same
UserNotificationTransferBean instance (bean) for every PushSubscription, risking
incorrect associations; add a copy constructor or factory method on
UserNotificationTransferBean (e.g., of(UserNotificationTransferBean) or a copy
ctor that clones all fields) and inside the loop create a fresh copy for each
subscription (e.g., UserNotificationTransferBean copy =
UserNotificationTransferBean.of(bean)) before setting copy.to = ps.getUserId()
and calling push(ps, copy), so each push works with an isolated bean instance.
🧹 Nitpick comments (3)
kernel/kernel-impl/src/main/java/org/sakaiproject/messaging/impl/UserMessagingServiceImpl.java (1)

499-509: Verify: Potential duplicate notifications when user is also the broadcast creator.

The method concatenates user-specific notifications with all broadcast notifications. If a broadcast notification has toUser set to a specific user (e.g., the creator), that notification may appear twice for that user.

Consider adding deduplication or filtering:

🔍 Suggested deduplication approach
     public List<UserNotificationTransferBean> getNotifications() {

         String userId = getCurrentUserId();
         if (userId == null) return Collections.emptyList();

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

         return beans;
     }

Note: This assumes UserNotification has proper equals/hashCode implementation based on its ID.

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

34-34: Add final modifier to constant.

DEFAULT_POOL_SIZE should be declared as final to prevent accidental modification.

♻️ Suggested fix
-    private static int DEFAULT_POOL_SIZE = 8;
+    private static final int DEFAULT_POOL_SIZE = 8;

50-55: Consider graceful shutdown for cleaner task termination.

shutdownNow() interrupts running tasks immediately. For scheduled periodic tasks that may be mid-execution, a graceful shutdown could prevent unexpected state:

♻️ Graceful shutdown alternative
     public void destroy() {

         if (executor != null) {
-            executor.shutdownNow();
+            executor.shutdown();
+            try {
+                if (!executor.awaitTermination(5, TimeUnit.SECONDS)) {
+                    log.warn("SchedulingService executor did not terminate gracefully, forcing shutdown");
+                    executor.shutdownNow();
+                }
+            } catch (InterruptedException e) {
+                executor.shutdownNow();
+                Thread.currentThread().interrupt();
+            }
         }
     }

Note: This may not be necessary if immediate shutdown is acceptable for your use case.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e8efb61 and 8db5b01.

📒 Files selected for processing (3)
  • kernel/kernel-impl/src/main/java/org/sakaiproject/messaging/impl/UserMessagingServiceImpl.java
  • kernel/kernel-impl/src/main/java/org/sakaiproject/scheduling/impl/SchedulingServiceImpl.java
  • plus/impl/src/main/java/org/sakaiproject/plus/impl/PlusServiceImpl.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:

  • plus/impl/src/main/java/org/sakaiproject/plus/impl/PlusServiceImpl.java
  • kernel/kernel-impl/src/main/java/org/sakaiproject/messaging/impl/UserMessagingServiceImpl.java
  • kernel/kernel-impl/src/main/java/org/sakaiproject/scheduling/impl/SchedulingServiceImpl.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: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
📚 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
📚 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
📚 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
📚 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: Applies to **/sakai-service-worker.js : Handle background push 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-push-utils.js : Handle browser-specific Web Push logic in sakai-push-utils.js

Applied to files:

  • kernel/kernel-impl/src/main/java/org/sakaiproject/messaging/impl/UserMessagingServiceImpl.java
🧬 Code graph analysis (2)
kernel/kernel-impl/src/main/java/org/sakaiproject/messaging/impl/UserMessagingServiceImpl.java (3)
msgcntr/messageforums-api/src/java/org/sakaiproject/api/app/messageforums/PrivateMessageUserNotificationHandler.java (1)
  • Slf4j (40-107)
webapi/src/main/java/org/sakaiproject/webapi/controllers/SitesController.java (1)
  • Slf4j (44-112)
kernel/api/src/main/java/org/sakaiproject/messaging/api/UserNotificationTransferBean.java (1)
  • UserNotificationTransferBean (22-56)
kernel/kernel-impl/src/main/java/org/sakaiproject/scheduling/impl/SchedulingServiceImpl.java (2)
kernel/kernel-impl/src/main/java/org/sakaiproject/content/impl/FileConversionServiceImpl.java (1)
  • Slf4j (52-238)
samigo/samigo-app/src/java/org/sakaiproject/tool/assessment/ui/queue/delivery/TimedAssessmentQueue.java (1)
  • Slf4j (39-106)
⏰ 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: maven-build
  • GitHub Check: sakai-deploy
🔇 Additional comments (9)
plus/impl/src/main/java/org/sakaiproject/plus/impl/PlusServiceImpl.java (1)

105-105: LGTM! Clean migration to SchedulingService abstraction.

The changes successfully migrate from a local ScheduledExecutorService to the centralized SchedulingService abstraction:

  1. Dependency injection (lines 105, 149): Standard Spring autowiring of SchedulingService with explicit type declaration.

  2. Scheduling (lines 163-168): Correctly uses scheduleWithFixedDelay with appropriate parameters—120-second initial delay for Sakai startup and 60-second intervals between task completions.

  3. Queue processing (lines 569-578): The synchronized block correctly ensures atomic check-and-remove operations on refreshQueue. Processing outside the lock minimizes contention. Removing items before processing prevents duplicate processing if the task reschedules.

The synchronization strategy is sound: refreshQueue is a Collections.synchronizedMap, making individual operations thread-safe, while the explicit synchronized block ensures compound operations (isEmpty + get + remove) are atomic.

Also applies to: 149-149, 163-168, 569-578

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

117-134: LGTM - Clean service wiring and thread pool configuration.

Good addition of SchedulingService for centralized scheduled task management. The separate executor for messaging processing and the configurable thread pool size are appropriate design choices.


173-183: LGTM - Scheduled cleanup with proper error handling.

The scheduled task for deleting expired notifications is well-implemented with appropriate exception handling to prevent task termination on errors. Using scheduleWithFixedDelay ensures consistent intervals between executions.


636-688: LGTM - Robust push notification handling with proper error recovery.

Good implementation with:

  • Validation of subscription details before attempting push
  • Comprehensive HTTP status code handling
  • Automatic cleanup of invalid subscriptions (410/404/400)
  • Appropriate logging levels for different failure scenarios

531-551: LGTM - Clean decoration with appropriate fallback handling.

The method correctly enriches the transfer bean with display metadata and handles missing user/site scenarios gracefully by falling back to ID values.


426-459: LGTM - Clean refactor with proper TTL handling.

Good changes:

  • Encapsulated parameters via UserNotificationData
  • Correct TTL-to-endDate conversion using eventDate.toInstant().plus(und.getTtl())
  • Proper null check before setting endDate

391-411: LGTM - Clean event handling with broadcast support.

The flow correctly:

  1. Processes handler notifications
  2. Inserts and decorates each notification
  3. Routes broadcast notifications to pushToAllUsers and regular notifications to pushToSingleUser
kernel/kernel-impl/src/main/java/org/sakaiproject/scheduling/impl/SchedulingServiceImpl.java (2)

38-48: LGTM - Proper initialization with validation.

Good practices:

  • Configuration validation with fallback to default
  • setRemoveOnCancelPolicy(true) prevents memory leaks from cancelled tasks
  • Parameterized logging format

57-79: LGTM - Clean delegation to executor.

The scheduling methods correctly delegate to the underlying ScheduledThreadPoolExecutor and return ScheduledFuture<?> for task management.

Note: These methods will throw NullPointerException if called before init() or after destroy(). This is acceptable given Spring's lifecycle management ensures proper initialization order.

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
@kernel/kernel-impl/src/main/java/org/sakaiproject/messaging/impl/UserMessagingServiceImpl.java:
- Around line 412-418: The code accesses pathParts[2] after splitting ref
without checking length, risking ArrayIndexOutOfBoundsException; update the
block handling SiteService.EVENT_SITE_PUBLISH (the transactionTemplate.execute
callback that calls userNotificationRepository.setDeferredBySiteId) to validate
pathParts.length >= 3 before calling setDeferredBySiteId, and if the check
fails, skip the transaction (or log a warning/error) instead of accessing
pathParts[2]; ensure the validation occurs immediately after splitting ref so
all callers use the safe index.
- Around line 499-509: getNotifications may return the same UserNotification
twice because you concatenate results from
userNotificationRepository.findByToUser(userId) and findByBroadcast(true);
update getNotifications to deduplicate by notification ID before
mapping/decorating (e.g., collect the concatenated stream into a Map keyed by
notification.getId() or use a distinct-by-id step) so each unique notification
is converted to UserNotificationTransferBean::of and passed to
decorateNotification exactly once; keep using
userNotificationRepository.findByToUser and findByBroadcast but collapse
duplicates by ID prior to collecting to the final List.
- Around line 587-613: The code mutates the shared UserNotificationTransferBean
(bean.to) while pushing asynchronously, causing race conditions; fix by creating
a per-user copy of the bean before calling push so each task gets its own
immutable data (e.g. add a copy constructor or clone method on
UserNotificationTransferBean or a withTo(String) factory and use that to produce
userBean for each PushSubscription), then call push(ps, userBean) inside the
forEach so no threads share the same bean instance.

In
@kernel/kernel-impl/src/main/java/org/sakaiproject/scheduling/impl/SchedulingServiceImpl.java:
- Around line 57-79: The three scheduling methods schedule, scheduleAtFixedRate,
and scheduleWithFixedDelay currently call executor directly and can NPE if
init() hasn't completed; add a defensive null check at the start of each method
that verifies executor != null (and/or not shutdown) and throw an
IllegalStateException (or log and throw) with a clear message like
"SchedulingService not initialized: executor is null" so callers get a
descriptive failure instead of an NPE; update schedule, scheduleAtFixedRate, and
scheduleWithFixedDelay to perform that check before delegating to executor.
🧹 Nitpick comments (3)
kernel/kernel-impl/src/main/java/org/sakaiproject/scheduling/impl/SchedulingServiceImpl.java (1)

38-48: Consider adding a custom ThreadFactory for better observability.

The current implementation uses the default thread naming, which results in generic names like pool-1-thread-1. For production debugging and monitoring, consider using a custom ThreadFactory to create threads with more descriptive names like Sakai-Scheduling-thread-N.

Example ThreadFactory implementation
 public void init() {
 
     int poolSize = serverConfigurationService.getInt("schedulingservice.poolsize", DEFAULT_POOL_SIZE);
     if (poolSize <= 0) {
       log.warn("schedulingservice.poolsize is invalid. Defaulting to {}", DEFAULT_POOL_SIZE);
       poolSize = DEFAULT_POOL_SIZE;
     }
 
-    executor = new ScheduledThreadPoolExecutor(poolSize);
+    ThreadFactory threadFactory = new ThreadFactory() {
+        private final AtomicInteger threadNumber = new AtomicInteger(1);
+        @Override
+        public Thread newThread(Runnable r) {
+            Thread thread = new Thread(r, "Sakai-Scheduling-" + threadNumber.getAndIncrement());
+            thread.setDaemon(true);
+            return thread;
+        }
+    };
+    executor = new ScheduledThreadPoolExecutor(poolSize, threadFactory);
     executor.setRemoveOnCancelPolicy(true);
 }
kernel/kernel-impl/src/main/java/org/sakaiproject/messaging/impl/UserMessagingServiceImpl.java (2)

239-250: Consider adding graceful shutdown with timeout.

The current implementation calls shutdown() but doesn't wait for running tasks to complete. Consider using awaitTermination() with a timeout followed by shutdownNow() if tasks don't complete in time.

♻️ Suggested improvement for graceful shutdown
 public void destroy() {
 
     if (executor != null) {
         try {
             // Attempt a graceful shutdown
             executor.shutdown();
             log.info("Messaging thread pool shutdown initiated");
+            
+            // Wait for tasks to complete with a timeout
+            if (!executor.awaitTermination(30, TimeUnit.SECONDS)) {
+                log.warn("Executor did not terminate gracefully, forcing shutdown");
+                executor.shutdownNow();
+                // Wait again after forcing shutdown
+                if (!executor.awaitTermination(10, TimeUnit.SECONDS)) {
+                    log.error("Executor did not terminate after forced shutdown");
+                }
+            }
         } catch (Exception e) {
             log.error("Error shutting down messaging thread pool: {}", e.toString());
         }
     }
 }

426-459: Consider validating TTL values.

While the null check for und.getTtl() is good, there's no validation that the TTL is reasonable. A negative TTL would set endDate in the past, and an excessively large TTL could cause issues.

♻️ Suggested TTL validation

Add validation before using the TTL:

         ba.setEventDate(eventDate.toInstant());
         if (und.getTtl() != null) {
+            if (und.getTtl().isNegative()) {
+                log.warn("Invalid negative TTL for notification, ignoring: {}", und.getTtl());
+            } else if (und.getTtl().toDays() > 365) {
+                log.warn("Excessively large TTL for notification ({}), capping to 365 days", und.getTtl());
+                ba.setEndDate(eventDate.toInstant().plusDays(365));
+            } else {
                 ba.setEndDate(eventDate.toInstant().plus(und.getTtl()));
+            }
         }
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e8efb61 and 8db5b01.

📒 Files selected for processing (3)
  • kernel/kernel-impl/src/main/java/org/sakaiproject/messaging/impl/UserMessagingServiceImpl.java
  • kernel/kernel-impl/src/main/java/org/sakaiproject/scheduling/impl/SchedulingServiceImpl.java
  • plus/impl/src/main/java/org/sakaiproject/plus/impl/PlusServiceImpl.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:

  • plus/impl/src/main/java/org/sakaiproject/plus/impl/PlusServiceImpl.java
  • kernel/kernel-impl/src/main/java/org/sakaiproject/scheduling/impl/SchedulingServiceImpl.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
📚 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
📚 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: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
📚 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
📚 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: Applies to **/sakai-service-worker.js : Handle background push 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-push-utils.js : Handle browser-specific Web Push logic in sakai-push-utils.js

Applied to files:

  • kernel/kernel-impl/src/main/java/org/sakaiproject/messaging/impl/UserMessagingServiceImpl.java
🧬 Code graph analysis (2)
kernel/kernel-impl/src/main/java/org/sakaiproject/scheduling/impl/SchedulingServiceImpl.java (3)
kernel/kernel-impl/src/main/java/org/sakaiproject/config/impl/StoredConfigService.java (1)
  • Slf4j (68-677)
kernel/kernel-impl/src/main/java/org/sakaiproject/event/impl/ClusterEventTracking.java (1)
  • Slf4j (52-742)
samigo/samigo-app/src/java/org/sakaiproject/tool/assessment/ui/queue/delivery/TimedAssessmentQueue.java (1)
  • Slf4j (39-106)
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 (22-56)
⏰ 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: maven-build
  • GitHub Check: sakai-deploy
🔇 Additional comments (10)
plus/impl/src/main/java/org/sakaiproject/plus/impl/PlusServiceImpl.java (3)

105-105: LGTM! Clean migration to SchedulingService.

The addition of the SchedulingService dependency follows Spring's dependency injection pattern and is consistent with the other autowired dependencies in this class.

Also applies to: 149-149


163-168: LGTM! Scheduling migration completed correctly.

The migration from a private ScheduledExecutorService to the centralized SchedulingService preserves the original scheduling semantics (120-second initial delay, 60-second recurring interval). This aligns with the PR objective to standardize scheduling across Sakai components.


569-578: LGTM! Proper synchronization for compound operations.

The explicit synchronized (refreshQueue) block is necessary even though refreshQueue is a Collections.synchronizedMap (line 159). The synchronizedMap only synchronizes individual method calls, but the compound operation (check isEmpty, get first entry, remove it) must be atomic to prevent race conditions where one thread checks the queue as non-empty while another thread concurrently removes the last element.

This is correct defensive programming for concurrent queue processing.

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

50-55: LGTM! Proper cleanup on destroy.

The null check and use of shutdownNow() ensure safe cleanup of the executor and its tasks during service shutdown.

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

18-117: LGTM - Imports and constants properly defined.

The new imports for scheduling, pagination, and transfer beans are appropriate for the added functionality. The DEFAULT_THREADPOOL_SIZE constant is well-named and used for configuration fallback.


159-237: LGTM - Improved initialization with scheduling and validation.

The thread pool size validation with fallback to DEFAULT_THREADPOOL_SIZE is good defensive coding. The scheduled cleanup task using SchedulingService.scheduleWithFixedDelay is appropriate for periodic maintenance operations like deleting expired notifications.


531-551: LGTM - Proper decoration with good error handling.

The method correctly decorates the transfer bean with display names, formatted dates, and site titles. Error handling provides sensible fallbacks when users or sites cannot be found.


572-585: LGTM - Test notification properly updated.

The test notification correctly uses the new UserNotificationTransferBean and follows the established pattern of decorating and pushing the notification.


615-688: LGTM - Excellent error handling for push notifications.

The enhanced error handling with the switch statement properly handles different HTTP response codes:

  • 410/404/400: Removes invalid subscriptions
  • 403: Warns about VAPID configuration issues
  • 429: Warns about rate limiting

The logging uses parameterized SLF4J format throughout, which is good practice. The subscription cleanup on error is appropriate.


690-868: LGTM - Supporting methods are well-implemented.

The clearPushSubscription method properly removes invalid subscriptions with appropriate logging. The EmailSender inner class remains unchanged and provides good email formatting functionality.

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

Caution

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

⚠️ Outside diff range comments (3)
samigo/samigo-app/src/java/org/sakaiproject/tool/assessment/ui/queue/delivery/TimedAssessmentQueue.java (3)

32-55: Add null validation for SchedulingService and tighten types on queue and singleton fields

ComponentManager.get(SchedulingService.class) can return null and is not validated in the constructor. Additionally, queue is a raw ConcurrentMap (should be generified), and schedulingService and tasks should be final for safe publication in this singleton pattern.

Proposed fix
-  private ConcurrentMap queue;
-  private SchedulingService schedulingService;
-  private ConcurrentMap<Long, ScheduledFuture<?>> tasks;
+  private final ConcurrentMap<Long, TimedAssessmentGradingModel> queue;
+  private final SchedulingService schedulingService;
+  private final ConcurrentMap<Long, ScheduledFuture<?>> tasks;

   private static class Loader {
     static final TimedAssessmentQueue INSTANCE = new TimedAssessmentQueue(ComponentManager.get(SchedulingService.class));
   }
 
   private TimedAssessmentQueue(SchedulingService schedulingService) {
+    if (schedulingService == null) {
+      throw new IllegalStateException("SchedulingService is not available; check Spring wiring for scheduling-components.xml");
+    }
     this.schedulingService = schedulingService;
-    queue = new ConcurrentHashMap();
+    queue = new ConcurrentHashMap<>();
     tasks = new ConcurrentHashMap<>();
   }

65-81: Prevent scheduled-task leaks when add(...) is called repeatedly for the same ID + fix log.error exception handling

The tasks.put(...) call overwrites any existing future without canceling it, leaving prior scheduled tasks running indefinitely without a reference. This is demonstrated by the proper cancellation pattern in the remove(Long) method (lines 95–99), which should be followed here. Additionally, log.error("... Exception:{}", ..., ex) uses a placeholder for the exception, which causes SLF4J to call toString() on it and drop the stack trace. Pass the exception as the final argument without a placeholder per SLF4J best practices.

Proposed fix
@@
   public void add(TimedAssessmentGradingModel timedAG){
@@
           try {
@@
-              tasks.put(timedAG.getAssessmentGradingId(),
-                            schedulingService.scheduleAtFixedRate(
-                            new TimedAssessmentRunnable(timedAG.getAssessmentGradingId()),
-                            0,
-                            3000,
-                            TimeUnit.MILLISECONDS)); 
-              log.info( "SAMIGO_TIMED_ASSESSMENT:QUEUE:NEWTIMER:SUCCESS ID:{}", timedAG.getAssessmentGradingId());
+              Long assessmentGradingId = timedAG.getAssessmentGradingId();
+              ScheduledFuture<?> newFuture = schedulingService.scheduleAtFixedRate(
+                      new TimedAssessmentRunnable(assessmentGradingId),
+                      0,
+                      3,
+                      TimeUnit.SECONDS);
+
+              ScheduledFuture<?> oldFuture = tasks.put(assessmentGradingId, newFuture);
+              if (oldFuture != null) {
+                  oldFuture.cancel(true);
+              }
+              log.info("SAMIGO_TIMED_ASSESSMENT:QUEUE:NEWTIMER:SUCCESS ID:{}", assessmentGradingId);
           } catch (Exception ex) {
-              log.error("SAMIGO_TIMED_ASSESSMENT:QUEUE:NEWTIMER:FAILED ID:{} Exception:{}", timedAG.getAssessmentGradingId(), ex);
+              log.error("SAMIGO_TIMED_ASSESSMENT:QUEUE:NEWTIMER:FAILED ID:{}", timedAG.getAssessmentGradingId(), ex);
           }
   }

92-104: Use atomic remove() and add generic type parameter to local variable

The get() then remove() pattern is racy and performs extra lookups. Use ScheduledFuture<?> future = tasks.remove(timedAG); instead to atomically retrieve and remove in one operation. Also avoid raw ScheduledFuture type—the explicit type parameter is required per coding standards.

Proposed fix
@@
   public void remove(long timedAG){
     log.info("SAMIGO_TIMED_ASSESSMENT:QUEUE:REMOVE ID:{}", timedAG);
     // Stop the task and remove it
-    ScheduledFuture future = tasks.get(timedAG);
-
-    if (future != null) {
-      future.cancel(true);
-    }
-
-    tasks.remove(timedAG);
+    ScheduledFuture<?> future = tasks.remove(timedAG);
+    if (future != null) {
+        future.cancel(true);
+    }
     // Remove the grading data from the queue
     queue.remove(timedAG);
   }

Also verify TimedAssessmentRunnable properly handles thread interruption from cancel(true). Currently it catches all exceptions generically but does not explicitly check Thread.currentThread().isInterrupted().

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8db5b01 and 97e917d.

📒 Files selected for processing (1)
  • samigo/samigo-app/src/java/org/sakaiproject/tool/assessment/ui/queue/delivery/TimedAssessmentQueue.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:

  • samigo/samigo-app/src/java/org/sakaiproject/tool/assessment/ui/queue/delivery/TimedAssessmentQueue.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 **/sakai-notifications.properties : Reconcile subscriptions proactively and keep localization strings in `sakai-notifications.properties`

Applied to files:

  • samigo/samigo-app/src/java/org/sakaiproject/tool/assessment/ui/queue/delivery/TimedAssessmentQueue.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:

  • samigo/samigo-app/src/java/org/sakaiproject/tool/assessment/ui/queue/delivery/TimedAssessmentQueue.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

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

🧹 Nitpick comments (3)
kernel/api/src/main/java/org/sakaiproject/messaging/api/UserNotificationTransferBean.java (3)

22-38: Consider encapsulating fields for API stability.

As this bean resides in kernel/api (public API), exposing mutable public fields means any field rename becomes a breaking change for consumers. Private fields with getters (or a Java record if immutability is desired) would provide more flexibility for future refactoring.

That said, this is a common pattern for simple DTOs and acceptable if the project consistently uses public fields for transfer beans.


40-55: Add null check or document nullability contract.

If un is null, this method will throw an unguarded NPE at line 43. Consider adding a precondition check to fail fast with a clear message, or documenting that null is not permitted.

🛡️ Suggested null check
     public static UserNotificationTransferBean of(UserNotification un) {
 
+        if (un == null) {
+            throw new IllegalArgumentException("UserNotification cannot be null");
+        }
         UserNotificationTransferBean bean = new UserNotificationTransferBean();
         bean.from = un.getFromUser();

57-75: Confusing naming: parameter and variable shadow field names.

The parameter from shadows the field from, and the local variable to shadows the field to. This makes lines 60-61 difficult to read:

to.from = from.from;
to.to = from.to;

Consider renaming for clarity.

✏️ Suggested rename for clarity
-    public static UserNotificationTransferBean of(UserNotificationTransferBean from) {
+    public static UserNotificationTransferBean of(UserNotificationTransferBean source) {
 
-        UserNotificationTransferBean to = new UserNotificationTransferBean();
-        to.from = from.from;
-        to.to = from.to;
-        to.event = from.event;
-        to.ref = from.ref;
-        to.title = from.title;
-        to.siteId = from.siteId;
-        to.url = from.url;
-        to.eventDate = from.eventDate;
-        to.viewed = from.viewed;
-        to.tool = from.tool;
-        to.broadcast = from.broadcast;
-        to.fromDisplayName = from.fromDisplayName;
-        to.siteTitle = from.siteTitle;
-        to.formattedEventDate = from.formattedEventDate;
-        return to;
+        UserNotificationTransferBean copy = new UserNotificationTransferBean();
+        copy.from = source.from;
+        copy.to = source.to;
+        copy.event = source.event;
+        copy.ref = source.ref;
+        copy.title = source.title;
+        copy.siteId = source.siteId;
+        copy.url = source.url;
+        copy.eventDate = source.eventDate;
+        copy.viewed = source.viewed;
+        copy.tool = source.tool;
+        copy.broadcast = source.broadcast;
+        copy.fromDisplayName = source.fromDisplayName;
+        copy.siteTitle = source.siteTitle;
+        copy.formattedEventDate = source.formattedEventDate;
+        return copy;
     }
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 97e917d and 136769d.

📒 Files selected for processing (2)
  • kernel/api/src/main/java/org/sakaiproject/messaging/api/UserNotificationTransferBean.java
  • kernel/kernel-impl/src/main/java/org/sakaiproject/authz/impl/DbAuthzGroupService.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/kernel-impl/src/main/java/org/sakaiproject/authz/impl/DbAuthzGroupService.java
  • kernel/api/src/main/java/org/sakaiproject/messaging/api/UserNotificationTransferBean.java
🧠 Learnings (1)
📓 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
⏰ 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: maven-build
  • GitHub Check: sakai-deploy
🔇 Additional comments (5)
kernel/kernel-impl/src/main/java/org/sakaiproject/authz/impl/DbAuthzGroupService.java (5)

63-63: LGTM! Abstract accessor pattern for SchedulingService dependency.

The schedulingService() abstract method follows the existing dependency injection pattern in this class (e.g., sqlService()), maintaining consistency with the codebase conventions.

Also applies to: 128-130


715-727: Correct fix for race condition on compound operations.

While refreshQueue is a Collections.synchronizedMap, compound operations (check isEmpty(), retrieve via iterator, then remove()) are not atomic without explicit synchronization. This synchronized block correctly ensures atomicity of the read-modify-write sequence, preventing potential issues where:

  1. Thread checks queue is non-empty
  2. Another thread drains the queue
  3. First thread gets null or throws on empty iterator

The pattern of extracting azGroupId inside the synchronized block before processing outside is efficient—it minimizes the lock holding time while still ensuring safe removal.


734-737: Good use of SLF4J parameterized logging.

The logging statements correctly use placeholder syntax {} with parameters, which is the recommended approach per coding guidelines. This avoids unnecessary string concatenation when the log level is disabled.


745-745: Explicit long literal is good practice.

While refreshMaxTime is already a long, using 1000L makes the intent explicit and ensures consistent long arithmetic regardless of any future type changes.


291-296: Migration to SchedulingService looks correct.

The task scheduling is properly delegated to the centralized SchedulingService bean instead of managing a local ScheduledExecutorService. The abstract method schedulingService() is correctly wired in authz-components.xml via Spring's lookup-method injection pattern, and the SchedulingService interface provides the scheduleWithFixedDelay() method used in the code. This aligns with the PR objective to consolidate scheduled executor usage across Sakai.

@adrianfish adrianfish marked this pull request as ready for review January 13, 2026 15:28
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.
@adrianfish
Copy link
Contributor Author

I don't see anything in ScheduledInvocationManager that would have been suitable for this PR. In fact, I think we should migrate the calls to that service to this new SchedulingService, thus using java's later concurrent stuff.

@adrianfish
Copy link
Contributor Author

@ern Could you approve this? I don't see any real use for the ScheduledInvocationManager here.

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