GSOC 2025: Replacing OpenMRS Standalone with a Modern, User-Friendly Alternative - Updates and Discussion

Just created a Jira ticket to track this here Jira

1 Like

Hi @jonathan, I’m having trouble building the Standalone in a Gitpod environment. The following are the environment specs:

mvn -v
Picked up JAVA_TOOL_OPTIONS: -XX:+UseContainerSupport -XX:ActiveProcessorCount=1
Apache Maven 3.9.10 (5f519b97e944483d878815739f519b2eade0a91d)
Maven home: /home/gitpod/.sdkman/candidates/maven/current
Java version: 17.0.14, vendor: Amazon.com Inc., runtime: /home/gitpod/.sdkman/candidates/java/17.0.14-amzn
Default locale: en_US, platform encoding: UTF-8
OS name: "linux", version: "6.1.91-060191-generic", arch: "amd64", family: "unix"

I get the following error:

[ERROR] ChangeSet liquibase-ciel-data.xml::20120315-1000::standalone encountered an exception.
liquibase.exception.DatabaseException: Unknown table 'openmrs.concept' [Failed SQL: (1051) DROP TABLE concept]
    at liquibase.executor.jvm.JdbcExecutor$ExecuteStatementCallback.doInStatement (JdbcExecutor.java:519)
    ...

UPDATE SUMMARY
Run:                          0
Previously run:               0
Filtered out:                 0
-------------------------------
Total change sets:            1

[ERROR] Failed to execute goal org.liquibase:liquibase-maven-plugin:4.32.0:update (empty-db-add-ciel-data) on project standalone-02: 
[ERROR] Error setting up or running Liquibase:
[ERROR] liquibase.exception.LiquibaseException: liquibase.exception.MigrationFailedException: Migration failed for changeset liquibase-ciel-data.xml::20120315-1000::standalone:
[ERROR]      Reason: liquibase.exception.DatabaseException: Unknown table 'openmrs.concept' [Failed SQL: (1051) DROP TABLE concept]

To reproduce this, you can use this link.

Would you know why it fails?

1 Like

@ruhanga you need to run mvn clean and then mvn install seperately.

2 Likes

Thanks @dkayiwa, that seems to have done the trick.

1 Like

Thanks for the great efforts here @jonathan! A few observations after building and testing the current Standalone (from the master branch): when it’s built on a Linux machine, it fails to run on macOS or Windows. This seems to be due to the MariaDB binaries being resolved and bundled during the build process, making them OS-specific.

This implies that we’d need separate Standalone builds for each supported OS. Alternatively, we could resolve the appropriate binaries dynamically at runtime. However, doing so would likely require deferring some of the current logic, like initial database population, from build time to runtime. The trade-off is that the first-time setup might take slightly longer

A potentially better solution, building on the proposal above, might be to rely on pre-generated DB dumps or .sql files(done at build time) that can be used at runtime regardless of the OS, avoiding the need to bundle os-specific binaries altogether.

1 Like

@dkayiwa We found an Unexpected behaviour talked about by @ruhanga

OpenMRS Standalone Platform 2.8.0-alpha was released — but when trying to run the freshly built standalone on another machine , it fails to start . (Also applies if built on another machine and use the zipped on a newer one)

:anxious_face_with_sweat: The Problem:

Artifacts built on Bamboo (the CI system) work fine — until you try to run them on a different system. then they fail.

On Sunday evening, I got a meetup with @ruhanga to debug this. After digging deep, we found the cause:

:magnifying_glass_tilted_left: MariaDB user permissions were being tied to the machine that built the artifact. Specifically, the embedded MariaDB tables for Demo and CIEL were zipped during build-time, and these include user/system-specific permission metadata.

When moved to another machine, MariaDB rightfully refuses access — since the runtime system wasn’t the one that created the DB.

:light_bulb: The Proposed Fix

Together, we came up with a plan:

:hammer_and_wrench: Solution:

  • Instead of zipping MariaDB binaries during the build phase…(pom steps 1-5) and zipping them
  • We will zip the SQL dump instead, and initialize the database during runtime for demo separately and Ciel.

Why this works?

The Expert Mode, which builds everything on the fly, already works fine on any machine — because it doesn’t carry any system-bound DB binaries.

This proposed runtime approach will:

  • Make Demo and CIEL modes portable across systems
  • Ensure future Platform releases (like 2.8.0-alpha) behave consistently

What is your take on this?

cc @dkayiwa @wikumc @ruhanga @sharif @suubi7

For a try out Download from here OpenMRS Platform - OpenMRS Platform 2.8.x Temp Rebuild 9: Artifacts - OpenMRS Bamboo the built Bamboo standalone and try to select the Demo or ciel … and then after the Expert mode

The Demo and ciel fail as explained while Expert mode passes fine

What do you mean by the mariadb binaries? Do you mean the files in the database/bin folder?

yes indeed unzipped from the demo and empty zips

I thought those had nothing to do the CIEL or Demo SQL data.

@dkayiwa , what we observed is that the zipped MariaDB data directories (not binaries like those in database/bin, but the actual data store folders containing ibdata1, *.ibd, mysql/*, openmrs/*, etc.) — which are included for the Demo and CIEL modes — carry over machine-specific metadata, including the mysql system database which defines users, permissions, and hostname bindings.

So although database/bin has the MariaDB server binaries, the issue lies in the unzipped data folder contents, which get restored as-is from the zipped demo/CIEL folders during runtime. These data folders contain:

  • Pre-initialized openmrs database (Demo/CIEL)
  • The mysql database with root user
  • Other internal MariaDB server files

That’s why when the Standalone is built (say, on Bamboo), and the zipped demo/CIEL folders are extracted and run on another machine, the embedded MariaDB sees a mismatch and fails — typically throwing errors

1 Like

Oh i see! Then i am happy with your proposed approach. Looking forward to testing it out. :slight_smile:

2 Likes

Am gonna revert to this for Platform, 2.x and 3.x Standalone :blush:

1 Like

Anxiously looking forward to the output. :smiley:

2 Likes

Hello @dkayiwa @wikumc @mherman22 @ibacher

Am trying to call the Context.updateSearchIndex(); in the standalone after demo and ciel initialized or imported and getting

📥 Importing SQL from: /Users/mutajonathan/openmrs/openmrs-standalone-2.7.4-SNAPSHOT/db/data/openmrs-demo-dump.sql
✅ Successfully imported SQL: /Users/mutajonathan/openmrs/openmrs-standalone-2.7.4-SNAPSHOT/db/data/openmrs-demo-dump.sql
Stopping MariaDB...
Database mode using demo database: DEMO_DATABASE
Exception in thread "main" java.lang.NoClassDefFoundError: org/openmrs/api/context/Context
	at org.openmrs.standalone.ApplicationController.init(ApplicationController.java:282)
	at org.openmrs.standalone.ApplicationController.<init>(ApplicationController.java:62)
	at org.openmrs.standalone.ApplicationController.main(ApplicationController.java:133)
Caused by: java.lang.ClassNotFoundException: org.openmrs.api.context.Context
	at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:580)
	at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:490)
	... 3 more

which makes sense since at this stage the openmrs is not yet fully initialised however am wondering how i can do this same thing of refreshing the search index via Rest Api to be clear will be checking for the session status and then refresh the search index

I think this could be what i was looking for

cc @dkayiwa @wikumc

However using the POST on http://localhost:8081/openmrs/ws/rest/v1/searchindex am getting

{
    "error": {
        "message": "[Required request body is missing: public java.lang.Object org.openmrs.module.webservices.rest.web.v1_0.controller.MainResourceController.create(java.lang.String,org.openmrs.module.webservices.rest.SimpleObject,javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse) throws org.openmrs.module.webservices.rest.web.response.ResponseException]",
        "code": "org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor:163",
        "detail": "org.springframework.http.converter.HttpMessageNotReadableException: Required request body is missing: public java.lang.Object org.openmrs.module.webservices.rest.web.v1_0.controller.MainResourceController.create(java.lang.String,org.openmrs.module.webservices.rest.SimpleObject,javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse) throws org.openmrs.module.webservices.rest.web.response.ResponseException\n\tat org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor.readWithMessageConverters(RequestResponseBodyMethodProcessor.java:163)\n\tat org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor.resolveArgument(RequestResponseBodyMethodProcessor.java:133)\n\tat org.springframework.web.method.support.HandlerMethodArgumentResolverComposite.resolveArgument(HandlerMethodArgumentResolverComposite.java:122)\n\tat org.springframework.web.method.support.InvocableHandlerMethod.getMethodArgumentValues(InvocableHandlerMethod.java:179)\n\tat org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:146)\n\tat org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:117)\n\tat org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:895)\n\tat org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:808)\n\tat org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)\n\tat org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1072)\n\tat org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:965)\n\tat org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006)\n\tat org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:909)\n\tat javax.servlet.http.HttpServlet.service(HttpServlet.java:555)\n\tat org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883)\n\tat javax.servlet.http.HttpServlet.service(HttpServlet.java:623)\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:199)\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:144)\n\tat org.openmrs.module.web.filter.ModuleFilterChain.doFilter(ModuleFilterChain.java:73)\n\tat org.openmrs.web.filter.GZIPFilter.doFilterInternal(GZIPFilter.java:66)\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117)\n\tat org.openmrs.module.web.filter.ModuleFilterChain.doFilter(ModuleFilterChain.java:71)\n\tat org.openmrs.module.webservices.rest.web.filter.AuthorizationFilter.doFilter(AuthorizationFilter.java:121)\n\tat org.openmrs.module.web.filter.ModuleFilterChain.doFilter(ModuleFilterChain.java:71)\n\tat org.openmrs.module.webservices.rest.web.filter.ContentTypeFilter.doFilter(ContentTypeFilter.java:64)\n\tat org.openmrs.module.web.filter.ModuleFilterChain.doFilter(ModuleFilterChain.java:71)\n\tat org.springframework.web.filter.ShallowEtagHeaderFilter.doFilterInternal(ShallowEtagHeaderFilter.java:106)\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117)\n\tat org.openmrs.module.web.filter.ModuleFilterChain.doFilter(ModuleFilterChain.java:71)\n\tat org.openmrs.module.owa.filter.OwaFilter.doFilter(OwaFilter.java:112)\n\tat org.openmrs.module.owa.filter.OwaFilter.doFilter(OwaFilter.java:90)\n\tat org.openmrs.module.web.filter.ModuleFilterChain.doFilter(ModuleFilterChain.java:71)\n\tat org.openmrs.module.web.filter.ModuleFilter.doFilter(ModuleFilter.java:57)\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:168)\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:144)\n\tat org.owasp.csrfguard.CsrfGuardFilter.handleSession(CsrfGuardFilter.java:107)\n\tat org.owasp.csrfguard.CsrfGuardFilter.doFilter(CsrfGuardFilter.java:97)\n\tat org.owasp.csrfguard.CsrfGuardFilter.doFilter(CsrfGuardFilter.java:68)\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:168)\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:144)\n\tat org.openmrs.web.filter.OpenmrsFilter.doFilterInternal(OpenmrsFilter.java:114)\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117)\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:168)\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:144)\n\tat org.openmrs.web.filter.CookieClearingFilter.doFilterInternal(CookieClearingFilter.java:77)\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117)\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:168)\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:144)\n\tat org.springframework.orm.hibernate5.support.OpenSessionInViewFilter.doFilterInternal(OpenSessionInViewFilter.java:156)\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117)\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:168)\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:144)\n\tat org.springframework.web.multipart.support.MultipartFilter.doFilterInternal(MultipartFilter.java:125)\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117)\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:168)\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:144)\n\tat org.openmrs.web.filter.StartupFilter.doFilter(StartupFilter.java:117)\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:168)\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:144)\n\tat org.openmrs.web.filter.StartupFilter.doFilter(StartupFilter.java:117)\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:168)\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:144)\n\tat org.openmrs.web.filter.StartupFilter.doFilter(StartupFilter.java:117)\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:168)\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:144)\n\tat org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201)\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117)\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:168)\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:144)\n\tat org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:168)\n\tat org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:90)\n\tat org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:482)\n\tat org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:130)\n\tat org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:93)\n\tat org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74)\n\tat org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:346)\n\tat org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:397)\n\tat org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:63)\n\tat org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:935)\n\tat org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1826)\n\tat org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:52)\n\tat org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1189)\n\tat org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:658)\n\tat org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:63)\n\tat java.base/java.lang.Thread.run(Thread.java:1447)\n"
    }
}

What body majorly is required? cc @dkayiwa @wikumc

1 Like

pushed PR for it here