Major Project Progress Update - 01
Upon initiating my GSoC work, I began by tackling the task of extracting Javadoc comments present at both class and method levels within each REST resource. After thorough exploration, I narrowed down the approach to two viable options: the Doclet API, which operates by invoking the javadoc
tool to process source code, and JavaParser, which parses .java
files into an Abstract Syntax Tree (AST), enabling traversal and structured analysis of code elements.
After evaluating the trade-offs (which I documented extensively), I chose to proceed with JavaParser due to its lightweight footprint, developer ergonomics, and seamless programmatic integration into a Maven plugin environment.
The next and most substantial challenge I encountered relates to accurately capturing the REST API representations (reps) defined in the webservices.rest
module—specifically, for GET
operations on resource classes. OpenMRS supports multiple representations like default
, full
, ref
, and custom
, each returning different sets of properties. To generate high-fidelity OpenAPI documentation, it’s essential to statically determine what each representation returns for every resource. This level of documentation is invaluable for new contributors, integrators, and external developers consuming the APIs.
Historically, the OpenMRS REST module relied on the getGETModel()
method to define representation content, but this was inconsistent and unreliable across resources. To overcome this, I’m now working on invoking the getRepresentationDescription(Representation rep)
method reflectively at build time. This method returns a DelegatingResourceDescription
, which explicitly defines the fields and sub-resources included in each representation type.
However, this approach introduces a significant technical caveat: during Maven’s build lifecycle, we lack access to the Spring application context and many of the supporting OpenMRS services that would normally be injected at runtime. As a result, invoking methods that internally rely on these services (even indirectly) can result in exceptions, NullPointerExceptions
, or incomplete behavior.
For instance, while testing with PatientResource1_8
, I was able to instantiate the class using its default no-args constructor. However, when calling getRepresentationDescription()
on it, the method fails because it references static members like RestConstants
, which in turn may rely on Spring-managed beans or modules not yet initialized in the build phase. To verify this, I commented out the problematic lines, and the method successfully returned the expected properties for both default
and full
representations.
[INFO] ------------< org.openmrs.module:webservices.rest-omod-1.8 >------------
[INFO] Building Rest Web Services 1.8 OMOD 2.50.0-SNAPSHOT
[INFO] from pom.xml
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- openapi-generator:1.0-SNAPSHOT:openapi (default-cli) @ webservices.rest-omod-1.8 ---
[INFO] === OPENAPI GENERATION STARTED ===
[INFO] ClassLoader setup complete: 22 URLs
[INFO] Loaded class: org.openmrs.module.webservices.rest.web.v1_0.resource.openmrs1_8.PatientResource1_8
[INFO] === Testing PatientResource Instance Creation ===
[INFO] Attempting to create PatientResource1_8 instance using normal constructor...
[INFO] ? SUCCESS! PatientResource1_8 instance created successfully!
[INFO] Instance class: org.openmrs.module.webservices.rest.web.v1_0.resource.openmrs1_8.PatientResource1_8
[INFO] Instance toString: org.openmrs.module.webservices.rest.web.v1_0.resource.openmrs1_8.PatientResource1_8@77f905e3
[INFO] --- Testing Basic Method Calls ---
[INFO] ? toString() works: org.openmrs.module.webservices.rest.web.v1_0.resource.openmrs1_8.PatientResource1_8@77f905e3
[INFO] ? getClass() works: org.openmrs.module.webservices.rest.web.v1_0.resource.openmrs1_8.PatientResource1_8
[INFO] ? Found 30 declared methods
[INFO] ? Found getRepresentationDescription method: public org.openmrs.module.webservices.rest.web.resource.impl.DelegatingResourceDescription org.openmrs.module.webservices.rest.web.v1_0.resource.openmrs1_8.PatientResource1_8.getRepresentationDescription(org.openmrs.module.webservices.rest.web.representation.Representation)
[INFO] === Testing getRepresentationDescription Method ===
[INFO] ? DEFAULT result: org.openmrs.module.webservices.rest.web.resource.impl.DelegatingResourceDescription@2b2954e1
[INFO] ? FULL result: org.openmrs.module.webservices.rest.web.resource.impl.DelegatingResourceDescription@58d6e55a
[INFO] === Extracting Schema from DEFAULT ===
[INFO] Found 5 properties:
[INFO] - uuid -> org.openmrs.module.webservices.rest.web.resource.impl.DelegatingResourceDescription$Property@235d659c
[INFO] - display -> org.openmrs.module.webservices.rest.web.resource.impl.DelegatingResourceDescription$Property@4232b34a
[INFO] - identifiers -> org.openmrs.module.webservices.rest.web.resource.impl.DelegatingResourceDescription$Property@2da16263
[INFO] - person -> org.openmrs.module.webservices.rest.web.resource.impl.DelegatingResourceDescription$Property@f5ce0bb
[INFO] - voided -> org.openmrs.module.webservices.rest.web.resource.impl.DelegatingResourceDescription$Property@47e51549
[INFO] === Extracting Schema from FULL ===
[INFO] Found 6 properties:
[INFO] - uuid -> org.openmrs.module.webservices.rest.web.resource.impl.DelegatingResourceDescription$Property@101a461c
[INFO] - display -> org.openmrs.module.webservices.rest.web.resource.impl.DelegatingResourceDescription$Property@360e9c06
[INFO] - identifiers -> org.openmrs.module.webservices.rest.web.resource.impl.DelegatingResourceDescription$Property@5ebffb44
[INFO] - person -> org.openmrs.module.webservices.rest.web.resource.impl.DelegatingResourceDescription$Property@311ff287
[INFO] - voided -> org.openmrs.module.webservices.rest.web.resource.impl.DelegatingResourceDescription$Property@7377781e
[INFO] - auditInfo -> org.openmrs.module.webservices.rest.web.resource.impl.DelegatingResourceDescription$Property@31db34da
[INFO] ==============
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
I also experimented with unsafe object allocation (via sun.misc.Unsafe
or ObjectInputStream
) to instantiate the class without triggering constructors or initialization blocks, but this yielded similar issues due to static dependencies being evaluated during method execution.
I intend to share the exact stack traces and points of failure in follow-up discussions, but I wanted to flag this design tension early—especially if there’s precedent or tooling within the OpenMRS ecosystem for solving this kind of build-time reflection over runtime-dependent logic.
Despite the complexity, I’m optimistic. Progress has been steady and productive, and I’m confident that with the right design abstractions, this plugin will bring clarity, consistency, and significant value to the way OpenMRS exposes its REST API documentation.
This is the architecture of the whole documentation generator that we have planned (Thank you @mherman22 for such a detailed graph)
Thank you again for the support and guidance—I’m excited to keep pushing forward and deliver something the community will be proud of.