Skip to content

Upgrade to support OpenMRS Platform 3.0 - #126

Merged
dkayiwa merged 6 commits into
openmrs:core-3.xfrom
dkayiwa:platform-3.0
Oct 10, 2026
Merged

dkayiwa merged 6 commits into
openmrs:core-3.xfrom
dkayiwa:platform-3.0

Conversation

@dkayiwa

@dkayiwa dkayiwa commented Oct 2, 2026 •

Copy link
Copy Markdown
Member

Updates queue so it builds against, and runs on, openmrs-core master (Platform 3.0.0-SNAPSHOT: Java 21, Jakarta EE, Spring 7, Hibernate 7). Same approach as the platform 3 upgrades of calculation (openmrs/openmrs-module-calculation#19), legacyui and webservices.rest.

Changes

Depends on

  • Ensure core service advisors apply when modules declare FactoryBean services openmrs-core#6631 (merged). queue.QueueEntryService is a TransactionProxyFactoryBean whose target references visitService and adminService, which could leave those core services without core's advisors (Core service advisors are skipped when modules declare TransactionProxyFactoryBean services openmrs-core#6630).
  • For O3-5765: Automatically clear queue entries on a schedule #119's two scheduled tasks to work on core master, these scheduler issues need fixing in core. None of them is in this module:
    • New task definitions cannot be saved on MySQL. TRUNK-6391 moved TaskDefinition to annotations, which write scheduler_task_config.creator, but the old created_by column keeps DEFAULT 0 and its FK to users, so the insert fails on scheduler_creator. QueueModuleActivator then logs "Unable to register task" for both tasks.
    • Tasks scheduled by daemon run without privileges. Daemon.executeScheduledTaskAsUser becomes the scheduling user and then drops daemon status, and tasks registered at startup are scheduled by daemon, which has no roles. The visit task fails with APIAuthenticationException: Privileges required: Get Queue Entries, and the close-time task makes the same getQueueEntries call once its time is set. Core's own Auto Close Visits Task is scheduled as daemon too, since its creator column is null.
    • A task with a start time first runs about a day after it is scheduled. JobRunrSchedulerService.scheduleTask gives it a placeholder recurring job with an interval of a day plus the wait to its first run, and the helper that later sets the real interval leaves the occurrence the placeholder already scheduled.
    • Fix NPE on startup for scheduled tasks without a creator openmrs-core#6629 (open). On a site that ran queue 3.1.0 or later on 2.x, both task definitions have no creator, because TRUNK-6391 added the column without copying created_by into it. onStartup() then throws an NPE and startup fails.
    • onStartup() never recreates the recurring job of a start-on-startup task. It enqueues the task's startup run under the task's uuid, and that run then makes the task look scheduled. On a site that ran queue 3.1.0 or later on 2.x, each task gets one run at startup and no recurring job.
    • Starting a module no longer reschedules the tasks that stopping it shut down, since TRUNK-6558 took SchedulerUtil.startup() out of refreshApplicationContext. After the queue module is stopped and started, both tasks stay unscheduled.

Verification

  • mvn clean verify against core 3.0.0-SNAPSHOT as published on 2026-10-07 (with #6631): 92 api, 73 omod and 127 integration tests pass, locally and in CI on Java 21 and 25.
  • On Tomcat 11.0.11 with the published openmrs-webapp 3.0.0-SNAPSHOT war, webservices.rest 5.0.0-SNAPSHOT and this omod, on MySQL 8.0.34, from an unattended install:
    • The module starts and its liquibase changesets run.
    • Task registration fails as described above.
    • With local stand-ins for the first three of these core fixes (the table made to match the mapping, the daemon user given System Developer, and the stale placeholder occurrences removed), both tasks register and run every minute. The visit task ends an entry on a stopped visit at the visit's stop time and leaves an entry on an open visit alone. Once queue.autoCloseQueueEntriesAtTime is set, the close-time task ends the entries started before that time at that time and leaves one started after it alone.
  • Before main was merged in:
    • Before queue.QueueEntryService was restored to a TransactionProxyFactoryBean, I deployed the omod on plain openmrs-core master (mvn jetty:run, MySQL 8) with legacyui, webservices.rest 5.0.0-SNAPSHOT, fhir2 6.0.0-SNAPSHOT, calculation, htmlwidgets, serialization.xstream, metadatamapping, event, idgen, emrapi and authentication:
      • All modules started, and the server log has no errors from this module.
      • Logging in through the UI works.
      • queue, queue-entry and queue-room return 200.
    • With all 31 O3 reference application modules upgraded to Platform 3, on openmrs-core master plus the core fixes Fix NPE on startup for scheduled tasks without a creator openmrs-core#6629, #6631, #6633, #6635, #6637 and #6639 (mvn jetty:run, MySQL 8): every module starts, logging in through the UI works, and core message codes resolve.
      • In that run, queue and queue-entry return 200, and anonymous calls to core services are rejected with 401.

🤖 Generated with Claude Code

https://claude.ai/code/session_01Fp7T1pGYEMrMVp1M7AW9Fg

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Fp7T1pGYEMrMVp1M7AW9Fg

@RunWith(MockitoJUnitRunner.class)
@ExtendWith(MockitoExtension.class)
@MockitoSettings(strictness = Strictness.LENIENT)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Could this be narrowed to the one stub that needs it? Without the class-level setting, every failure comes from the shared getConceptByUuid stub in setupMocks(): five of the tests never call it, and the three invalid-GP tests call it with "invalid". Dropping this annotation and marking just that stub lenient keeps all 11 passing (I ran it):

lenient().when(conceptService.getConceptByUuid(conceptSet1.getUuid())).thenReturn(conceptSet1);

That leaves strict stubs on for the per-test global property stubs. With the class-wide LENIENT, a test that stubs the wrong property passes on the mock's null default. For example, stubbing QUEUE_PRIORITY instead of QUEUE_SERVICE in getAllowedServices_shouldThrowErrorIfNoGpConfigured still gives 11/11 as it stands, but fails with a PotentialStubbingProblem once only the setup stub is lenient. Not blocking.

…up stub

Only the getConceptByUuid stub in setupMocks() trips strict stubs: most
tests never call it, and the invalid-GP tests call it with "invalid".
Marking that one stub lenient() keeps strict stubs on for the per-test
global property stubs, so a test that stubs the wrong property fails
instead of passing on the mock's null default.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@dkayiwa dkayiwa left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

This needs a rebase onto main before it can go in, and the rebase is a port as well as a conflict fix: main picked up #119 (O3-5765) after this branch was cut, and none of that code has been built or run on Platform 3.

  • Besides the seven conflicting files, four more merge cleanly with #119's changes and then fail test-compile here. AutoCloseQueueEntryTaskTest, QueueModuleActivatorTest and AutoCloseQueueEntryTaskIntegrationTest are JUnit 4, and junit:junit isn't on this branch's test classpath. The last two also extend the JUnit 4 org.openmrs.test.BaseModuleContextSensitiveTest, and the integration test builds its task with TaskFactory; core master has dropped both. The two tests #119 added to QueueServicesWrapperTest use @Test(expected = ...), which Jupiter's @Test doesn't have. I compiled each of these against this branch's classpath to confirm.
  • QueueModuleActivator registers both tasks through SchedulerService's TaskDefinition methods. They still compile, but core 3.0 deprecates them and runs them on JobRunrSchedulerService rather than the timer scheduler #119 was tested against on 2.7.4, and the test counts and server checks in the description all come from a branch without #119.

So what I'd hold the merge on is porting #119's tests in the rebase, then checking on a 3.0 server that both queue tasks register and fire.

<activator>${project.parent.groupId}.${project.parent.artifactId}.QueueModuleActivator</activator>

<require_version>${openmrsPlatformVersion}</require_version>
<require_version>3.0.0</require_version>

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Could the README's Prerequisites be updated in this PR too? It still says "OpenMRS Platform ≥ 2.3.x" and "Java 8 or higher", and with this line and maven.compiler.release set to 21, a 4.x build needs Platform 3.0.0 and Java 21.

Comment on lines +20 to +22
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd">
http://www.springframework.org/schema/context/spring-context.xsd">

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Spring 7 doesn't need this change: spring-beans and spring-context 7.0.9 still map the versioned -3.0.xsd locations to their bundled schemas in META-INF/spring.schemas, and omod's webModuleApplicationContext.xml keeps the 3.0 ones. I'd drop these two lines so the diff stays on what the upgrade needs; if you'd rather have the versionless form, change omod's file too so the two don't disagree.

Suggested change
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd">
http://www.springframework.org/schema/context/spring-context.xsd">
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd">

dkayiwa and others added 3 commits October 7, 2026 22:21
Brings in openmrs#119 (O3-5765) and the 3.1.0 release, and ports openmrs#119 to
Platform 3:
- updateIfUnmodified keeps openmrs#119's CriteriaUpdate, on jakarta.persistence
  and createMutationQuery.
- openmrs#119's tests move to JUnit 5. QueueModuleActivatorTest and
  AutoCloseQueueEntryTaskIntegrationTest extend the Jupiter
  BaseModuleContextSensitiveTest, and the integration test runs the task
  through core's LegacyTask, which is how the JobRunr scheduler runs a
  task definition now that TaskFactory is gone.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…t.xml

Spring 7 still maps the -3.0.xsd locations to its bundled schemas, which
webModuleApplicationContext.xml already relies on, so the change was not
needed for the upgrade.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@dkayiwa dkayiwa left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Before this merges I'd like the core side tracked, and there's more of it than the Depends on list names.

The server check was a fresh install. A site that ran queue 3.1.0 or later on 2.x already has both task definitions when it moves to Platform 3, so started() finds them and returns, and scheduling them is left to core's onStartup(). On core master that goes wrong in two places:

  • The upgraded rows have no creator (TRUNK-6391 added the column without copying created_by), so onStartup() throws the NPE that openmrs/openmrs-core#6629 fixes, and startup fails. #6629 is still open.
  • With #6629 in, onStartup() enqueues each task's startup run under the task's uuid, then looks for a job with that uuid to decide whether to call scheduleTask, finds that run, and skips it. Each task gets one run at startup and no recurring job.

I reproduced both in an integration test against snapshot 434, with the definitions as 2.x leaves them:

schedulerService.saveTaskDefinition(definition);          // each task: startOnStartup, no JobRunr jobs
new QueueModuleActivator().started();                     // finds them registered and returns
schedulerService.onStartup();                             // what Listener.startOpenmrs calls
schedulerService.getRecurringTask(definition.getUuid());  // empty
schedulerService.getTask(definition.getUuid());           // present, the startup run

The same call does create the recurring job for a definition that isn't startOnStartup. A stop and start of the queue module ends the same way, since WebModuleUtil.stopModule still shuts the module's tasks down and TRUNK-6558 took SchedulerUtil.startup() out of refreshApplicationContext. So the Javadoc on QueueModuleActivator.registerTask, which says the scheduler starts the task at server startup and restores it across a module stop and start, now describes 2.x. I'd reword it here to say what 3.0 does.

None of these has a TRUNK ticket, and neither do the three in the description, so right now this description is the only record of them. Could you file them and list them, with #6629, under Depends on before this goes in? If 3.0.0 ships with #6629 but without the onStartup fix, a site upgrading from queue 3.1.0 or later gets one auto-close run at its first startup and none after.

On 2.x the scheduler rescheduled every start-on-startup task at server
startup and whenever a module started. On 3.0 onStartup() does not
recreate a task's missing recurring job, and starting a module no
longer reschedules the tasks that stopping it shut down, since
TRUNK-6558 took SchedulerUtil.startup() out of
refreshApplicationContext.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@sonarqubecloud

sonarqubecloud Bot commented Oct 7, 2026

Copy link
Copy Markdown

@dkayiwa
dkayiwa changed the base branch from main to core-3.x October 10, 2026 18:00
@dkayiwa
dkayiwa merged commit acbb1a8 into openmrs:core-3.x Oct 10, 2026
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant