This repository contains analyzer plugins for OpenELIS Global. Each plugin integrates a specific laboratory analyzer with the OpenELIS LIMS.
All plugins MUST follow the Maven standard directory layout:
analyzers/YourAnalyzer/
├── pom.xml
├── README.md (recommended)
├── src/
│ ├── main/
│ │ ├── java/
│ │ │ └── [your package structure]
│ │ └── resources/
│ │ └── [XML configs, if needed]
│ └── test/
│ └── java/
│ └── [test files]
All plugins MUST use Java 21. The parent pom manages compiler configuration.
CORRECT pom.xml:
<parent>
<groupId>org.openelisglobal</groupId>
<artifactId>openelisglobal-plugins</artifactId>
<version>1.0</version>
<relativePath>../../pom.xml</relativePath>
</parent>DO NOT override Java version in child modules:
<!-- ❌ WRONG - Do not add these -->
<properties>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>The parent pom configures Java 21 for all modules.
Before committing, ALWAYS run:
mvn spotless:applyThis formats code according to Google Java Format and sorts POMs.
Never commit debug code:
- ❌
System.out.println() - ❌
System.err.println() - ❌ Commented-out code blocks
- ❌ TODO comments without issue references
Use proper logging instead:
import org.openelisglobal.common.log.LogEvent;
LogEvent.logDebug(this.getClass().getSimpleName(), "methodName", "Debug message");
LogEvent.logWarn(this.getClass().getSimpleName(), "methodName", "Warning message");
LogEvent.logError(this.getClass().getSimpleName(), "methodName", "Error message", exception);Plugins MUST work generically across multiple sites. DO NOT hardcode:
- Site-specific test mappings
- Site-specific analyzer identifiers
- Site-specific result interpretations
- Site-specific units or reference ranges
If customization is needed, use configuration files or database-driven settings.
All plugins MUST use lazy initialization for Spring beans and services.
This pattern allows unit testing without requiring the full Spring context and follows the framework's AnalyzerLineInserter base class design.
// ❌ WRONG - Static initializers break unit testing
static HashMap<String, Test> testNameMap = new HashMap<>();
static String ANALYZER_ID;
static {
testNameMap.put("GLU2", SpringContext.getBean(TestService.class).getTestByName("Glucose"));
ANALYZER_ID = SpringContext.getBean(AnalyzerService.class).getAnalyzerByName("MyAnalyzer").getId();
}// ✅ CORRECT - Lazy initialization allows unit testing
private TestService testService;
private String analyzerId;
private HashMap<String, Test> testNameMap;
protected TestService getTestService() {
if (testService == null) {
testService = SpringContext.getBean(TestService.class);
}
return testService;
}
protected String getAnalyzerId() {
if (analyzerId == null) {
Analyzer analyzer = SpringContext.getBean(AnalyzerService.class)
.getAnalyzerByName("MyAnalyzer");
if (analyzer != null) {
analyzerId = analyzer.getId();
}
}
return analyzerId;
}
protected HashMap<String, Test> getTestNameMap() {
if (testNameMap == null) {
testNameMap = new HashMap<>();
TestService ts = getTestService();
testNameMap.put("GLU2", ts.getTestByName("Glucose"));
}
return testNameMap;
}Why this matters:
- Static initializers run at class load time, requiring Spring context
- Unit tests fail with
ExceptionInInitializerErrorwithout Spring - Lazy initialization defers bean lookup until runtime
- Tests can instantiate the class and verify error handling
See analyzers/AB7500Fast or analyzers/SysmexKX21 for reference implementations.
src/
├── oe/plugin/analyzer/
│ ├── AnalyzerName.java (main plugin)
│ ├── AnalyzerNameImplementation.java (line inserter)
│ ├── AnalyzerNameMenu.java
│ └── AnalyzerNamePermission.java
└── AnalyzerName.xml (metadata)
This pattern is retained for backward compatibility but NOT recommended for new plugins.
src/main/java/
└── org/openelisglobal/plugins/analyzer/[analyzer]/
├── [Analyzer]Analyzer.java
├── [Analyzer]AnalyzerLineInserter.java
└── (other classes as needed)
src/main/resources/
└── [Analyzer].xml (if needed)
src/test/java/
└── (test files)
Example: See HoribaPentra60 or GenericASTM for reference implementations.
Use GenericASTM for analyzers that can be configured entirely through the OpenELIS dashboard (Feature 004/011). This avoids writing Java code for each new analyzer.
When to use GenericASTM:
- Analyzer follows ASTM LIS2-A2 protocol
- Test mappings can be configured via dashboard
- No custom parsing logic required
When to write a dedicated plugin:
- Proprietary protocol (not ASTM/HL7)
- Complex parsing rules
- Custom result transformation logic
ALL new plugins MUST include tests for parsing logic.
import org.junit.Test;
import org.openelisglobal.plugin.test.PluginTestBase;
import static org.junit.Assert.*;
public class MyAnalyzerLineInserterTest extends PluginTestBase {
@Test
public void testParseLine_WithValidData_ReturnsRecord() {
MyAnalyzerLineInserter inserter = new MyAnalyzerLineInserter();
MyAnalyzerLineInserter.Record record =
inserter.parseLine("SAMPLE-001\tHGB\t15.2\tg/dL");
assertNotNull(record);
assertEquals("SAMPLE-001", record.getSampleId());
assertEquals("HGB", record.getTestCode());
assertEquals("15.2", record.getValue());
}
@Test
public void testParseLine_WithMissingData_ReturnsNull() {
MyAnalyzerLineInserter inserter = new MyAnalyzerLineInserter();
MyAnalyzerLineInserter.Record record =
inserter.parseLine("\t\t\t");
assertNull(record);
}
}Add to your plugin's pom.xml:
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.openelisglobal.plugins</groupId>
<artifactId>test-utilities</artifactId>
<version>1.0</version>
<scope>test</scope>
</dependency>
</dependencies>See README.md Testing Section for more details.
The GenericASTM plugin requires OpenELIS features that currently exist only on the demo/madagascar branch:
org.openelisglobal.analyzer.service.AnalyzerConfigurationServiceorg.openelisglobal.analyzer.valueholder.AnalyzerConfigurationanalyzer_configurationdatabase tableanalyzer_test_mappingdatabase table
Current Status: CI builds against demo/madagascar to support GenericASTM.
Exit Criteria: Once Feature 004 (analyzer-management) or Feature 011 (madagascar-analyzer-integration) merges to OpenELIS develop, CI will revert to building against develop.
For New Plugins: If your plugin doesn't require these features, it will work with both develop and demo/madagascar.
Before creating a new plugin, check if your analyzer is already supported or if GenericASTM can handle it.
git checkout develop
git pull origin develop
git checkout -b feat/analyzer-your-analyzer-nameFollow the Pattern B (Maven Standard) structure.
Write tests for your parsing logic (see Writing Tests).
mvn spotless:apply# First time? Install the OpenELIS classes JAR:
# (from OpenELIS-Global-2 root) plugins/scripts/install-oe-jar.sh
# Build and test your plugin (from repo root)
mvn clean install -pl :YourAnalyzer -am
# Verify all plugins still build
mvn clean installAdd your module to pom.xml:
<modules>
<!-- ... existing modules ... -->
<module>./analyzers/YourAnalyzer</module>
</modules>Target branch: develop
PR Title: feat(analyzer): add [Analyzer Name] plugin
PR Description should include:
- Analyzer model and manufacturer
- Protocol used (ASTM, HL7, File, etc.)
- Test results from CI
- Example input/output files (if applicable)
- Any site/region-specific context (e.g., "Used in Haiti PEPFAR sites")
Before submitting PR, verify:
- Java 21 (no version override in child pom)
- Maven standard layout (
src/main/java,src/main/resources) - Lazy initialization pattern used (no static SpringContext calls)
- Tests included and passing (
mvn test) - Tests run WITHOUT @Ignore (lazy init enables testability)
- Code formatted (
mvn spotless:apply) - No debug print statements
- No site-specific hardcoded configurations
- README.md created (recommended)
- Parent pom updated with new module
- Full build passes (
mvn clean install)
- Architecture Questions: See existing plugins for examples
- Testing Issues: See README.md Testing Section
- CI Failures: Check CI workflow for build requirements
- PR #23 Salvage: See PR23-TRIAGE.md for lessons learned