Skip to content

Performance: DTO Projection Tickler on read operations (Tickler, Tickler Comment, Tickler Link)#2257

Merged
lacarmen merged 12 commits into
hotfix/01232026from
performance/dto-projection-tickler
Feb 3, 2026
Merged

Performance: DTO Projection Tickler on read operations (Tickler, Tickler Comment, Tickler Link)#2257
lacarmen merged 12 commits into
hotfix/01232026from
performance/dto-projection-tickler

Conversation

@LiamStanziani

@LiamStanziani LiamStanziani commented Jan 30, 2026

Copy link
Copy Markdown
Collaborator

User description

In this PR, I have:

  • Created DTO Projection functionality for tickler read operations (Tickler, Tickler Comment, Tickler Link)
    • This massively improves DB hits on the 'TicklerMain' page since the old operations cause N+1 query issues
  • Created Unit/Integration tests for this new functionality

I have tested this by:

  • Comparing JavaMelody stats from before and after the updates
  • Testing the main tickler page "TicklerMain.jsp"

Summary by Sourcery

Introduce DTO-based read path for tickler list views to reduce database load and support efficient projection of ticklers, comments, and links.

New Features:

  • Add TicklerListDTO, TicklerCommentDTO, and TicklerLinkDTO to represent tickler list rows, comments, and links for read-only views.

Enhancements:

  • Add TicklerDao and TicklerManager APIs and implementations to fetch tickler data via JPQL DTO projections with optional pagination, batching comment and link loading to avoid N+1 queries.
  • Refactor ticklerMain.jsp to render tickler lists, comments, and links from DTOs instead of entity graphs, simplifying the view and leveraging the optimized read path.
  • Register JavaMelody DataSource bean post-processor and temporarily relax monitoring security constraints for local development diagnostics.

Tests:

  • Add TicklerDaoDTOIntegrationTest to verify DTO projection queries, pagination behavior, comment and link batch loading, and filter handling.
  • Add unit tests for TicklerListDTO and TicklerCommentDTO helper and formatting methods.
  • Update test persistence configuration to include TicklerLink so DTO link tests can run.

Summary by cubic

Introduced DTO projection for Tickler list reads (ticklers, comments, links) and updated ticklerMain.jsp to use it. This removes N+1 queries and cuts TicklerMain page DB calls from ~25 to 2–3 per load.

  • Refactors

    • Added TicklerListDTO, TicklerCommentDTO, TicklerLinkDTO with JPQL constructor projections.
    • New DAO/Manager methods: getTicklerDTOs(filter[, offset, limit]); batch-loads comments and links; enforces read privilege and logs demographic-filtered access.
    • Rewrote ticklerMain.jsp to render from DTOs only; removed legacy path.
    • Added unit and integration tests for DTOs and DAO projection.
  • Migration

    • No schema changes.

Written for commit 61d8389. Summary will update on new commits.

Summary by CodeRabbit

  • New Features
    • Introduces lightweight tickler DTOs and new endpoints to retrieve tickler lists (with optional pagination) including associated comments and links.
  • Performance Improvements
    • Optimized list retrieval with projection-based queries and batch loading to reduce database round-trips.
  • UI
    • Tickler list view updated to consume DTOs (simpler rendering and safer null handling).
  • Tests
    • New unit and integration tests covering DTOs, pagination, comment/link batching, and formatting utilities.

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


PR Type

Enhancement, Tests


Description

  • Introduced DTO projection for tickler list reads reducing database queries from ~25 to 2-3 per page load

    • Added TicklerListDTO, TicklerCommentDTO, TicklerLinkDTO with JPQL constructor projections
    • Implemented batch loading for comments and links to avoid N+1 query problems
  • Added new DAO and Manager methods for optimized tickler retrieval with pagination support

    • getTicklerDTOs(filter) and getTicklerDTOs(filter, offset, limit) in both TicklerDao and TicklerManager
    • Includes privilege checking and audit logging for demographic-filtered queries
  • Refactored ticklerMain.jsp to render from DTOs only, removing legacy entity-based rendering path

    • Simplified view logic with safer null handling through DTO helper methods
    • Removed direct dependencies on entity relationships and TicklerLinkDao
  • Added comprehensive unit and integration tests for DTO functionality and formatting methods


Diagram Walkthrough

flowchart LR
  A["TicklerManager"] -->|calls| B["TicklerDao.getTicklerDTOs"]
  B -->|JPQL projection| C["TicklerListDTO"]
  B -->|batch load| D["TicklerCommentDTO"]
  B -->|batch load| E["TicklerLinkDTO"]
  C -->|contains| D
  C -->|contains| E
  F["ticklerMain.jsp"] -->|renders| C
  F -->|renders| D
  F -->|renders| E
Loading

File Walkthrough

Relevant files
Enhancement
8 files
TicklerDao.java
Added DTO projection method signatures                                     
+34/-0   
TicklerDaoImpl.java
Implemented DTO projection with batch loading                       
+282/-0 
TicklerManager.java
Added DTO retrieval method signatures                                       
+37/-0   
TicklerManagerImpl.java
Implemented DTO retrieval with privilege checks                   
+45/-0   
TicklerListDTO.java
Created main DTO for tickler list display                               
+297/-0 
TicklerCommentDTO.java
Created DTO for tickler comments with formatting                 
+178/-0 
TicklerLinkDTO.java
Created lightweight DTO for tickler links                               
+143/-0 
ticklerMain.jsp
Refactored to render from DTOs only                                           
+42/-43 
Tests
3 files
TicklerDaoDTOIntegrationTest.java
Added integration tests for DTO projection                             
+373/-0 
TicklerCommentDTOTest.java
Added unit tests for comment DTO methods                                 
+236/-0 
TicklerListDTOTest.java
Added unit tests for list DTO methods                                       
+288/-0 
Configuration changes
1 files
persistence.xml
Added TicklerLink entity to test configuration                     
+1/-0     

@LiamStanziani LiamStanziani requested a review from Copilot January 30, 2026 19:37
@LiamStanziani LiamStanziani self-assigned this Jan 30, 2026
@sourcery-ai

sourcery-ai Bot commented Jan 30, 2026

Copy link
Copy Markdown

Reviewer's Guide

Introduces DTO-based JPQL projections and batch loading for ticklers, comments, and links to eliminate N+1 queries on TicklerMain, wires them through DAO/manager layers, updates the JSP to consume DTOs instead of entities, and adds JavaMelody wiring plus unit/integration tests for the new DTOs and DAO behavior.

Sequence diagram for TicklerMain DTO-based tickler loading

sequenceDiagram
    actor User
    participant TicklerMainJSP
    participant TicklerManagerImpl
    participant TicklerDaoImpl
    participant EntityManager
    participant Database

    User->>TicklerMainJSP: Load TicklerMain.jsp
    TicklerMainJSP->>TicklerManagerImpl: getTicklerDTOs(loggedInInfo, filter)
    TicklerManagerImpl->>TicklerManagerImpl: checkPrivilege(loggedInInfo, PRIVILEGE_READ)
    TicklerManagerImpl->>TicklerDaoImpl: getTicklerDTOs(filter)

    TicklerDaoImpl->>TicklerDaoImpl: getTicklerDTOQueryString(paramList, filter)
    TicklerDaoImpl->>EntityManager: createQuery(jpql)
    EntityManager-->>TicklerDaoImpl: Query ticklerQuery
    TicklerDaoImpl->>EntityManager: ticklerQuery.setParameters(paramList)
    TicklerDaoImpl->>EntityManager: ticklerQuery.setFirstResult(offset)
    TicklerDaoImpl->>EntityManager: setLimit(ticklerQuery, limit)
    TicklerDaoImpl->>Database: Execute ticklerQuery
    Database-->>TicklerDaoImpl: List<TicklerListDTO> ticklerDTOs

    TicklerDaoImpl->>TicklerDaoImpl: loadCommentsForTicklerDTOs(ticklerDTOs)
    TicklerDaoImpl->>EntityManager: createQuery(commentSql)
    EntityManager-->>TicklerDaoImpl: Query commentQuery
    TicklerDaoImpl->>EntityManager: commentQuery.setParameter(ticklerIds)
    TicklerDaoImpl->>Database: Execute commentQuery
    Database-->>TicklerDaoImpl: List<TicklerCommentDTO> comments
    TicklerDaoImpl->>TicklerDaoImpl: Map comments to TicklerListDTO.comments

    TicklerDaoImpl->>TicklerDaoImpl: loadLinksForTicklerDTOs(ticklerDTOs)
    TicklerDaoImpl->>EntityManager: createQuery(linkSql)
    EntityManager-->>TicklerDaoImpl: Query linkQuery
    TicklerDaoImpl->>EntityManager: linkQuery.setParameter(ticklerIds)
    TicklerDaoImpl->>Database: Execute linkQuery
    Database-->>TicklerDaoImpl: List<TicklerLinkDTO> links
    TicklerDaoImpl->>TicklerDaoImpl: Map links to TicklerListDTO.links

    TicklerDaoImpl-->>TicklerManagerImpl: List<TicklerListDTO>
    TicklerManagerImpl-->>TicklerMainJSP: List<TicklerListDTO>
    TicklerMainJSP-->>User: Render tickler table using DTOs
Loading

Class diagram for new Tickler DTOs and DAO/manager methods

classDiagram
    class Tickler {
        <<entity>>
        enum STATUS
        enum PRIORITY
    }

    class TicklerListDTO {
        <<dto>>
        - Integer id
        - String message
        - Date serviceDate
        - Date createDate
        - Tickler.STATUS status
        - Tickler.PRIORITY priority
        - Integer demographicNo
        - String demographicLastName
        - String demographicFirstName
        - String creatorLastName
        - String creatorFirstName
        - String assigneeLastName
        - String assigneeFirstName
        - List~TicklerCommentDTO~ comments
        - List~TicklerLinkDTO~ links
        + TicklerListDTO()
        + TicklerListDTO(Integer id, String message, Date serviceDate, Date createDate, Tickler.STATUS status, Tickler.PRIORITY priority, Integer demographicNo, String demographicLastName, String demographicFirstName, String creatorLastName, String creatorFirstName, String assigneeLastName, String assigneeFirstName)
        + Integer getId()
        + void setId(Integer id)
        + String getMessage()
        + void setMessage(String message)
        + Date getServiceDate()
        + void setServiceDate(Date serviceDate)
        + Date getCreateDate()
        + void setCreateDate(Date createDate)
        + Tickler.STATUS getStatus()
        + void setStatus(Tickler.STATUS status)
        + Tickler.PRIORITY getPriority()
        + void setPriority(Tickler.PRIORITY priority)
        + Integer getDemographicNo()
        + void setDemographicNo(Integer demographicNo)
        + String getDemographicLastName()
        + void setDemographicLastName(String demographicLastName)
        + String getDemographicFirstName()
        + void setDemographicFirstName(String demographicFirstName)
        + String getCreatorLastName()
        + void setCreatorLastName(String creatorLastName)
        + String getCreatorFirstName()
        + void setCreatorFirstName(String creatorFirstName)
        + String getAssigneeLastName()
        + void setAssigneeLastName(String assigneeLastName)
        + String getAssigneeFirstName()
        + void setAssigneeFirstName(String assigneeFirstName)
        + List~TicklerCommentDTO~ getComments()
        + void setComments(List~TicklerCommentDTO~ comments)
        + List~TicklerLinkDTO~ getLinks()
        + void setLinks(List~TicklerLinkDTO~ links)
        + String getDemographicFormattedName()
        + String getCreatorFormattedName()
        + String getAssigneeFormattedName()
        + String getStatusDesc(Locale locale)
    }

    class TicklerCommentDTO {
        <<dto>>
        - Integer id
        - Integer ticklerNo
        - String message
        - Date updateDate
        - String providerLastName
        - String providerFirstName
        + TicklerCommentDTO()
        + TicklerCommentDTO(Integer id, Integer ticklerNo, String message, Date updateDate, String providerLastName, String providerFirstName)
        + Integer getId()
        + void setId(Integer id)
        + Integer getTicklerNo()
        + void setTicklerNo(Integer ticklerNo)
        + String getMessage()
        + void setMessage(String message)
        + Date getUpdateDate()
        + void setUpdateDate(Date updateDate)
        + String getProviderLastName()
        + void setProviderLastName(String providerLastName)
        + String getProviderFirstName()
        + void setProviderFirstName(String providerFirstName)
        + String getProviderFormattedName()
        + boolean isUpdateDateToday()
        + String getUpdateTime(Locale locale)
        + String getUpdateDateTime(Locale locale)
        + String getUpdateDate(Locale locale)
    }

    class TicklerLinkDTO {
        <<dto>>
        - Integer id
        - Integer ticklerNo
        - String tableName
        - Long tableId
        + TicklerLinkDTO()
        + TicklerLinkDTO(Integer id, Integer ticklerNo, String tableName, Long tableId)
        + Integer getId()
        + void setId(Integer id)
        + Integer getTicklerNo()
        + void setTicklerNo(Integer ticklerNo)
        + String getTableName()
        + void setTableName(String tableName)
        + Long getTableId()
        + void setTableId(Long tableId)
    }

    class TicklerDao {
        <<interface>>
        + List~Tickler~ getTicklers(CustomFilter filter)
        + int getNumTicklers(CustomFilter filter)
        + List~TicklerListDTO~ getTicklerDTOs(CustomFilter filter, int offset, int limit)
        + List~TicklerListDTO~ getTicklerDTOs(CustomFilter filter)
    }

    class TicklerDaoImpl {
        <<service>>
        - EntityManager entityManager
        + List~TicklerListDTO~ getTicklerDTOs(CustomFilter filter, int offset, int limit)
        + List~TicklerListDTO~ getTicklerDTOs(CustomFilter filter)
        - String getTicklerDTOQueryString(List~Object~ paramList, CustomFilter filter)
        - void loadCommentsForTicklerDTOs(List~TicklerListDTO~ ticklerDTOs)
        - void loadLinksForTicklerDTOs(List~TicklerListDTO~ ticklerDTOs)
    }

    class TicklerManager {
        <<interface>>
        + List~Tickler~ getTicklers(LoggedInInfo loggedInInfo, CustomFilter filter)
        + List~TicklerListDTO~ getTicklerDTOs(LoggedInInfo loggedInInfo, CustomFilter filter)
        + List~TicklerListDTO~ getTicklerDTOs(LoggedInInfo loggedInInfo, CustomFilter filter, int offset, int limit)
    }

    class TicklerManagerImpl {
        <<service>>
        - TicklerDao ticklerDao
        - SecurityInfoManager securityInfoManager
        + List~TicklerListDTO~ getTicklerDTOs(LoggedInInfo loggedInInfo, CustomFilter filter)
        + List~TicklerListDTO~ getTicklerDTOs(LoggedInInfo loggedInInfo, CustomFilter filter, int offset, int limit)
        - void checkPrivilege(LoggedInInfo loggedInInfo, String privilege)
    }

    TicklerListDTO "1" o-- "*" TicklerCommentDTO : comments
    TicklerListDTO "1" o-- "*" TicklerLinkDTO : links
    TicklerDaoImpl ..|> TicklerDao
    TicklerManagerImpl ..|> TicklerManager
    TicklerListDTO --> Tickler : uses STATUS and PRIORITY enums
    TicklerCommentDTO --> TicklerListDTO : ticklerNo
    TicklerLinkDTO --> TicklerListDTO : ticklerNo
    TicklerManagerImpl --> TicklerDao : uses
Loading

File-Level Changes

Change Details Files
Add TicklerListDTO/TicklerCommentDTO/TicklerLinkDTO projection model and DAO methods to load tickler lists with batched comments and links.
  • Introduce TicklerListDTO with constructor-based JPQL projection fields, helper formatting/status methods, and comment/link collections.
  • Introduce TicklerCommentDTO and TicklerLinkDTO as lightweight projections for comments and links, including date/name formatting helpers for comments.
  • Extend TicklerDao interface with getTicklerDTOs(filter,offset,limit) and getTicklerDTOs(filter) signatures returning TicklerListDTOs.
  • Implement getTicklerDTOs in TicklerDaoImpl using a dynamically built JPQL constructor query plus helper methods that batch-load TicklerCommentDTO and TicklerLinkDTO for all ticklers in two additional queries.
src/main/java/ca/openosp/openo/tickler/dto/TicklerListDTO.java
src/main/java/ca/openosp/openo/tickler/dto/TicklerCommentDTO.java
src/main/java/ca/openosp/openo/tickler/dto/TicklerLinkDTO.java
src/main/java/ca/openosp/openo/commn/dao/TicklerDao.java
src/main/java/ca/openosp/openo/commn/dao/TicklerDaoImpl.java
Expose DTO-based tickler retrieval through the service layer and switch TicklerMain.jsp to render from DTOs instead of entities, removing per-row DAO calls.
  • Extend TicklerManager and TicklerManagerImpl with getTicklerDTOs overloads that enforce read privilege and delegate to TicklerDao.
  • Update ticklerMain.jsp imports and bean lookups to use DTO classes instead of Tickler/TicklerComment/TicklerLink entities and remove TicklerLinkDao usage.
  • Change tickler list retrieval in ticklerMain.jsp to always use TicklerManager.getTicklerDTOs with the CustomFilter, adjusting demographic filtering to set filter.demographicNo when needed.
  • Update JSP rendering logic to use DTO fields (demographic names, creator/assignee formatted names, statusDesc, links, comments) and iterate over pre-populated DTO comments/links rather than performing per-row lookups and traversing entity relationships.
src/main/java/ca/openosp/openo/managers/TicklerManager.java
src/main/java/ca/openosp/openo/managers/TicklerManagerImpl.java
src/main/webapp/tickler/ticklerMain.jsp
Add JavaMelody and JPA test wiring plus unit/integration tests validating DTO behavior and DAO DTO projections.
  • Wire JavaMelody SpringDataSourceBeanPostProcessor into spring_jpa.xml to monitor SQL in dev and temporarily comment out the web.xml security-constraint for monitoring (with a DO NOT COMMIT note).
  • Register TicklerLink entity in test persistence.xml so link DTO integration tests can run.
  • Add TicklerDaoDTOIntegrationTest to verify getTicklerDTOs filtering, pagination, batch comment loading, null relationship handling, and priority filtering.
  • Add TicklerListDTOTest and TicklerCommentDTOTest unit tests to cover formatting helpers, null-handling, constructors, and basic getters/setters for the DTOs.
src/main/resources/spring_jpa.xml
src/main/webapp/WEB-INF/web.xml
src/test-modern/resources/META-INF/persistence.xml
src/test-modern/java/ca/openosp/openo/tickler/dao/TicklerDaoDTOIntegrationTest.java
src/test-modern/java/ca/openosp/openo/tickler/dto/TicklerListDTOTest.java
src/test-modern/java/ca/openosp/openo/tickler/dto/TicklerCommentDTOTest.java

Possibly linked issues

  • #perf: Implement DTO projection for Tickler list view performance optimization: PR implements the described DTO-based Tickler list projection, updates TicklerMain.jsp, and batch-loads comments as requested.

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@coderabbitai

coderabbitai Bot commented Jan 30, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

Adds DTO-based list retrieval for Ticklers: three new DTOs (TicklerListDTO, TicklerCommentDTO, TicklerLinkDTO), DAO and Manager methods (with optional pagination and batch-loading of comments/links), a JSP refactor to use DTOs, and accompanying unit/integration tests and persistence update.

Changes

Cohort / File(s) Summary
DAO Layer
src/main/java/ca/openosp/openo/commn/dao/TicklerDao.java, src/main/java/ca/openosp/openo/commn/dao/TicklerDaoImpl.java
Added two new API methods to return List<TicklerListDTO> (with and without offset/limit). Implementation uses JPQL projection and batch-loads comments and links to avoid N+1 queries.
Manager Layer
src/main/java/ca/openosp/openo/managers/TicklerManager.java, src/main/java/ca/openosp/openo/managers/TicklerManagerImpl.java
Added two new methods that perform privilege checks and delegate to DAO to retrieve DTO lists (paginated and unpaginated).
DTO Classes
src/main/java/ca/openosp/openo/tickler/dto/TicklerListDTO.java, src/main/java/ca/openosp/openo/tickler/dto/TicklerCommentDTO.java, src/main/java/ca/openosp/openo/tickler/dto/TicklerLinkDTO.java
New serializable DTOs with JPQL projection constructors, defensive null handling, formatting helpers, and collections for comments/links.
View
src/main/webapp/tickler/ticklerMain.jsp
Refactored to consume List<TicklerListDTO> instead of domain Tickler objects; iterates DTO comments/links; removed TicklerLinkDao usage and adjusted field accessors to DTO getters.
Tests
src/test-modern/java/ca/openosp/openo/tickler/dao/TicklerDaoDTOIntegrationTest.java, src/test-modern/java/ca/openosp/openo/tickler/dto/TicklerCommentDTOTest.java, src/test-modern/java/ca/openosp/openo/tickler/dto/TicklerListDTOTest.java
New integration and unit tests validating DTO projections, pagination, batch-loading of comments/links, formatting helpers, date handling, and null-safe behaviors.
Persistence config
src/test-modern/resources/META-INF/persistence.xml
Added TicklerLink entity to the explicit class list for tests.

Sequence Diagram

sequenceDiagram
    actor User
    participant JSP as "ticklerMain.jsp"
    participant Manager as "TicklerManager"
    participant DAO as "TicklerDao"
    participant DB as "Database"

    User->>JSP: Request tickler list
    JSP->>Manager: getTicklerDTOs(loggedInInfo, filter[, offset,limit])
    Manager->>Manager: Privilege checks / log demographic access
    Manager->>DAO: getTicklerDTOs(filter[, offset,limit])
    DAO->>DB: Execute JPQL projection -> TicklerListDTOs
    DB-->>DAO: TicklerListDTO rows
    DAO->>DB: Batch query comments for tickler IDs -> TicklerCommentDTOs
    DB-->>DAO: TicklerCommentDTOs
    DAO->>DAO: Attach comments to corresponding DTOs
    DAO->>DB: Batch query links for tickler IDs -> TicklerLinkDTOs
    DB-->>DAO: TicklerLinkDTOs
    DAO->>DAO: Attach links to corresponding DTOs
    DAO-->>Manager: Populated TicklerListDTOs
    Manager-->>JSP: DTO list
    JSP->>JSP: Render using DTO fields
    JSP-->>User: Rendered tickler list
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Suggested reviewers

  • yingbull

Poem

🐇 I nibble code with careful paws,

DTOs bundled without a pause,
Comments and links all fetched in batch,
No more N+1 in my patch,
Hoppy lists — a cleaner cause!

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.64% 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
Title check ✅ Passed The title 'Performance: DTO Projection Tickler on read operations (Tickler, Tickler Comment, Tickler Link)' directly and clearly summarizes the main change: introducing DTO projections for performance optimization on tickler read operations.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

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

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch performance/dto-projection-tickler

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.

@gemini-code-assist

Copy link
Copy Markdown

Summary of Changes

Hello @LiamStanziani, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request delivers a significant performance enhancement for tickler read operations, particularly on the 'TicklerMain' page. By implementing DTO projections and batch loading for related entities, the system now retrieves tickler data, comments, and links with drastically fewer database queries, addressing long-standing N+1 query problems and improving overall responsiveness. The changes span the data access layer, business logic, and the UI, all supported by new DTO structures and comprehensive tests.

Highlights

  • Performance Optimization via DTO Projection: Introduced Data Transfer Object (DTO) projection functionality for Tickler, Tickler Comment, and Tickler Link read operations. This change significantly reduces the number of database queries from approximately 25 per page load to just 2 queries on the 'TicklerMain' page, effectively resolving N+1 query issues caused by EAGER relationships.
  • New DTO Structures: Created three new DTO classes: TicklerListDTO, TicklerCommentDTO, and TicklerLinkDTO. These DTOs are designed to provide lightweight representations of tickler data, optimized for display in list views, and include helper methods for formatting and data access.
  • Refactored Data Access Layer: The TicklerDao and TicklerManager interfaces and their implementations have been updated to include new methods for retrieving TicklerListDTOs, leveraging JPQL constructor expressions and batch loading for associated comments and links.
  • Updated UI to use DTOs: The ticklerMain.jsp page has been refactored to consume the new TicklerListDTOs, ensuring that the UI benefits directly from the performance improvements in the data access layer.
  • Comprehensive Testing: Added new unit tests for the TicklerCommentDTO and TicklerListDTO classes, as well as a dedicated integration test suite (TicklerDaoDTOIntegrationTest) to validate the DTO projection and batch loading mechanisms, including handling of null relationships and pagination.
  • Temporary JavaMelody Configuration: Included temporary JavaMelody configuration in spring_jpa.xml and web.xml for SQL monitoring during development, with explicit notes to prevent committing these temporary changes.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a significant optimization to the Tickler list view by implementing Data Transfer Objects (DTOs) and JPQL projection queries. New DTOs (TicklerListDTO, TicklerCommentDTO, TicklerLinkDTO) have been added to provide a lightweight representation of tickler data, reducing the number of database queries from approximately 25 per page load to 3 (one for ticklers, one for comments, and one for links). The TicklerDao and TicklerManager interfaces and their implementations have been updated to support these new DTO-based retrieval methods, including pagination and batch loading of comments and links. The ticklerMain.jsp page has been refactored to consume these DTOs directly, removing previous direct entity access and separate DAO calls for related data. Additionally, new unit and integration tests have been added to validate the DTO projection and helper methods. Review comments highlight the need to remove temporary JavaMelody configuration, correct Javadoc inaccuracies regarding the number of queries performed, refactor dynamic JPQL query construction to use named parameters for safety, and remove redundant list initializations in DTOs. A potential bug was also identified where multiple demographic number conditions could be added to the query.

I am having trouble creating individual review comments. Click here to see my feedback.

src/main/resources/spring_jpa.xml (20-21)

critical

This change introduces a JavaMelody bean for development purposes, as indicated by the comment 'TEMPORARY FOR DEV'. Such development-specific configurations should not be merged into the main codebase. Please remove this bean before merging the pull request.

src/main/java/ca/openosp/openo/commn/dao/TicklerDaoImpl.java (580-588)

high

There is a potential bug in the query construction logic. If both includeClientClause and includeDemographicClause are true, the query will contain two separate AND t.demographicNo = ? conditions. This will likely lead to incorrect query results or an error, depending on the database. Please ensure that only one condition for demographicNo is added to the query, or that these filters are mutually exclusive and handled appropriately.

src/main/java/ca/openosp/openo/commn/dao/TicklerDao.java (76-78)

medium

The Javadoc states that this method reduces database queries to 'exactly 2 queries' and that 'Comments are batch-loaded'. The implementation actually performs up to three queries (one for ticklers, one for comments, and one for links) and also batch-loads links. Please update the Javadoc to accurately reflect that it performs 3 queries and batch-loads both comments and links for clarity.

     * for display, reducing database queries from ~25 per page load to 3 queries.
     * Comments and links are batch-loaded in separate queries.

src/main/java/ca/openosp/openo/commn/dao/TicklerDao.java (91-93)

medium

The Javadoc states that this method reduces database queries to 'exactly 2 queries' and that 'Comments are batch-loaded'. The implementation actually performs up to three queries (one for ticklers, one for comments, and one for links) and also batch-loads links. Please update the Javadoc to accurately reflect that it performs 3 queries and batch-loads both comments and links for clarity.

     * for display, reducing database queries from ~25 per page load to 3 queries.
     * Comments and links are batch-loaded in separate queries.

src/main/java/ca/openosp/openo/commn/dao/TicklerDaoImpl.java (453)

medium

The method getTicklerDTOQueryString dynamically constructs a JPQL query using positional parameters (?). This approach is error-prone, especially with complex conditional logic, as it's easy to mismatch parameters and their positions. For improved readability, maintainability, and safety, please refactor this to use named parameters (e.g., :paramName).

src/main/java/ca/openosp/openo/commn/dao/TicklerDaoImpl.java (616)

medium

The comments list is already initialized in the TicklerListDTO constructor. This call to dto.setComments(new ArrayList<>()) is redundant and can be removed.

src/main/java/ca/openosp/openo/commn/dao/TicklerDaoImpl.java (657)

medium

The links list is already initialized in the TicklerListDTO constructor. This call to dto.setLinks(new ArrayList<>()) is redundant and can be removed.

src/main/java/ca/openosp/openo/managers/TicklerManager.java (160-161)

medium

The Javadoc states that this method reduces database queries to 'exactly 2 queries'. The underlying implementation actually performs up to three queries (one for ticklers, one for comments, and one for links). Please update the Javadoc to accurately reflect the number of queries for clarity.

     * This method provides optimized data retrieval for list views, reducing database
     * queries from ~25 per page load (due to EAGER relationships) to 3 queries.

src/main/java/ca/openosp/openo/managers/TicklerManager.java (173-174)

medium

The Javadoc states that this method reduces database queries to 'exactly 2 queries'. The underlying implementation actually performs up to three queries (one for ticklers, one for comments, and one for links). Please update the Javadoc to accurately reflect the number of queries for clarity.

     * This method provides optimized data retrieval for list views, reducing database
     * queries from ~25 per page load (due to EAGER relationships) to 3 queries.

@LiamStanziani

Copy link
Copy Markdown
Collaborator Author

@claude review

@LiamStanziani

Copy link
Copy Markdown
Collaborator Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jan 30, 2026

Copy link
Copy Markdown
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@github-actions

github-actions Bot commented Jan 30, 2026

Copy link
Copy Markdown

Claude finished @LiamStanziani's task in 3m 0s —— View job


Code Review for DTO Projection Performance Optimization

Overview

This PR introduces a significant performance optimization for tickler read operations through DTO projection. The implementation successfully reduces database queries from ~25 per page load to 3 queries (ticklers, comments, links) by eliminating N+1 issues caused by EAGER relationships. The code quality is generally high with good test coverage, but there are several critical issues that must be addressed before merging.


Critical Issues 🔴

1. Temporary Development Code Must Be Removed

Location: Latest commit message and PR description

The commit 9f5a47bc indicates "accidental commit of testing config changes for javaMelody dev env". However, I can see JavaMelody is already integrated into the codebase in web.xml and is not a temporary addition in this PR. The PR description should clarify what temporary changes were reverted.

Action Required: Verify that all temporary development/debugging code has been removed before merging.

2. Potential Duplicate WHERE Clause Bug

Location: TicklerDaoImpl.java:580-588

if (includeClientClause) {
    query.append("AND t.demographicNo = ?").append(paramIndex++).append(" ");
    paramList.add(Integer.parseInt(filter.getClient()));
}

if (includeDemographicClause) {
    query.append("AND t.demographicNo = ?").append(paramIndex++).append(" ");
    paramList.add(Integer.parseInt(filter.getDemographicNo()));
}

Issue: If both includeClientClause and includeDemographicClause are true, the query will contain two separate AND t.demographicNo = ? conditions with potentially different values, leading to incorrect results or no results at all.

Recommendation:

  • Verify these filters are mutually exclusive in practice
  • Add defensive logic to ensure only one demographic filter is applied
  • Consider adding a comment explaining the relationship between client and demographicNo filters
  • Add integration test coverage for this edge case

Fix this →

3. Inaccurate JavaDoc - Query Count

Locations:

  • TicklerDao.java:77-78, 92-93
  • TicklerManager.java:161, 174

Current JavaDoc:

"reducing database queries from ~25 per page load to exactly 2 queries. Comments are batch-loaded"

Actual Implementation: The code performs 3 queries:

  1. Main tickler query (JPQL constructor projection)
  2. Batch comment loading (loadCommentsForTicklerDTOs)
  3. Batch link loading (loadLinksForTicklerDTOs)

Action Required: Update all JavaDoc to accurately reflect "3 queries" and mention that both comments and links are batch-loaded.


High Priority Issues 🟡

4. JPQL Query Construction Should Use Named Parameters

Location: TicklerDaoImpl.java:453-596 (getTicklerDTOQueryString method)

Current Approach: Uses positional parameters (?1, ?2, etc.) with complex parameter index tracking

Issues:

  • Error-prone with complex conditional logic
  • Difficult to maintain and debug
  • Easy to mismatch parameters with positions
  • Increases risk of SQL injection if refactored incorrectly

Recommendation: Refactor to use named parameters for improved safety and maintainability:

// Instead of:
query.append("AND t.status = ?").append(paramIndex++);
paramList.add(status);

// Use:
query.append("AND t.status = :status ");
// Then in query setup:
query.setParameter("status", status);

This is a best practice explicitly mentioned in CLAUDE.md: "Parameterized queries ONLY - never string concatenation"

Fix this →

5. Redundant List Initialization

Locations: TicklerDaoImpl.java:616, 657

dto.setComments(new ArrayList<>());  // Line 616
dto.setLinks(new ArrayList<>());      // Line 657

Issue: Both comments and links lists are already initialized in the TicklerListDTO constructor. These redundant initializations waste memory and are unnecessary.

Action Required: Remove these redundant lines.


Security Review ✅

Excellent OWASP Encoding Usage

Location: ticklerMain.jsp:877-892, 965-985

The JSP properly uses Encode.forHtmlContent() for all user-controlled data:

<%=Encode.forHtmlContent(tickler.getDemographicLastName())%>
<%=Encode.forHtmlContent(tickler.getMessage())%>
<%=Encode.forHtmlContent(tc.getMessage())%>

Compliant with CLAUDE.md security requirements

Proper Security Checks

Locations:

  • JSP: Lines 62-69 (OSCAREC security tag)
  • Manager: TicklerManagerImpl.java:773, 780 (privilege checks before DAO access)

Security implementation follows repository patterns correctly ✅


Code Quality Assessment

Strengths ✅

  1. Well-designed DTOs - Clean separation of concerns with appropriate helper methods
  2. Excellent test coverage - Comprehensive integration tests with proper BDD naming
  3. Proper use of modern test framework - Extends OpenOTestBase, uses JUnit 5 tags
  4. Batch loading implementation - Efficiently eliminates N+1 queries
  5. JavaDoc quality - Comprehensive documentation (aside from query count issue)
  6. Follows repository conventions - Proper package structure, naming conventions
  7. Security-first approach - OWASP encoding throughout JSP

Areas for Improvement

  1. Query construction should use named parameters (see Add missing method to ActionUtils #4)
  2. JavaDoc accuracy needs correction (see Added development-drugref.sql #3)
  3. Potential duplicate filter bug (see Fix method calls in DemographicRelationship #2)
  4. Minor code cleanup opportunities (see fix: re-add devcontainer openosp_files #5)

Test Coverage Review ✅

Location: src/test-modern/java/ca/openosp/openo/tickler/dao/TicklerDaoDTOIntegrationTest.java

Excellent test coverage including:

  • DTO projection with various filters
  • Pagination behavior
  • Batch comment loading
  • Null relationship handling
  • Empty result scenarios

Follows modern test framework standards:

  • Proper BDD naming: shouldReturnDTOs_whenFilterMatchesActiveTicklers
  • Appropriate tags: @Tag("integration"), @Tag("query"), @Tag("read")
  • Nested test organization with @Nested classes

Missing test case: Edge case for duplicate demographic filter (see issue #2)


Performance Impact Assessment

Before: ~25 database queries per page load (N+1 issues from EAGER relationships)

After: 3 optimized queries:

  1. Main tickler projection (with JOINs for demographic/provider data)
  2. Batch comment loading (single IN query)
  3. Batch link loading (single IN query)

Estimated improvement: ~88% reduction in database queries 🚀

This aligns with the PR's stated goal and JavaMelody testing mentioned in the PR description.


Recommendations for Merge

Must Fix Before Merge:

  1. ✅ Verify all temporary dev code is removed
  2. 🔴 Fix or document the duplicate demographic filter bug (Fix method calls in DemographicRelationship #2)
  3. 🔴 Update JavaDoc to accurately reflect 3 queries (Added development-drugref.sql #3)

Should Fix Before Merge:

  1. 🟡 Refactor to named parameters (Add missing method to ActionUtils #4) - improves maintainability and security
  2. 🟡 Remove redundant list initializations (fix: re-add devcontainer openosp_files #5)

Can Address in Follow-up:

  • Consider extracting query building logic into a query builder pattern for better testability
  • Add performance benchmarks to integration tests to catch regressions

Final Verdict

This is a well-implemented performance optimization with strong test coverage and proper security practices. The DTO projection pattern is correctly applied and will significantly improve the tickler page performance.

However, the duplicate demographic filter bug (#2) is a potential data correctness issue that must be resolved before merging. The JavaDoc inaccuracies (#3) should also be corrected for accurate developer documentation.

Once these critical issues are addressed, this PR will be ready for merge. The code quality is generally excellent and follows OpenO EMR development standards.


Overall Assessment: ✅ Approve with required changes
| View PR branch

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Implements DTO-based JPQL projections for Tickler read/list operations to reduce N+1 query overhead on the Tickler main page, and adds modern tests around the new projection behavior.

Changes:

  • Added TicklerListDTO/TicklerCommentDTO/TicklerLinkDTO and DAO/manager APIs to fetch ticklers via projection + batch-loaded related data.
  • Updated ticklerMain.jsp to render ticklers/comments/links from DTOs instead of entity graphs.
  • Added modern unit/integration tests and updated test JPA persistence config for the new entity usage.

Reviewed changes

Copilot reviewed 12 out of 12 changed files in this pull request and generated 19 comments.

Show a summary per file
File Description
src/test-modern/resources/META-INF/persistence.xml Adds TicklerLink entity to the modern test persistence unit.
src/test-modern/java/ca/openosp/openo/tickler/dto/TicklerListDTOTest.java Unit tests for TicklerListDTO helper behavior.
src/test-modern/java/ca/openosp/openo/tickler/dto/TicklerCommentDTOTest.java Unit tests for TicklerCommentDTO helper behavior.
src/test-modern/java/ca/openosp/openo/tickler/dao/TicklerDaoDTOIntegrationTest.java Integration tests validating the new DTO projection DAO methods.
src/main/webapp/tickler/ticklerMain.jsp Switches Tickler list rendering to DTOs to avoid N+1 entity loading.
src/main/webapp/WEB-INF/web.xml Modifies monitoring security constraint configuration.
src/main/resources/spring_jpa.xml Adds JavaMelody DataSource post-processor bean to Spring context.
src/main/java/ca/openosp/openo/tickler/dto/TicklerListDTO.java Introduces the tickler list DTO used by JPQL constructor projections.
src/main/java/ca/openosp/openo/tickler/dto/TicklerLinkDTO.java Introduces lightweight DTO for tickler links (batch-loaded).
src/main/java/ca/openosp/openo/tickler/dto/TicklerCommentDTO.java Introduces lightweight DTO for tickler comments (batch-loaded).
src/main/java/ca/openosp/openo/managers/TicklerManagerImpl.java Adds manager methods to call the new DAO DTO projection APIs with privilege checks.
src/main/java/ca/openosp/openo/managers/TicklerManager.java Adds manager interface methods for DTO projection retrieval (with pagination overload).
src/main/java/ca/openosp/openo/commn/dao/TicklerDaoImpl.java Implements DTO projection query + batch-loading of comments and links.
src/main/java/ca/openosp/openo/commn/dao/TicklerDao.java Adds DAO interface for DTO projection retrieval (paged + non-paged).

Comment thread src/main/java/ca/openosp/openo/tickler/dto/TicklerListDTO.java Outdated
Comment thread src/main/java/ca/openosp/openo/managers/TicklerManager.java
Comment thread src/main/java/ca/openosp/openo/commn/dao/TicklerDao.java
Comment thread src/main/java/ca/openosp/openo/commn/dao/TicklerDaoImpl.java
Comment thread src/main/java/ca/openosp/openo/commn/dao/TicklerDaoImpl.java
Comment thread src/main/java/ca/openosp/openo/commn/dao/TicklerDaoImpl.java
Comment thread src/main/java/ca/openosp/openo/tickler/dto/TicklerListDTO.java Outdated
Comment thread src/main/java/ca/openosp/openo/tickler/dto/TicklerListDTO.java Outdated
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 15

🤖 Fix all issues with AI agents
In `@src/main/java/ca/openosp/openo/commn/dao/TicklerDao.java`:
- Around line 73-99: Add `@since` tags to both public DAO methods
getTicklerDTOs(CustomFilter, int, int) and getTicklerDTOs(CustomFilter) using
the appropriate dates from git history; update each JavaDoc block to include the
`@since` tag and also ensure the `@param` tags specify the exact data types (e.g.,
CustomFilter, int offset, int limit), the `@return` tag states the exact return
type (List<TicklerListDTO>), and include `@throws` tags for any checked/runtime
exceptions the methods can propagate so the public API JavaDoc meets the project
guidelines.

In `@src/main/java/ca/openosp/openo/commn/dao/TicklerDaoImpl.java`:
- Around line 420-444: Add comprehensive JavaDoc to both public methods
getTicklerDTOs(CustomFilter, int, int) and its overload
getTicklerDTOs(CustomFilter): document each `@param` (filter: CustomFilter,
offset: int, limit: int) with expected semantics, include `@return` with the exact
type List<TicklerListDTO>, add `@throws` for runtime persistence exceptions (e.g.,
PersistenceException or runtime exceptions thrown by entityManager/query
execution), and include a `@since` tag (use the appropriate git history/tag
value). Ensure the JavaDoc mentions pagination behavior for offset/limit and
that the overload without pagination uses TicklerDao.MAX_LIST_RETURN_SIZE.
- Around line 453-595: The method getTicklerDTOQueryString currently composes
JPQL via StringBuilder; replace it with a Criteria API implementation: create a
CriteriaBuilder and CriteriaQuery<TicklerListDTO>, define Root<Tickler> t and
LEFT JOINs to Demographic d and Provider creator/assignee, build a
List<Predicate> and add predicates conditionally based on CustomFilter (use
filter.getMrp(), getStartDate(), getEndDate(), getProviders()/getAssignees(),
getProgramId(), getStatus() via convertStatus(), getPriority() via
convertPriority(), getClient(), getDemographicNo(), getMessage()), handle
endDate by setting time to 23:59:59 before creating predicate, then set the
selection to construct new TicklerListDTO(t.get("id"), t.get("message"), ...
creator/assignee/d fields), apply predicates with
criteriaQuery.where(cb.and(predicates.toArray(new Predicate[0])), create
TypedQuery from entityManager, and replace paramList use with direct parameter
values bound by Criteria (or set them on the TypedQuery) so callers expecting
the Query result can execute the typed query instead of using
getTicklerDTOQueryString.

In `@src/main/java/ca/openosp/openo/managers/TicklerManager.java`:
- Around line 157-183: Add `@since` tags to the two new public methods
getTicklerDTOs(LoggedInInfo, CustomFilter) and getTicklerDTOs(LoggedInInfo,
CustomFilter, int, int) in TicklerManager.java by looking up the commit date
from git history for when each overload was introduced and adding that date in
the JavaDoc block (e.g., "@since 2024-01-15"); keep the existing `@param` and
`@return` text intact and only append the correct `@since` line to each method's
JavaDoc.

In `@src/main/java/ca/openosp/openo/managers/TicklerManagerImpl.java`:
- Around line 771-783: The getTicklerDTOs(LoggedInInfo, CustomFilter) and
getTicklerDTOs(LoggedInInfo, CustomFilter, int, int) methods currently call
ticklerDao.getTicklerDTOs(...) directly and must enforce the same facility/role
access as getTicklers(); after checkPrivilege(...) call, retrieve DTOs from
ticklerDao.getTicklerDTOs(...), then apply the existing
ticklerFacilityFiltering(...) and/or the manager-level
filterTicklersByAccess(...) (or add a new
filterTicklerDTOsByAccess(LoggedInInfo, List<TicklerListDTO>) that mirrors
getTicklers() logic) before returning results to ensure facility/role
constraints; also annotate the TicklerManagerImpl class with `@Transactional` to
reflect patient-data modifications.

In `@src/main/java/ca/openosp/openo/tickler/dto/TicklerCommentDTO.java`:
- Around line 73-177: The public methods in TicklerCommentDTO (e.g.,
getProviderFormattedName, isUpdateDateToday, getUpdateTime(Locale),
getUpdateDateTime(Locale), getUpdateDate(Locale), and all public getters/setters
like getId/setId, getTicklerNo/setTicklerNo, getMessage/setMessage,
getUpdateDate/setUpdateDate, getProviderLastName/setProviderLastName,
getProviderFirstName/setProviderFirstName) are missing comprehensive JavaDoc;
add JavaDoc to each public method including a one-line description, `@param` for
parameters (with types, e.g., Locale locale), `@return` with exact return type,
`@throws` where applicable, and `@since` using the appropriate git commit/tag date
or id, ensuring helper methods (isUpdateDateToday, getProviderFormattedName)
also get `@since` and clear descriptions to satisfy coding guidelines.

In `@src/main/java/ca/openosp/openo/tickler/dto/TicklerLinkDTO.java`:
- Around line 62-94: The public accessor methods in TicklerLinkDTO (getId,
setId, getTicklerNo, setTicklerNo, getTableName, setTableName, getTableId,
setTableId) lack the required JavaDoc; update each public getter/setter with
full JavaDoc including a one-line description, an `@return` tag with the exact
return type (e.g., Integer, String, Long) for getters, an `@param` tag with the
exact parameter type for setters, an `@since` tag populated using the git history
date for when the method was introduced, and include `@throws` tags only if the
method can throw checked exceptions (none in these accessors). Ensure the
JavaDoc format matches project guidelines for all listed methods in
TicklerLinkDTO.

In `@src/main/java/ca/openosp/openo/tickler/dto/TicklerListDTO.java`:
- Around line 172-295: The public getters/setters in TicklerListDTO (e.g.,
getId, setId, getMessage, setMessage, getServiceDate, setServiceDate,
getCreateDate, setCreateDate, getStatus, setStatus, getPriority, setPriority,
getDemographicNo, setDemographicNo, getDemographicLastName,
setDemographicLastName, getDemographicFirstName, setDemographicFirstName,
getCreatorLastName, setCreatorLastName, getCreatorFirstName,
setCreatorFirstName, getAssigneeLastName, setAssigneeLastName,
getAssigneeFirstName, setAssigneeFirstName, getComments, setComments, getLinks,
setLinks) lack the required JavaDoc; add Javadoc blocks for each public method
including a concise description, `@param` for setters (with exact data types),
`@return` for getters (with exact return types), any applicable `@throws`, and an
`@since` tag populated from the git history date for when the method was
introduced; ensure setComments and setLinks note the null-to-empty-list behavior
in the description and `@param`, and apply the same JavaDoc style consistently
across all those methods in TicklerListDTO.

In `@src/main/webapp/tickler/ticklerMain.jsp`:
- Around line 875-878: The onclick handler and the JavaScript URL parameters
embed unencoded values; update the popupPage and openNoteDialog invocations to
wrap dynamic values with OWASP Encoder's Encode.forUriComponent() (e.g. use
Encode.forUriComponent(tickler.getDemographicNo()) and
Encode.forUriComponent(tickler.getId())) so the demographic_no and id URL
parameters are safely encoded in the onClick attribute and in openNoteDialog
calls; ensure you replace the raw tickler.getDemographicNo() and tickler.getId()
usages in the popupPage(...) and openNoteDialog(...) argument strings with the
encoded variants.

In
`@src/test-modern/java/ca/openosp/openo/tickler/dao/TicklerDaoDTOIntegrationTest.java`:
- Around line 118-133: The test
shouldReturnEmptyList_whenFilterMatchesNoTicklers currently doesn’t assert
emptiness; make the filter guaranteed to match nothing (e.g., use clearly
impossible values on CustomFilter such as
filter.setStatus("NON_EXISTENT_STATUS") and
filter.setPriority("NON_EXISTENT_PRIORITY") or set an impossible identifier) and
replace the loose assertions with assertThat(results).isEmpty(); reference the
existing CustomFilter instance and the ticklerDao.getTicklerDTOs(...) call so
the test deterministically verifies an empty result.
- Around line 54-60: The test class TicklerDaoDTOIntegrationTest is missing the
required `@Tag`("dao") annotation; update the class declaration in
TicklerDaoDTOIntegrationTest (the class that extends
TicklerDaoBaseIntegrationTest) to include `@Tag`("dao") alongside the existing
tags so Maven test filtering picks it up (i.e., add `@Tag`("dao") to the
annotation list above the class).

In
`@src/test-modern/java/ca/openosp/openo/tickler/dto/TicklerCommentDTOTest.java`:
- Line 42: The test class TicklerCommentDTOTest must extend the modern OpenO
test base; update its declaration to extend OpenOUnitTestBase (instead of a
plain class) so SpringUtils mocking and JUnit5 modern test conventions work;
ensure the appropriate import for OpenOUnitTestBase is added and keep the class
as a unit test (use OpenOUnitTestBase rather than OpenOTestBase).
- Around line 37-40: The test class TicklerCommentDTOTest is missing a standard
CRUD/filter tag; update the class-level `@Tag` annotations (those currently
`@Tag`("unit"), `@Tag`("fast"), `@Tag`("tickler"), `@Tag`("dto")) to include at least
one standard tag such as `@Tag`("read") (or another from the allowed set:
"integration","dao","create","read","update","delete") so modern Maven test
filters will pick it up; keep the existing custom tags and add the chosen
standard tag annotation to the class declaration in TicklerCommentDTOTest.

In `@src/test-modern/java/ca/openosp/openo/tickler/dto/TicklerListDTOTest.java`:
- Line 43: The test class TicklerListDTOTest does not extend the required OpenO
test base; change its declaration to extend the appropriate base (likely
OpenOUnitTestBase for a pure unit test) so SpringUtils and modern test behaviors
work correctly. Locate the class TicklerListDTOTest and update its declaration
to extend OpenOUnitTestBase (or OpenOTestBase if this is an integration test),
and ensure any necessary imports for the chosen base are added and compile. Keep
the original BDD-style test method names intact and run the unit tests to verify
SpringUtils-related mocks behave as expected.
- Around line 38-41: The test class TicklerListDTOTest uses only custom tags
(`@Tag`("unit"), `@Tag`("fast"), `@Tag`("tickler"), `@Tag`("dto")) which aren't
recognized by standard Maven filters; update the class annotations (the `@Tag`
lines in TicklerListDTOTest) to include at least one standard CRUD/filter tag
such as `@Tag`("read") (you may also add `@Tag`("create"), `@Tag`("update"),
`@Tag`("delete"), or `@Tag`("integration") as needed) while keeping the existing
custom tags so modern test filtering picks up this test.

Comment thread src/main/java/ca/openosp/openo/commn/dao/TicklerDao.java Outdated
Comment thread src/main/java/ca/openosp/openo/commn/dao/TicklerDaoImpl.java
Comment thread src/main/java/ca/openosp/openo/commn/dao/TicklerDaoImpl.java
Comment thread src/main/java/ca/openosp/openo/managers/TicklerManager.java Outdated
Comment thread src/main/java/ca/openosp/openo/managers/TicklerManagerImpl.java
@LiamStanziani

Copy link
Copy Markdown
Collaborator Author

@coderabbitai review

@LiamStanziani

Copy link
Copy Markdown
Collaborator Author

@claude review

@coderabbitai

coderabbitai Bot commented Jan 30, 2026

Copy link
Copy Markdown
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@LiamStanziani LiamStanziani requested a review from Copilot January 30, 2026 21:37
@github-actions

github-actions Bot commented Jan 30, 2026

Copy link
Copy Markdown

Claude finished @LiamStanziani's task in 2m 36s —— View job


Code Review Complete ✅

I've completed a comprehensive review of PR #2257. This is a well-implemented performance optimization that successfully addresses N+1 query issues through DTO projections. The code quality is generally excellent with proper security controls and comprehensive test coverage.


Summary

Overall Assessment: ✅ APPROVE with minor recommendations

This PR delivers on its promise to reduce database queries from ~25 to 3 per page load through well-designed DTO projection. The implementation follows OpenO EMR coding standards with proper security checks, OWASP encoding, and audit logging. The test coverage is comprehensive with both unit and integration tests following BDD naming conventions.

Performance Impact: 🚀 ~88% reduction in database queries (from ~25 to 3)


Strengths

1. Excellent DTO Design

  • Clean separation between domain entities and DTOs
  • Proper defensive null handling in formatters (getDemographicFormattedName(), etc.)
  • Helper methods for display logic (getStatusDesc(), isUpdateDateToday())
  • Implements Serializable for framework compatibility
  • JPQL constructor projection pattern correctly applied

2. Proper Security Implementation

The code follows all OpenO EMR security requirements:

OWASP Encoding (ticklerMain.jsp):

<%=Encode.forHtmlContent(tickler.getDemographicLastName())%>
<%=Encode.forHtmlContent(tickler.getMessage())%>
<%=Encode.forUriComponent(String.valueOf(tickler.getDemographicNo()))%>
<%=Encode.forJavaScript(String.valueOf(tickler.getId()))%>

✅ All user-controlled data properly encoded for context

Authorization Checks (TicklerManagerImpl.java:778, 800):

checkPrivilege(loggedInInfo, PRIVILEGE_READ);

✅ Proper SecurityInfoManager.hasPrivilege() checks before data access

Parameterized Queries (TicklerDaoImpl.java:436-437):

Query query = entityManager.createQuery(sql);
for (int x = 0; x < paramList.size(); x++) {
    query.setParameter(x + 1, paramList.get(x));
}

✅ All query parameters properly bound, preventing SQL injection

Audit Logging (TicklerManagerImpl.java:784-787):

for (TicklerListDTO dto : result) {
    LogAction.addLogSynchronous(loggedInInfo, "TicklerManager.getTicklerDTOs",
            "ticklerId=" + dto.getId());
}

✅ PHI access properly logged when filtering by demographic

3. Efficient Batch Loading Pattern

The DAO implementation correctly eliminates N+1 queries:

Main Query (TicklerDaoImpl.java:494-502):

SELECT NEW ca.openosp.openo.tickler.dto.TicklerListDTO(...)
FROM Tickler t 
LEFT JOIN Demographic d ON d.DemographicNo = t.demographicNo 
LEFT JOIN Provider creator ON creator.ProviderNo = t.creator 
LEFT JOIN Provider assignee ON assignee.ProviderNo = t.taskAssignedTo

Batch Comment Loading (lines 632-642):

WHERE c.ticklerNo IN (:ticklerIds)

Batch Link Loading (lines 673-680):

WHERE l.ticklerNo IN (:ticklerIds)

This achieves exactly 3 queries as claimed, properly reducing the EAGER relationship overhead.

4. Comprehensive Test Coverage

Modern JUnit 5 tests with proper organization:

Test Class Structure (TicklerDaoDTOIntegrationTest.java):

  • ✅ Extends TicklerDaoBaseIntegrationTest (Spring context handling)
  • ✅ Proper tags: @Tag("integration"), @Tag("dao"), @Tag("read"), @Tag("dto")
  • ✅ BDD naming: shouldReturnDTOs_whenFilterMatchesActiveTicklers()
  • @Nested classes for logical grouping
  • ✅ Tests for projection, pagination, batch loading, null handling

Unit Tests:

  • TicklerListDTOTest - tests formatter methods and getters/setters
  • TicklerCommentDTOTest - tests date formatting and name helpers

5. Proper Package Structure

  • New DTOs in ca.openosp.openo.tickler.dto.* package
  • Follows OpenO EMR namespace conventions
  • Clean separation from entity models

Issues Found

Critical Issues: None ✅

The code is production-ready with no blocking issues.

Recommendations for Future Enhancement

1. Consider Defensive Copying for Collections (Minor)

Location: TicklerListDTO.java:282-283, 290-291

The getters for comments and links expose internal mutable lists. While the setters create defensive copies, the getters don't:

public List<TicklerCommentDTO> getComments() {
    return comments;  // Exposes internal list
}

Recommendation: For strict immutability, consider defensive copying in getters as well:

public List<TicklerCommentDTO> getComments() {
    return new ArrayList<>(comments);
}

Impact: Low - DTOs are typically used in read-only contexts, but defensive copying would prevent accidental modification.

2. Potential for Query Optimization (Enhancement)

Location: TicklerDaoImpl.java:467-566

The query builder uses positional parameters (?1, ?2, etc.) with manual index tracking. While secure (parameters are properly bound), named parameters could improve maintainability:

// Current approach (positional)
query.append("AND t.status = ?").append(paramIndex++);
paramList.add(status);

// Alternative (named parameters)
query.append("AND t.status = :status ");
query.setParameter("status", status);

Benefit: Easier to maintain and less error-prone for complex conditional queries.

Trade-off: The current approach works correctly and is already implemented. This is an enhancement for future refactoring, not a bug.


Performance Analysis

Verified Performance Claims ✅

Before: ~25 database queries per page load

  • 1 query to fetch ticklers
  • ~12 queries for EAGER-loaded tickler relationships (creator, assignee, demographic)
  • N queries for comments (where N = number of ticklers)
  • N queries for links (where N = number of ticklers)

After: Exactly 3 queries

  1. Main JPQL projection with LEFT JOINs (TicklerDaoImpl.java:494-502)
  2. Batch comment loading with IN clause (lines 632-642)
  3. Batch link loading with IN clause (lines 673-680)

Reduction: ~88% fewer database queries 🚀

This matches the PR description's claim of reducing queries from ~25 to 3 (not 2 as mentioned in some JavaDoc, but 3 is still accurate).


Code Review by Category

Security: ✅ EXCELLENT

  • OWASP encoding applied correctly (context-appropriate: forHtml, forJavaScript, forUriComponent)
  • Parameterized queries prevent SQL injection
  • Authorization checks before data access
  • Audit logging for PHI access
  • No PHI exposed in logs or error messages

Code Quality: ✅ EXCELLENT

  • Clean DTO design with proper encapsulation
  • Defensive null handling throughout
  • Helper methods for display logic
  • Proper package structure following OpenO conventions
  • Consistent coding style

Test Coverage: ✅ EXCELLENT

  • Comprehensive integration tests
  • Unit tests for DTO helpers
  • Proper BDD naming conventions
  • Appropriate use of tags for test filtering
  • Tests for edge cases (null handling, empty results, pagination)

Documentation: ✅ GOOD

  • JavaDoc present on all classes and methods
  • @since tags with dates
  • Parameter types documented
  • Return types specified
  • Clear explanations of purpose

Minor note: The class-level JavaDoc in TicklerListDTO and some tests mention "exactly 2 queries" but the implementation actually uses 3 queries (ticklers + comments + links). This is documented correctly in other places and doesn't affect functionality.

JSP Review: ✅ SECURE

  • All dynamic content properly encoded
  • JavaScript encoding for onClick handlers
  • URI encoding for URL parameters
  • No XSS vulnerabilities detected

Comparison with Other Reviews

I've reviewed the feedback from other AI reviewers (sourcery-ai, coderabbitai, gemini-code-assist, copilot). Most suggestions fall into these categories:

  1. JavaDoc accuracy - The "2 vs 3 queries" discrepancy has been discussed in comments
  2. Criteria API refactoring - The current parameterized query approach is secure and functional; Criteria API would be a nice-to-have enhancement
  3. Test improvements - Most suggestions are already addressed in recent commits

The code quality is already high, and the security implementation is excellent.


Final Verdict

APPROVED

This PR is ready to merge. It delivers significant performance improvements while maintaining OpenO EMR's high security and code quality standards.

Key Achievements:

  • ✅ ~88% reduction in database queries
  • ✅ Comprehensive security implementation (OWASP encoding, parameterized queries, auth checks, audit logging)
  • ✅ Excellent test coverage with modern JUnit 5 tests
  • ✅ Clean DTO design following best practices
  • ✅ No security vulnerabilities detected
  • ✅ Follows OpenO EMR coding conventions

Recommendations for follow-up (optional enhancements):

  • Consider defensive copying in DTO getters for immutability
  • Future refactoring could use named parameters for improved maintainability
  • Update JavaDoc to consistently mention "3 queries" instead of "2 queries"

Great work on this performance optimization! The reduction from ~25 to 3 queries will significantly improve the user experience on the TicklerMain page. 🚀


| View PR branch

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 12 out of 12 changed files in this pull request and generated 9 comments.

Comment thread src/main/java/ca/openosp/openo/commn/dao/TicklerDaoImpl.java
Comment thread src/main/webapp/tickler/ticklerMain.jsp Outdated
Comment thread src/main/webapp/tickler/ticklerMain.jsp
Comment thread src/main/java/ca/openosp/openo/commn/dao/TicklerDaoImpl.java
Comment thread src/main/java/ca/openosp/openo/commn/dao/TicklerDaoImpl.java
Comment thread src/main/java/ca/openosp/openo/commn/dao/TicklerDaoImpl.java
Comment thread src/main/java/ca/openosp/openo/commn/dao/TicklerDaoImpl.java
Comment thread src/main/java/ca/openosp/openo/tickler/dto/TicklerListDTO.java
Comment thread src/main/java/ca/openosp/openo/tickler/dto/TicklerListDTO.java
@LiamStanziani LiamStanziani marked this pull request as ready for review January 30, 2026 22:14

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Sorry @LiamStanziani, you have reached your weekly rate limit of 500000 diff characters.

Please try again later or upgrade to continue using Sourcery

@LiamStanziani

Copy link
Copy Markdown
Collaborator Author

Will need to re-test, but want to see a final review again

@qodo-code-review

qodo-code-review Bot commented Jan 30, 2026

Copy link
Copy Markdown

PR Compliance Guide 🔍

Below is a summary of compliance checks for this PR:

Security Compliance
🟢
No security concerns identified No security vulnerabilities detected by AI analysis. Human verification advised for critical code.
Ticket Compliance
🎫 No ticket provided
  • Create ticket/issue
Codebase Duplication Compliance
Codebase context is not defined

Follow the guide to enable codebase context checks.

Custom Compliance
🟢
Generic: Meaningful Naming and Self-Documenting Code

Objective: Ensure all identifiers clearly express their purpose and intent, making code
self-documenting

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Secure Error Handling

Objective: To prevent the leakage of sensitive system information through error messages while
providing sufficient detail for internal debugging.

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Secure Logging Practices

Objective: To ensure logs are useful for debugging and auditing without exposing sensitive
information like PII, PHI, or cardholder data.

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

🔴
Generic: Robust Error Handling and Edge Case Management

Objective: Ensure comprehensive error handling that provides meaningful context and graceful
degradation

Status:
Potential null dereference: The new DTO rendering path calls tickler.getServiceDate().toInstant() without guarding
against a null serviceDate, which can cause runtime errors when data is missing or
inconsistent.

Referred Code
for (TicklerListDTO tickler : ticklers) {
        LocalDateTime serviceDate = tickler.getServiceDate().toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime();
        LocalDateTime currentDate = LocalDateTime.now();

        long daysDifference = Duration.between(serviceDate, currentDate).toDays();
        long ticklerWarnDays = Long.parseLong(numDaysUntilWarn);
        boolean ignoreWarning = (ticklerWarnDays <= 0);

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Comprehensive Audit Trails

Objective: To create a detailed and reliable record of critical system actions for security analysis
and compliance.

Status:
Partial read auditing: The new DTO-based tickler read path only writes audit logs when filter.getDemographicNo()
is set, so other tickler list reads may not be auditable depending on policy expectations
for sensitive-data access.

Referred Code
public List<TicklerListDTO> getTicklerDTOs(LoggedInInfo loggedInInfo, CustomFilter filter) {
    checkPrivilege(loggedInInfo, PRIVILEGE_READ);

    List<TicklerListDTO> result = ticklerDao.getTicklerDTOs(filter);

    // Log access when filtering by demographic (matches search_tickler_bydemo behavior)
    if (filter.getDemographicNo() != null && !filter.getDemographicNo().isEmpty()) {
        for (TicklerListDTO dto : result) {
            LogAction.addLogSynchronous(loggedInInfo, "TicklerManager.getTicklerDTOs",
                    "ticklerId=" + dto.getId());
        }
    }

    return result;
}

/**
 * {@inheritDoc}
 *
 * @since 2026-01-30
 */


 ... (clipped 16 lines)

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Security-First Input Validation and Data Handling

Objective: Ensure all data inputs are validated, sanitized, and handled securely to prevent
vulnerabilities

Status:
Pagination input validation: The new getTicklerDTOs(filter, offset, limit) path does not visibly validate that offset
and limit are non-negative and bounded, which may allow unexpected behavior or errors
depending on callers.

Referred Code
public List<TicklerListDTO> getTicklerDTOs(CustomFilter filter, int offset, int limit) {
    ArrayList<Object> paramList = new ArrayList<Object>();
    String sql = getTicklerDTOQueryString(paramList, filter);

    Query query = entityManager.createQuery(sql);
    for (int x = 0; x < paramList.size(); x++) {
        query.setParameter(x + 1, paramList.get(x));
    }
    query.setFirstResult(offset);
    setLimit(query, limit);

Learn more about managing compliance generic rules or creating your own custom rules

  • Update
Compliance status legend 🟢 - Fully Compliant
🟡 - Partial Compliant
🔴 - Not Compliant
⚪ - Requires Further Human Verification
🏷️ - Compliance label

@qodo-code-review

qodo-code-review Bot commented Jan 30, 2026

Copy link
Copy Markdown

PR Code Suggestions ✨

Explore these optional code suggestions:

CategorySuggestion                                                                                                                                    Impact
Possible issue
Prevent mutually exclusive query conditions

Use an if-else if block to prevent adding conflicting t.demographicNo conditions
to the query when both filter.getClient() and filter.getDemographicNo() are set.

src/main/java/ca/openosp/openo/commn/dao/TicklerDaoImpl.java [551-559]

-if (includeClientClause) {
+if (includeDemographicClause) {
+    query.append("AND t.demographicNo = ?").append(paramIndex++).append(" ");
+    paramList.add(Integer.parseInt(filter.getDemographicNo()));
+} else if (includeClientClause) {
     query.append("AND t.demographicNo = ?").append(paramIndex++).append(" ");
     paramList.add(Integer.parseInt(filter.getClient()));
 }
 
-if (includeDemographicClause) {
-    query.append("AND t.demographicNo = ?").append(paramIndex++).append(" ");
-    paramList.add(Integer.parseInt(filter.getDemographicNo()));
-}
-
  • Apply / Chat
Suggestion importance[1-10]: 8

__

Why: This is a valid bug fix that prevents generating a logically incorrect SQL query when two different demographic filter properties are active, which would lead to incorrect or no results.

Medium
Prevent empty IN clauses

Add a guard clause to the appendInClause method to check if the providers set is
empty, preventing the generation of an invalid IN () SQL clause.

src/main/java/ca/openosp/openo/commn/dao/TicklerDaoImpl.java [601-614]

 private void appendInClause(StringBuilder query, List<Object> paramList, String fieldName,
                             Set<Provider> providers, int paramIndex) {
+    if (providers == null || providers.isEmpty()) {
+        // skip empty IN clause
+        return;
+    }
     query.append("AND ").append(fieldName).append(" IN (");
     int i = 0;
     for (Provider provider : providers) {
         if (i > 0) {
             query.append(",");
         }
         query.append("?").append(paramIndex + i);
         paramList.add(provider.getProviderNo());
         i++;
     }
     query.append(") ");
 }
  • Apply / Chat
Suggestion importance[1-10]: 7

__

Why: This suggestion correctly identifies a potential bug where an empty providers set would generate an invalid IN () clause in the SQL query, preventing a runtime error.

Medium
High-level
Refactor duplicated query logic

The getTicklerDTOQueryString method duplicates query-building logic from
getTicklerQueryString. This should be refactored into a shared helper method to
improve maintainability and reduce redundancy.

Examples:

src/main/java/ca/openosp/openo/commn/dao/TicklerDaoImpl.java [467-572]
    private String getTicklerDTOQueryString(List<Object> paramList, CustomFilter filter) {
        int paramIndex = 1;

        boolean includeMRPClause = filter.getMrp() != null
                && !filter.getMrp().isEmpty()
                && !"All Providers".equals(filter.getMrp());
        boolean includeProviderClause = filter.getProvider() != null
                && !filter.getProvider().isEmpty()
                && !"All Providers".equals(filter.getProvider());
        boolean includeAssigneeClause = filter.getAssignee() != null

 ... (clipped 96 lines)

Solution Walkthrough:

Before:

// In TicklerDaoImpl.java

private String getTicklerDTOQueryString(List<Object> paramList, CustomFilter filter) {
    // ... ~50 lines of logic to check filter conditions ...
    StringBuilder query = new StringBuilder();
    query.append("SELECT NEW ca.openosp.openo.tickler.dto.TicklerListDTO(...) ...");
    // ... ~60 lines of logic to build WHERE clause from filter ...
    // e.g., if (includeMRPClause) { ... }
    // e.g., if (includeProviderClause) { ... }
    query.append("ORDER BY t.serviceDate ...");
    return query.toString();
}

private String getTicklerQueryString(String selectQuery, List<Object> paramList, CustomFilter filter) {
    // ... (clipped) ~130 lines of nearly identical logic to build WHERE and ORDER BY clauses ...
    return query.toString();
}

After:

// In TicklerDaoImpl.java

private String getTicklerDTOQueryString(List<Object> paramList, CustomFilter filter) {
    StringBuilder query = new StringBuilder("SELECT NEW ca.openosp.openo.tickler.dto.TicklerListDTO(...) FROM Tickler t LEFT JOIN ...");
    appendFilterConditions(query, paramList, filter);
    return query.toString();
}

private String getTicklerQueryString(String selectQuery, List<Object> paramList, CustomFilter filter) {
    StringBuilder query = new StringBuilder(selectQuery);
    appendFilterConditions(query, paramList, filter);
    return query.toString();
}

private void appendFilterConditions(StringBuilder query, List<Object> paramList, CustomFilter filter) {
    // ... ~110 lines of shared logic to build WHERE and ORDER BY clauses ...
    // e.g., if (includeMRPClause) { ... }
    // e.g., if (includeProviderClause) { ... }
    query.append("ORDER BY t.serviceDate ...");
}
Suggestion importance[1-10]: 7

__

Why: The suggestion correctly identifies significant logic duplication between the new getTicklerDTOQueryString and existing getTicklerQueryString methods, which creates a high risk for future maintenance and bugs.

Medium
General
Add secondary sort key
Suggestion Impact:The ORDER BY clause was updated to include a secondary sort on t.id (added as ", t.id DESC") to ensure deterministic ordering when serviceDate values are equal.

code diff:

         // ORDER BY clause for deterministic results and stable pagination
+        // Secondary sort by id ensures consistent ordering when serviceDates are equal
         String orderBy = "ORDER BY t.serviceDate ";
-        orderBy += "desc".equalsIgnoreCase(filter.getSort_order()) ? "DESC " : "ASC ";
+        orderBy += "desc".equalsIgnoreCase(filter.getSort_order()) ? "DESC" : "ASC";
+        orderBy += ", t.id DESC ";
         query.append(orderBy);

Add t.id as a secondary sort key to the ORDER BY clause to ensure deterministic
ordering and stable pagination results.

src/main/java/ca/openosp/openo/commn/dao/TicklerDaoImpl.java [567-569]

 String orderBy = "ORDER BY t.serviceDate ";
-orderBy += "desc".equalsIgnoreCase(filter.getSort_order()) ? "DESC " : "ASC ";
+orderBy += "desc".equalsIgnoreCase(filter.getSort_order()) ? "DESC" : "ASC";
+orderBy += ", t.id ";
 query.append(orderBy);

[Suggestion processed]

Suggestion importance[1-10]: 6

__

Why: The suggestion correctly identifies that sorting by a non-unique column can lead to inconsistent pagination and improves query stability by adding a unique secondary sort key.

Low
Improve DTO encapsulation with defensive copying

Improve DTO encapsulation by having the getComments() and getLinks() methods
return a new copy of the internal lists, preventing external code from modifying
the DTO's state.

src/main/java/ca/openosp/openo/tickler/dto/TicklerListDTO.java [286-296]

+public List<TicklerCommentDTO> getComments() {
+    return new ArrayList<>(comments);
+}
+
 public void setComments(List<TicklerCommentDTO> comments) {
     this.comments = comments != null ? new ArrayList<>(comments) : new ArrayList<>();
+}
+
+public List<TicklerLinkDTO> getLinks() {
+    return new ArrayList<>(links);
 }
 
 public void setLinks(List<TicklerLinkDTO> links) {
     this.links = links != null ? new ArrayList<>(links) : new ArrayList<>();
 }
  • Apply / Chat
Suggestion importance[1-10]: 4

__

Why: The suggestion correctly points out that returning a direct reference to internal lists from getters breaks encapsulation. Returning a copy improves the DTO's robustness against unintended side effects.

Low
Security
Prevent potential log injection vulnerability

Use String.format or a parameterized logger to construct the log message,
preventing potential log injection vulnerabilities if the data type of
dto.getId() changes in the future.

src/main/java/ca/openosp/openo/managers/TicklerManagerImpl.java [782-788]

 // Log access when filtering by demographic (matches search_tickler_bydemo behavior)
 if (filter.getDemographicNo() != null && !filter.getDemographicNo().isEmpty()) {
     for (TicklerListDTO dto : result) {
-        LogAction.addLogSynchronous(loggedInInfo, "TicklerManager.getTicklerDTOs",
-                "ticklerId=" + dto.getId());
+        // Use String.format to prevent log injection vulnerabilities
+        String logDetails = String.format("ticklerId=%d", dto.getId());
+        LogAction.addLogSynchronous(loggedInInfo, "TicklerManager.getTicklerDTOs", logDetails);
     }
 }
  • Apply / Chat
Suggestion importance[1-10]: 2

__

Why: The suggestion correctly identifies that parameterized logging is a best practice, but overstates the risk by calling it a vulnerability, as the logged value dto.getId() is an integer and poses no injection risk.

Low
Possible issue
Avoid null provider set crashes

Refactor the query-building logic to prevent a NullPointerException by checking
if the filter.getProviders() and filter.getAssignees() sets are non-null and not
empty before attempting to access them.

src/main/java/ca/openosp/openo/commn/dao/TicklerDaoImpl.java [473-534]

-boolean includeProviderClause = filter.getProvider() != null
-        && !filter.getProvider().isEmpty()
-        && !"All Providers".equals(filter.getProvider());
-boolean includeAssigneeClause = filter.getAssignee() != null
-        && !filter.getAssignee().isEmpty()
-        && !"All Providers".equals(filter.getAssignee());
+Set<Provider> providers = filter.getProviders();
+Set<Provider> assignees = filter.getAssignees();
+
+boolean includeProviderClause = providers != null && !providers.isEmpty();
+boolean includeAssigneeClause = assignees != null && !assignees.isEmpty();
 ...
 if (includeProviderClause) {
-    appendInClause(query, paramList, "t.creator", filter.getProviders(), paramIndex);
-    paramIndex += filter.getProviders().size();
+    appendInClause(query, paramList, "t.creator", providers, paramIndex);
+    paramIndex += providers.size();
 }
 
 if (includeAssigneeClause) {
-    appendInClause(query, paramList, "t.taskAssignedTo", filter.getAssignees(), paramIndex);
-    paramIndex += filter.getAssignees().size();
+    appendInClause(query, paramList, "t.taskAssignedTo", assignees, paramIndex);
+    paramIndex += assignees.size();
 }

[To ensure code accuracy, apply this suggestion manually]

Suggestion importance[1-10]: 8

__

Why: This suggestion correctly identifies a potential NullPointerException if the filter object has string flags set for providers/assignees but the corresponding Set objects are null. The proposed change to base the logic on the Sets themselves makes the code more robust and prevents a runtime crash.

Medium
Filter null provider numbers

In the appendInClause method, add a check to skip Provider objects that have a
null provider number (providerNo) to avoid adding null parameters to the SQL IN
clause.

src/main/java/ca/openosp/openo/commn/dao/TicklerDaoImpl.java [601-614]

 private void appendInClause(StringBuilder query, List<Object> paramList, String fieldName,
                             Set<Provider> providers, int paramIndex) {
-    query.append("AND ").append(fieldName).append(" IN (");
     int i = 0;
+    StringBuilder inClause = new StringBuilder();
+
     for (Provider provider : providers) {
+        if (provider == null || provider.getProviderNo() == null) {
+            continue;
+        }
         if (i > 0) {
-            query.append(",");
+            inClause.append(",");
         }
-        query.append("?").append(paramIndex + i);
+        inClause.append("?").append(paramIndex + i);
         paramList.add(provider.getProviderNo());
         i++;
     }
-    query.append(") ");
+
+    if (i == 0) {
+        return;
+    }
+
+    query.append("AND ").append(fieldName).append(" IN (").append(inClause).append(") ");
 }
  • Apply / Chat
Suggestion importance[1-10]: 6

__

Why: The suggestion correctly points out that a Provider object in the set might have a null provider number, which could lead to adding null to the query parameters. Adding a check to skip such providers makes the IN clause generation more robust and prevents potential query issues.

Low
Make DTO name getters null-safe

Modify the name-related getter methods in TicklerListDTO to return an empty
string ("") instead of null to prevent potential rendering issues or
NullPointerExceptions.

src/main/java/ca/openosp/openo/tickler/dto/TicklerListDTO.java [234-280]

 public String getDemographicLastName() {
-    return demographicLastName;
+    return demographicLastName != null ? demographicLastName : "";
 }
 
 public void setDemographicLastName(String demographicLastName) {
     this.demographicLastName = demographicLastName;
 }
 
 public String getDemographicFirstName() {
-    return demographicFirstName;
+    return demographicFirstName != null ? demographicFirstName : "";
 }
 
 public void setDemographicFirstName(String demographicFirstName) {
     this.demographicFirstName = demographicFirstName;
 }
 
 public String getCreatorLastName() {
-    return creatorLastName;
+    return creatorLastName != null ? creatorLastName : "";
 }
 
 public void setCreatorLastName(String creatorLastName) {
     this.creatorLastName = creatorLastName;
 }
 
 public String getCreatorFirstName() {
-    return creatorFirstName;
+    return creatorFirstName != null ? creatorFirstName : "";
 }
 
 public void setCreatorFirstName(String creatorFirstName) {
     this.creatorFirstName = creatorFirstName;
 }
 
 public String getAssigneeLastName() {
-    return assigneeLastName;
+    return assigneeLastName != null ? assigneeLastName : "";
 }
 
 public void setAssigneeLastName(String assigneeLastName) {
     this.assigneeLastName = assigneeLastName;
 }
 
 public String getAssigneeFirstName() {
-    return assigneeFirstName;
+    return assigneeFirstName != null ? assigneeFirstName : "";
 }
 
 public void setAssigneeFirstName(String assigneeFirstName) {
     this.assigneeFirstName = assigneeFirstName;
 }
  • Apply / Chat
Suggestion importance[1-10]: 5

__

Why: The suggestion correctly identifies that LEFT JOINs can result in null name fields in the DTO, and the JSP directly accesses getDemographicLastName() and getDemographicFirstName(). While Encode.forHtmlContent handles null gracefully, making these getters null-safe is a good defensive practice to prevent potential NullPointerExceptions in other contexts.

Low
  • Update

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

1 issue found across 12 files

Confidence score: 4/5

  • Minor UI formatting risk in src/main/java/ca/openosp/openo/tickler/dto/TicklerCommentDTO.java: provider name can render with a leading/trailing comma when only one name part is present.
  • Overall impact appears small and localized, so this PR is likely safe to merge with minimal risk.
  • Pay close attention to src/main/java/ca/openosp/openo/tickler/dto/TicklerCommentDTO.java - provider name formatting logic may produce stray commas.
Prompt for AI agents (all issues)

Check if these issues are valid — if so, understand the root cause of each and fix them.


<file name="src/main/java/ca/openosp/openo/tickler/dto/TicklerCommentDTO.java">

<violation number="1" location="src/main/java/ca/openosp/openo/tickler/dto/TicklerCommentDTO.java:84">
P3: Avoid returning a leading/trailing comma when only one of the provider name parts is present; it will render as ", John" or "Doe, " in the UI.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

Comment thread src/main/java/ca/openosp/openo/tickler/dto/TicklerCommentDTO.java Outdated
@claude

claude Bot commented Jan 30, 2026

Copy link
Copy Markdown

Code review

No issues found. Checked for bugs and CLAUDE.md compliance.


This PR demonstrates excellent adherence to OpenO EMR coding standards:

Security Compliance

  • Proper OWASP encoding in JSP (Encode.forHtmlContent, Encode.forUriComponent, Encode.forJavaScriptAttribute)
  • Parameterized JPQL queries with named/positional parameters
  • SecurityInfoManager privilege checks in manager layer
  • No PHI logged inappropriately

Package Structure

  • Correct ca.openosp.openo.* namespace usage
  • Proper DTO organization in tickler.dto.*

Documentation Standards

  • Comprehensive JavaDoc on all public classes and methods
  • Proper @SInCE tags (2026-01-30)
  • Detailed @param and @return documentation
  • No @author tags

Modern Test Framework

  • Tests in src/test-modern/ directory
  • BDD naming convention (should<Action>_when<Condition>)
  • Proper tags (@tag("integration"), @tag("dao"), @tag("read"))
  • Tests extend OpenOTestBase with modern JUnit 5 annotations

Code Quality

  • Null safety in DTO helper methods
  • Correct JPQL property names matching Hibernate mappings
  • Proper batch loading logic
  • Healthcare-specific field handling preserved

The performance optimization (25 queries → 2-3 queries) is well-architected and maintains all security, compliance, and audit logging requirements.

@LiamStanziani

Copy link
Copy Markdown
Collaborator Author

I see one small issue with ordering compared to the old version, I will fix soon, and that's the only issue i see

@claude

claude Bot commented Jan 31, 2026

Copy link
Copy Markdown

Code Review

I found 1 issue that should be addressed:


Incorrect OWASP encoding context for JavaScript function parameters

File: src/main/webapp/tickler/ticklerMain.jsp
Line: 957

The code uses Encode.forJavaScriptAttribute() for values that appear inside JavaScript string literals (within single quotes) being passed as function parameters. According to CLAUDE.md, the correct encoder for JavaScript string contexts is Encode.forJavaScript().

CLAUDE.md Rule:

Use context-appropriate OWASP encoding when outputting user data:

  • Encode.forJavaScript() - JavaScript string contexts
  • Encode.forJavaScriptAttribute() - JS in HTML attributes

Current code:

onClick="openNoteDialog('<%=Encode.forJavaScriptAttribute(String.valueOf(tickler.getDemographicNo()))%>','<%=Encode.forJavaScriptAttribute(String.valueOf(tickler.getId()))%>')"

Recommended fix:

onClick="openNoteDialog('<%=Encode.forJavaScript(String.valueOf(tickler.getDemographicNo()))%>','<%=Encode.forJavaScript(String.valueOf(tickler.getId()))%>')"

The values are within JavaScript string literals (single quotes), so forJavaScript() is the appropriate encoder. While both methods provide XSS protection, using the correct encoder ensures proper handling of all edge cases.

Reference: See CLAUDE.md Error Handling section and appointmentprovideradminday.jsp:1213 which correctly uses forJavaScript() for the same pattern.


🤖 Generated with Claude Code

@LiamStanziani

LiamStanziani commented Jan 31, 2026

Copy link
Copy Markdown
Collaborator Author

No, this use is fine. It works for this use case.

Also not sure why I can't tag yingbull for review

@lacarmen lacarmen merged commit 013293d into hotfix/01232026 Feb 3, 2026
7 checks passed
@lacarmen lacarmen deleted the performance/dto-projection-tickler branch February 3, 2026 19:06
@LiamStanziani LiamStanziani restored the performance/dto-projection-tickler branch February 11, 2026 14:08
@LiamStanziani LiamStanziani linked an issue Feb 11, 2026 that may be closed by this pull request
2 tasks
@lacarmen lacarmen deleted the performance/dto-projection-tickler branch March 5, 2026 18:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Ticklers loading very slowly

3 participants