Skip to content

Move the remaining log4j 1.x API uses to log4j2 - #539

Open
NicoPiel wants to merge 4 commits into
OpenIntegrationEngine:mainfrom
NicoPiel:modernise/log4j1-api
Open

NicoPiel wants to merge 4 commits into
OpenIntegrationEngine:mainfrom
NicoPiel:modernise/log4j1-api

Conversation

@NicoPiel

Copy link
Copy Markdown
Contributor

What

Moves the last Java uses of the log4j 1.x API to log4j2:

  • DefaultDebugUsageController now uses LogManager/Logger like the other controllers.
  • ExtensionStatuses and ExtensionStatusProvider look up org.apache.logging.log4j.LogManager reflectively instead of org.apache.log4j.Logger.

Why

These were the only Java callers of the 1.2 bridge API. The bridge itself stays, because the generated transformer script still exposes org.apache.log4j.Logger to user scripts.

Reviewer notes

  • The extprops classes are packed into the launcher jar, which has no log4j on its classpath, so they keep the reflective lookup through the context classloader. That's the same lookup and LoggerWrapper usage MirthLauncher already does.
  • Verified with ./gradlew :server:compileJava and DebugUsageControllerTest. No automated test covers the extprops path.

🤖 Generated with Claude Code

Nico Piel added 2 commits October 29, 2026 19:29
Signed-off-by: Nico Piel <nico.piel@hotmail.de>
The launcher jar has no log4j on its classpath, so the logger is still looked up through the context classloader, the same way MirthLauncher already does it.

Signed-off-by: Nico Piel <nico.piel@hotmail.de>
@github-actions

github-actions Bot commented Oct 30, 2026 •

Copy link
Copy Markdown

Test Results

128 files   - 2  128 suites   - 2   3m 48s ⏱️ +37s
788 tests  - 1  788 ✅  - 1  0 💤 ±0  0 ❌ ±0 
914 runs   - 1  908 ✅  - 1  6 💤 ±0  0 ❌ ±0 

Results for commit dd012af. ± Comparison against base commit 4589238.

This pull request removes 3 and adds 2 tests. Note that renamed tests count towards both.
com.mirth.connect.connectors.ws.WebServiceConnectorServletTest ‑ testCommonsSSL
com.mirth.connect.donkey.server.data.jdbc.OracleJdbcDaoTest ‑ testBatchInsertMessageContentKeepsStatementOpenUntilExecuted
com.mirth.connect.donkey.server.data.jdbc.OracleJdbcDaoTest ‑ testFailedBatchInsertClearsAndClosesTheStatement
com.mirth.connect.donkey.server.data.jdbc.JdbcDaoTest ‑ insertMessageContentBatchBatchesEveryRowThenExecutesOnce
com.mirth.connect.donkey.server.data.jdbc.JdbcDaoTest ‑ insertMessageContentBatchClosesTheStatementWhenBindingFails

♻️ This comment has been updated with latest results.

gibson9583
gibson9583 previously approved these changes Oct 30, 2026

@gibson9583 gibson9583 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Looks safe to merge.

mgaffigan
mgaffigan previously approved these changes Oct 30, 2026
@mgaffigan

Copy link
Copy Markdown
Contributor

Let's kill the bridge, too. We don't want to break user scripts unnecessarily, but this one seems like it can be compensated by a plugin - and the scripts have had a decade or two to update.

Nothing in the engine calls the log4j 1.x API any more, so remove
log4j-1.2-api from the distribution. slf4j-log4j12 bound slf4j to the
log4j 1.x classes the bridge provided, so replace it with
log4j-slf4j-impl. The transformer script now resets its logger through
the log4j2 LogManager.

The vendored not-going-to-be-commons-ssl jar called log4j 1.x directly
and was only kept for SoapUI, which the engine no longer uses. Remove
it together with its regression test and the leftover SoapUI log4j
config.

Signed-off-by: Nico Piel <nico.piel@hotmail.de>
@NicoPiel
NicoPiel dismissed stale reviews from mgaffigan and gibson9583 via e831ac2 October 30, 2026 15:50
MyBatis 3.1.1 has no log4j2 adapter, and its log4j adapter needs the
log4j 1.x classes the bridge used to provide, so the server could not
start. commons-logging hands MyBatis logging to the log4j2 API.

Signed-off-by: Nico Piel <nico.piel@hotmail.de>
private void init() {
try {
LogFactory.useLog4JLogging();
LogFactory.useCommonsLogging();

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

is this only needed because the MyBatis upgrade hasn't merged yet?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Yes. MyBatis 3.1.1 has no log4j2 adapter (it arrived in 3.2), and its log4j adapter needs the log4j 1.x classes this PR removes, so commons-logging is the only route to log4j2 for now. Once #545 (MyBatis 3.5.19) lands, this can become LogFactory.useLog4J2Logging(); whichever PR merges second will make that switch.

🤖 Addressed by Claude Code

@pacmano1

pacmano1 commented Nov 1, 2026

Copy link
Copy Markdown
Contributor

This also fixes the CLI's exportmessages, which is broken in 4.7.0. The CLI prints "Exporting messages to file", then dies with NoClassDefFoundError: org/apache/log4j/Level (Velocity's SLF4J init, via ValueReplacer), writes nothing, and exits 0. cli-lib ships slf4j-log4j12 without the log4j 1.2 bridge.

Built dd012af and ran three CLIs against its server, same channel, 3 messages:

  • this branch's CLI: 3 exported
  • stock 4.7.0 CLI: the NoClassDefFoundError, nothing written, exit 0
  • 4.7.0 CLI with only slf4j-log4j12 swapped for log4j-slf4j-impl-2.26.1: 3 exported

Might be worth a line in the description, since it's a user-facing fix.

Co-authored with Claude Code.

pacmano1 added a commit to pacmano1/docs-website that referenced this pull request Nov 5, 2026
Moves the unofficial wiki (pacmano1/unofficial-oie-wiki) into the site,
adds four concept pages and a Mirth Connect migration guide, checks every
page against OIE 4.7.0 (tag v4.7.0, engine commit f9ba73e28), and adds 35
screenshots of the 4.7.0 desktop Administrator. 41 new pages; six
existing pages and the sidebar changed.

The site is a reference manual for the engine plus explanation of the
concepts people misunderstand. Wiki content that failed that test did not
move: the Quick Start tutorial and the best-practice and tips sections.
Facts from them that state engine behaviour went into the pages that own
them.

Structure
- Sidebar in reading order: Concepts first and open, then Getting
  Started, Migrating from Mirth Connect, Administrators, Channels,
  Connectors, Scripting, Configuration, Operations, Security, Extensions,
  CLI and API, Help and Reference. Glossary at the top level, with an
  anchor per term. Contributing and Building from Source at the top level.
- Three wiki pages overlapped pages from OpenIntegrationEngine#13 and were folded in rather
  than duplicated: the Installation Guide's directory layout and heap
  default into Installation, the Administrator GUI tour as Desktop
  Administrator, and Plugins and Extensions into the Plugin Guide.
- Headings are sentence case inside a page.
- The glossary's Channel entry gives the Cloverleaf and Rhapsody terms
  for the same idea, from chimanwadike's core concepts page (OpenIntegrationEngine#14).
- contributing.md gains a documentation contribution path.
- Screenshots get an edge and a drop shadow, and clicking one shows it
  full size (.vitepress/theme). No package: medium-zoom, the usual
  choice, published two versions on 2026-10-25 after none since 2023.

Sources
- Connector labels, panel order, field greying and defaults come from the
  Swing client: labels from each panel, order from initLayout, greying
  from the action handlers, defaults from the *Properties constructors.
  Engine behaviour comes from the server code. Every page was checked
  against f9ba73e28, the commit the 4.7.0 release was built from.
- Where a claim could be run, it was run on the 4.7.0 release's
  oie_unix_4_7_0.tar.gz (sha256 ab08558873dc6be1e474afaacc46efe94d29c945
  cd3381c10d0ae548ecb429de) in Docker.

Concept pages, first written from main in September 2026 (file:line at
that time), then rechecked at f9ba73e28:
- Message Lifecycle and Storage: statuses and where each is set
  (Status.java:18-40, Channel.java:1456, FilterTransformerExecutor.java:
  104-143, DestinationConnector.java:449-450, :949-959, :1010-1015); the
  per-channel tables (JdbcDao.java:2166-2178, donkeydbconf/postgres.xml:
  4-138); the storage mode flags (DonkeyEngineController.java:1270-1345)
  and the passthrough DAO for Disabled (PassthruDao.java:173-266);
  asynchronous commit (JdbcDao.java:2388-2403); remove content on
  completion (Channel.java:2055-2155); the pruner's skip statuses and
  thresholds (DataPruner.java:94-99, :247-304, :449-554, :701-725); custom
  metadata (MetaDataReplacer.java:29-59, Channel.java:2268-2316). At
  4.7.0: Transformed holds msg or tmp after the filter and transformer,
  even for a filtered message; Sent is stored before any send attempt;
  PENDING is set only when the response transformer will run
  (ResponseTransformerExecutor.isActive).
- Queueing: a queue is a status query with a buffer
  (ConnectorMessageQueue.java:24-116, default.xml:318-354); the source
  queue setting stored inverted as respondAfterProcessing
  (SourceConnectorProperties.java:73); On Failure sends only when the
  queue is empty (DestinationConnector.java:261-263); Retry Count applies
  before queueing only (:517-533) and the queue loop has no limit
  (:615-895); thread buckets (DestinationQueue.java:68-83, :273-329);
  pause keeps queues draining (Channel.java:890-901); recovery before
  queues start (RecoveryTask.java:86-156); the pruner never removes
  QUEUED (DataPruner.java:97).
- Threading and Ordering: Max Processing Threads is a fair semaphore
  (DefaultChannelProcessLock.java:22-45); chains are built at deploy from
  the enabled destinations in list order and Wait for previous
  destination (DonkeyEngineController.java:1207-1229); chains run in
  parallel with the last inline (Channel.java:1829-1868); Respond From
  and response precedence (ResponseSelector.java:24-105); batch
  sequencing (SourceConnector.java:201-295); thread names (ChannelTask,
  DestinationChain, RecoveryTask, PollConnectorJob).
- Deploy, Start, and Channel State: deploy sequence and Initial State
  (DonkeyEngineController.java:294-452, :1844-2045); start, stop, pause,
  resume and halt (Channel.java:697-1105); undeploy
  (DonkeyEngineController.java:2073-2155); code templates inlined at
  compile time (JavaScriptBuilder.java:103-115); dependency ordering
  (ChannelDependencyUtil.java:31-53); startup deploy
  (DonkeyEngineController.java:285-291).

4.7.0 behaviour documented
- A new database gets a random admin password, logged once at WARN as
  "Initial admin password is ...", unless server.initialadminpassword is
  set (ServerMigrator; observed). The first-login Welcome dialog requires
  a new password and appears on every user's first login
  (FirstLoginDialog, LoginPanel).
- conf/mirth.properties ships password.minlength 15,
  password.retrylimit 100 and password.lockoutperiod 1; an absent key
  reads as 0 (PasswordRequirementsChecker). Upgrades keep their file.
- With lockout on, a failed login says how many attempts remain for an
  existing account and when a locked one unlocks, while an unknown
  username gets only "Incorrect username or password." (observed). An
  unknown username also skips the password digest
  (DefaultUserController). Public as engine issue #240.
- oieserver, oieservice and oiecommand are install4j launchers in the
  installers and archives; oieserver.ps1 is not shipped. On the Linux
  archive, and on an install from the Linux installer: -jvm=<Java home>
  in conf/custom.vmoptions or INSTALL4J_JAVA_HOME_OVERRIDE chooses the
  Java, -java-cmd stops the server ("Unrecognized option: -java-cmd"),
  OIE_JAVA_PATH is ignored, and JAVA_HOME loses to a java on the PATH.
  All observed. The 4.6.0 Linux archive already shipped the same
  install4j oieserver, so the upgrade note is for those who started
  4.6.0 with oieserver.ps1.
- A preprocessor that ends without return leaves the message unchanged
  (JavaScriptUtil; observed). channelName is set in the postprocessor
  (Message.getMergedConnectorMessage; observed).
- removeAll without clearStatistics keeps statistics, despite the API
  documentation's default of true (observed).
- New fields: HTTP Listener Request Header Size; HTTP Sender Override
  Local Binding and Local Address.
- Extensions must list 4.7.0 in <mirthVersion>. A browser interface for
  an extension is a separately built web half in a webadmin/ folder of
  the extension zip, served on 4.7.0 by the Web Support extension (no
  /api/webplugins endpoint at f9ba73e28).

Corrections to the wiki and earlier pages, among others
- Retry Count bounds the attempts before queueing; a queued message is
  retried without limit and never becomes ERROR for running out.
- A connector with no filter and no transformer has no Transformed
  content; its Encoded content is the raw content, and only when the
  data types match and neither requires serializing.
- The destination queue is off by default on every type: the boolean
  passed to DestinationConnectorProperties is validateResponse.
- Database Reader Fetch Size applies only when Cache Results is No
  (DatabaseReceiverQuery); in JavaScript mode the connector opens no
  connection but still requires the driver.
- File Reader move-to directories are on the source, not the OIE server
  (FileReceiver.renameFile, deleteFile).
- Role-based access control and the additional connectors are community
  extensions, not commercial ones.
- Serialization follows the Set Data Types dialog: inbound is
  serialization to the internal representation, outbound is
  deserialization (DataTypePropertiesTableModel). The engine does the
  work, using the data type.
- The engine runs no SFTP server, and among the stock listeners only the
  DICOM Listener terminates TLS (DefaultTcpConfiguration,
  DefaultHttpConfiguration).
- The postprocessor's response map is writable; the channel map is not.

Not documented on purpose: server.api.allowhttp. Documenting it would be
a recipe for serving the API over plain HTTP.

Mirth Connect migration: the Linux procedure was run end to end on a
systemd host, from Mirth Connect 4.5.2 installed with NextGen's installer
as mcservice to OIE 4.7.0 from the Linux installer, on Derby and again on
PostgreSQL, SQL Server (both drivers), MySQL and Oracle. Channels,
messages (an encrypted channel included), users, code templates,
resources, the configuration map, global scripts and alerts came across,
and Mirth 4.5.2 refuses to start on the migrated database. The run added
the step that creates the oie account and narrowed the extension copy to
the extensions you added yourself. From the code: database changes start
below 4.3.0 (Migrate4_3_0; Migrate4_4_0 and Migrate4_5_2 touch no table);
<mirthVersion> is an exact, comma-split match. A keystore password
mismatch logs "Could not initialize security settings." and the server
reports that it started, with no web server. Windows and macOS are stubs
that name the steps needing platform equivalents.

Screenshots: the real 4.7.0 Swing client driven under a virtual display
against an engine seeded with sample channels, messages, a code template
library and a second user. One picture per source and destination
connector type, with the window sized to each panel. The first-login
picture replaces gs_oie_credential_update_step_1.png, which showed the
Edit User dialog and is removed. The Dashboard picture on Accessing the
Administrator, taken on 4.5.2, is retaken on a new 4.7.0 engine.
Accessing the Administrator and the Launchers page describe Launcher
only, with two pictures of it: it is the launcher known to work, and it
is maintained. The sections on NextGen's Mirth Connect Administrator
Launcher (and its picture, gs_mcal_step_1.png) and on Ballista are
dropped. The Launchers page notes that Windows Defender may flag Launcher
as a false positive.

Connector behaviour run on a 4.7.0 engine (the f9ba73e28 tarball on
Java 21, with SFTP, FTP, SMB, WebDAV, SMTP and JMS servers beside it):
- webdav does not work: the File Reader fails to start and the File
  Writer writes nothing, NoClassDefFoundError org/jdom/input/DOMBuilder.
  webdavclient4j-core-0.92 needs jdom 1.x; the engine ships only jdom2.
- The Web Service Listener fails every SOAP request on Java 19 and later
  (OpenIntegrationEngine/engine#509). With the issue's custom.vmoptions
  line it works, faults and Basic authentication included. The WSDL
  service is always DefaultAcceptMessageService; Service Name sets only
  the path.
- Check File Age over ftp uses the listing's time, which vsftpd gives to
  the minute: with File Age 15000 a file was read after 1 s (SFTP: 15 s).
- File Exists = Error refuses to overwrite over file, sftp and smb
  (2026-10-30) and over ftp against proftpd 1.3.8b and Ubuntu's vsftpd
  3.0.5 (2026-11-01). The Alpine vsftpd 3.0.5-r2 in the
  delfer/alpine-ftp-server test image lists a file by absolute path as
  empty unless the working directory is its folder, so there the check
  (FtpConnection.exists) misses it on a fresh connection; a test-server
  quirk, not an engine defect.
- Error Reading Action applies only when the file cannot be read or the
  dispatch throws (FileReceiver.processFile); a preprocessor error gets
  the After Processing Action.
- Store Received Objects in Directory is unused (MirthDcmRcv.onCStoreRQ
  writes no file).
- TCP Listener Receive Timeout closes an idle connection only when Keep
  Connection Open is No (TcpReceiver continues on a timeout otherwise).
- Database Reader JavaScript post-process: Once with Aggregate No runs
  from afterPoll with neither variable, and a returned List never reaches
  the aggregate post-process (DatabaseReceiverScript.afterPoll,
  DatabaseReceiver.processResultList).
- JMS classes load from the connector's own library resources
  (JmsClient). A durable subscription needs a Client ID with JNDI on too;
  the Administrator checks it only with JNDI off (JmsConnectorPanel).
- A JavaScript Writer script that throws is ERROR and never queued
  (JavaScriptDispatcher). HL7 v2 response validation gives ERROR for AE,
  AR and a control ID mismatch, and QUEUED for a blank reply or an
  invalid ACK (HL7v2ResponseValidator).

Velocity replacement, run on the same 4.7.0 build: ${name} is left as
literal text in a JavaScript step, a Mapper mapping and a Message Builder
mapping, while an HTTP Sender URL and content on the same message are
replaced; text with no $ is used exactly as typed
(ValueReplacer.hasReplaceableValues); ${COUNT} gave 1, 2 on one message,
3, 4 on the next and 1, 2 again after a redeploy. In Database Reader and
Writer SQL each ${} is bound as a JDBC parameter
(JdbcUtils.extractParameters): a quoted '${a}' fails, and an unresolved
one is bound as its own text.

Runtime pass over the other walked pages, on the same 4.7.0 build:
- Deploying a channel that is already deployed redeploys it: its
  undeploy and deploy scripts run again.
- A var declared in a filter rule is undefined in the transformer; a
  variable assigned without var is visible there.
- tmp exists in the source and destination filters when the connector's
  transformer has an outbound template. destinationSet exists in the
  Database Reader post-process when it has the processed message
  (DatabaseReceiverScript.UpdateTask).
- Raw storage keeps the source map (Channel: insertConnectorMessage with
  storeMaps || isRawDurable).
- Mapper and Message Builder Default Value and String Replacement are
  pasted into the script as JavaScript (MapperStep, MessageBuilderStep):
  an unquoted default makes the step error, an unquoted regular
  expression stops the deploy.
- A cron schedule that can never fire, a past year or one more than 100
  years ahead, leaves the channel stopped (Quartz SchedulerException).
- Rule Builder and Destination Set Filter values are JavaScript
  expressions compared as text, not regular expressions
  (RuleBuilderRule, DestinationSetFilterStep: field == value,
  field.indexOf(value)): an unquoted ADT errors every message with
  ReferenceError, and 'A.T' never matches ADT.
- Source map keys checked per connector on the engine; the JMS and
  Database Readers add none, but every source map holds the engine's
  destinationSet key. A JavaScript Reader returning a JavaScript array
  makes one message holding the array's text.
- A response transformer runs only when the response is not empty or
  its inbound data type is Raw (ResponseTransformerExecutor.isActive).
  With HL7 v2 it did not run for a refused connection, an HTTP 500
  with an empty body or a JavaScript Writer that throws, and did run
  for an HTTP 500 with a body (HttpDispatcher keeps the body as the
  response for every status). With Raw it ran in every case.
- Debug Channel opens the Rhino debugger window in the server process
  (MirthMain, MirthSwingGui). On a server started as shipped, with
  -Djava.awt.headless=true, the debug deploy fails with
  java.awt.HeadlessException and the channel is not deployed. The
  page points to the JavaScript Debugger community extension
  (gibson9583/oie-debugger), which runs in the Web Administrator.
- Configuration pages: password.graceperiod 0 = unlimited and -1 = none
  (DefaultUserController), reuseperiod 0 = off and -1 = never
  (PasswordRequirementsChecker), lockout only when retrylimit > 0
  (LoginRequirementsChecker), server.startuplocksleep waits on another
  server's startup lock row (DefaultMigrationController). A stored
  password hash is salt plus digest; verification tries the digest.*
  then the digest.fallback.* settings (Digester). On a Derby install
  appdata/ holds mirthdb/, keystore.jks, extension.properties,
  server.id and temp/, and configuration.properties once the
  Configuration Map is first saved (observed). The API's default
  security headers were checked on the engine.
- Operations pages run on the engine. A source connector that fails to
  start raises no alert (a TCP Listener on a port in use stopped with
  no alert while a transformer error did alert); no source or
  destination error event is dispatched from a start path. Raw storage
  keeps the source map, custom metadata and attachments
  (DonkeyEngineController storage settings). A logger line carries the
  thread, naming the script and channel, but no message ID, and the
  server does not reload log4j2.properties while running.
- Security pages: vulnerability reports go to the Eclipse Foundation
  Security Team (security@eclipse-foundation.org), matching the engine's
  SECURITY.md after OpenIntegrationEngine/engine#492; the
  openintegrationengine.org mailboxes are retired. Security Posture and
  Hardening is cut down to verifiable facts and the checklist, with no
  cloud provider assumed: no ClassShutter anywhere in the server, the
  listening ports, where TLS terminates, and the repository's Renovate
  and Gradle verification settings. The HTTPS listener accepted TLS 1.2
  and 1.3, and any non-blank X-Requested-With passed while a missing
  one got 400. TLS Manager 1.1.0 and Simple Channel History 2.9.0 were
  installed and run on 4.7.0.

Claim-by-claim check (2026-11-02 to 2026-11-05)
Every factual statement on the 53 pages was listed as a claim, 3,572 in
all, and each was settled one way: run on a 4.7.0 engine wherever it
could be run, read from the 4.7.0 client or server source only where it
cannot be run here (with the reason recorded), or taken from the 4.7.0
release notes, an upstream issue or pull request, or a standard. 103 were
wrong and are corrected. The Swing Administrator and the Web
Administrator were driven for real (under Xvfb, and with Playwright),
with Web Support, RBAC, TLS Manager and Simple Channel History installed.
Advice that is not a checkable fact was kept, reworded or cut. The
Windows and macOS halves of claims were not tested; they are left for
other contributors.

Behaviour broken in 4.7.0 is described as it should work, with a box:
exportmessages in the CLI (fixed by OpenIntegrationEngine/engine#539, not
yet in a release) and DICOM over TLS on Java 17 and 21
(OpenIntegrationEngine/engine#556). Four more are stated plainly until
issues are filed: the DICOM Sender's Priority sends High and Low swapped
against DICOM PS3.7; the DICOM A-RELEASE-RP timeout is never read; the
debugger's F8 does not step out while the source pane has focus; Remove
All Messages on a channel that is not deployed skips the two-at-a-time
delete limit.

Signed-off-by: Finnegan's Owner <44065187+pacmano1@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
pacmano1 added a commit to OpenIntegrationEngine/docs-website that referenced this pull request Nov 6, 2026
Moves the unofficial wiki (pacmano1/unofficial-oie-wiki) into the site,
adds four concept pages and a Mirth Connect migration guide, checks every
page against OIE 4.7.0 (tag v4.7.0, engine commit f9ba73e28), and adds 35
screenshots of the 4.7.0 desktop Administrator. 41 new pages; six
existing pages and the sidebar changed.

The site is a reference manual for the engine plus explanation of the
concepts people misunderstand. Wiki content that failed that test did not
move: the Quick Start tutorial and the best-practice and tips sections.
Facts from them that state engine behaviour went into the pages that own
them.

Structure
- Sidebar in reading order: Concepts first and open, then Getting
  Started, Migrating from Mirth Connect, Administrators, Channels,
  Connectors, Scripting, Configuration, Operations, Security, Extensions,
  CLI and API, Help and Reference. Glossary at the top level, with an
  anchor per term. Contributing and Building from Source at the top level.
- Three wiki pages overlapped pages from #13 and were folded in rather
  than duplicated: the Installation Guide's directory layout and heap
  default into Installation, the Administrator GUI tour as Desktop
  Administrator, and Plugins and Extensions into the Plugin Guide.
- Headings are sentence case inside a page.
- The glossary's Channel entry gives the Cloverleaf and Rhapsody terms
  for the same idea, from chimanwadike's core concepts page (#14).
- contributing.md gains a documentation contribution path.
- Screenshots get an edge and a drop shadow, and clicking one shows it
  full size (.vitepress/theme). No package: medium-zoom, the usual
  choice, published two versions on 2026-10-25 after none since 2023.

Sources
- Connector labels, panel order, field greying and defaults come from the
  Swing client: labels from each panel, order from initLayout, greying
  from the action handlers, defaults from the *Properties constructors.
  Engine behaviour comes from the server code. Every page was checked
  against f9ba73e28, the commit the 4.7.0 release was built from.
- Where a claim could be run, it was run on the 4.7.0 release's
  oie_unix_4_7_0.tar.gz (sha256 ab08558873dc6be1e474afaacc46efe94d29c945
  cd3381c10d0ae548ecb429de) in Docker.

Concept pages, first written from main in September 2026 (file:line at
that time), then rechecked at f9ba73e28:
- Message Lifecycle and Storage: statuses and where each is set
  (Status.java:18-40, Channel.java:1456, FilterTransformerExecutor.java:
  104-143, DestinationConnector.java:449-450, :949-959, :1010-1015); the
  per-channel tables (JdbcDao.java:2166-2178, donkeydbconf/postgres.xml:
  4-138); the storage mode flags (DonkeyEngineController.java:1270-1345)
  and the passthrough DAO for Disabled (PassthruDao.java:173-266);
  asynchronous commit (JdbcDao.java:2388-2403); remove content on
  completion (Channel.java:2055-2155); the pruner's skip statuses and
  thresholds (DataPruner.java:94-99, :247-304, :449-554, :701-725); custom
  metadata (MetaDataReplacer.java:29-59, Channel.java:2268-2316). At
  4.7.0: Transformed holds msg or tmp after the filter and transformer,
  even for a filtered message; Sent is stored before any send attempt;
  PENDING is set only when the response transformer will run
  (ResponseTransformerExecutor.isActive).
- Queueing: a queue is a status query with a buffer
  (ConnectorMessageQueue.java:24-116, default.xml:318-354); the source
  queue setting stored inverted as respondAfterProcessing
  (SourceConnectorProperties.java:73); On Failure sends only when the
  queue is empty (DestinationConnector.java:261-263); Retry Count applies
  before queueing only (:517-533) and the queue loop has no limit
  (:615-895); thread buckets (DestinationQueue.java:68-83, :273-329);
  pause keeps queues draining (Channel.java:890-901); recovery before
  queues start (RecoveryTask.java:86-156); the pruner never removes
  QUEUED (DataPruner.java:97).
- Threading and Ordering: Max Processing Threads is a fair semaphore
  (DefaultChannelProcessLock.java:22-45); chains are built at deploy from
  the enabled destinations in list order and Wait for previous
  destination (DonkeyEngineController.java:1207-1229); chains run in
  parallel with the last inline (Channel.java:1829-1868); Respond From
  and response precedence (ResponseSelector.java:24-105); batch
  sequencing (SourceConnector.java:201-295); thread names (ChannelTask,
  DestinationChain, RecoveryTask, PollConnectorJob).
- Deploy, Start, and Channel State: deploy sequence and Initial State
  (DonkeyEngineController.java:294-452, :1844-2045); start, stop, pause,
  resume and halt (Channel.java:697-1105); undeploy
  (DonkeyEngineController.java:2073-2155); code templates inlined at
  compile time (JavaScriptBuilder.java:103-115); dependency ordering
  (ChannelDependencyUtil.java:31-53); startup deploy
  (DonkeyEngineController.java:285-291).

4.7.0 behaviour documented
- A new database gets a random admin password, logged once at WARN as
  "Initial admin password is ...", unless server.initialadminpassword is
  set (ServerMigrator; observed). The first-login Welcome dialog requires
  a new password and appears on every user's first login
  (FirstLoginDialog, LoginPanel).
- conf/mirth.properties ships password.minlength 15,
  password.retrylimit 100 and password.lockoutperiod 1; an absent key
  reads as 0 (PasswordRequirementsChecker). Upgrades keep their file.
- With lockout on, a failed login says how many attempts remain for an
  existing account and when a locked one unlocks, while an unknown
  username gets only "Incorrect username or password." (observed). An
  unknown username also skips the password digest
  (DefaultUserController). Public as engine issue #240.
- oieserver, oieservice and oiecommand are install4j launchers in the
  installers and archives; oieserver.ps1 is not shipped. On the Linux
  archive, and on an install from the Linux installer: -jvm=<Java home>
  in conf/custom.vmoptions or INSTALL4J_JAVA_HOME_OVERRIDE chooses the
  Java, -java-cmd stops the server ("Unrecognized option: -java-cmd"),
  OIE_JAVA_PATH is ignored, and JAVA_HOME loses to a java on the PATH.
  All observed. The 4.6.0 Linux archive already shipped the same
  install4j oieserver, so the upgrade note is for those who started
  4.6.0 with oieserver.ps1.
- A preprocessor that ends without return leaves the message unchanged
  (JavaScriptUtil; observed). channelName is set in the postprocessor
  (Message.getMergedConnectorMessage; observed).
- removeAll without clearStatistics keeps statistics, despite the API
  documentation's default of true (observed).
- New fields: HTTP Listener Request Header Size; HTTP Sender Override
  Local Binding and Local Address.
- Extensions must list 4.7.0 in <mirthVersion>. A browser interface for
  an extension is a separately built web half in a webadmin/ folder of
  the extension zip, served on 4.7.0 by the Web Support extension (no
  /api/webplugins endpoint at f9ba73e28).

Corrections to the wiki and earlier pages, among others
- Retry Count bounds the attempts before queueing; a queued message is
  retried without limit and never becomes ERROR for running out.
- A connector with no filter and no transformer has no Transformed
  content; its Encoded content is the raw content, and only when the
  data types match and neither requires serializing.
- The destination queue is off by default on every type: the boolean
  passed to DestinationConnectorProperties is validateResponse.
- Database Reader Fetch Size applies only when Cache Results is No
  (DatabaseReceiverQuery); in JavaScript mode the connector opens no
  connection but still requires the driver.
- File Reader move-to directories are on the source, not the OIE server
  (FileReceiver.renameFile, deleteFile).
- Role-based access control and the additional connectors are community
  extensions, not commercial ones.
- Serialization follows the Set Data Types dialog: inbound is
  serialization to the internal representation, outbound is
  deserialization (DataTypePropertiesTableModel). The engine does the
  work, using the data type.
- The engine runs no SFTP server, and among the stock listeners only the
  DICOM Listener terminates TLS (DefaultTcpConfiguration,
  DefaultHttpConfiguration).
- The postprocessor's response map is writable; the channel map is not.

Not documented on purpose: server.api.allowhttp. Documenting it would be
a recipe for serving the API over plain HTTP.

Mirth Connect migration: the Linux procedure was run end to end on a
systemd host, from Mirth Connect 4.5.2 installed with NextGen's installer
as mcservice to OIE 4.7.0 from the Linux installer, on Derby and again on
PostgreSQL, SQL Server (both drivers), MySQL and Oracle. Channels,
messages (an encrypted channel included), users, code templates,
resources, the configuration map, global scripts and alerts came across,
and Mirth 4.5.2 refuses to start on the migrated database. The run added
the step that creates the oie account and narrowed the extension copy to
the extensions you added yourself. From the code: database changes start
below 4.3.0 (Migrate4_3_0; Migrate4_4_0 and Migrate4_5_2 touch no table);
<mirthVersion> is an exact, comma-split match. A keystore password
mismatch logs "Could not initialize security settings." and the server
reports that it started, with no web server. Windows and macOS are stubs
that name the steps needing platform equivalents.

Screenshots: the real 4.7.0 Swing client driven under a virtual display
against an engine seeded with sample channels, messages, a code template
library and a second user. One picture per source and destination
connector type, with the window sized to each panel. The first-login
picture replaces gs_oie_credential_update_step_1.png, which showed the
Edit User dialog and is removed. The Dashboard picture on Accessing the
Administrator, taken on 4.5.2, is retaken on a new 4.7.0 engine.
Accessing the Administrator and the Launchers page describe Launcher
only, with two pictures of it: it is the launcher known to work, and it
is maintained. The sections on NextGen's Mirth Connect Administrator
Launcher (and its picture, gs_mcal_step_1.png) and on Ballista are
dropped. The Launchers page notes that Windows Defender may flag Launcher
as a false positive.

Connector behaviour run on a 4.7.0 engine (the f9ba73e28 tarball on
Java 21, with SFTP, FTP, SMB, WebDAV, SMTP and JMS servers beside it):
- webdav does not work: the File Reader fails to start and the File
  Writer writes nothing, NoClassDefFoundError org/jdom/input/DOMBuilder.
  webdavclient4j-core-0.92 needs jdom 1.x; the engine ships only jdom2.
- The Web Service Listener fails every SOAP request on Java 19 and later
  (OpenIntegrationEngine/engine#509). With the issue's custom.vmoptions
  line it works, faults and Basic authentication included. The WSDL
  service is always DefaultAcceptMessageService; Service Name sets only
  the path.
- Check File Age over ftp uses the listing's time, which vsftpd gives to
  the minute: with File Age 15000 a file was read after 1 s (SFTP: 15 s).
- File Exists = Error refuses to overwrite over file, sftp and smb
  (2026-10-30) and over ftp against proftpd 1.3.8b and Ubuntu's vsftpd
  3.0.5 (2026-11-01). The Alpine vsftpd 3.0.5-r2 in the
  delfer/alpine-ftp-server test image lists a file by absolute path as
  empty unless the working directory is its folder, so there the check
  (FtpConnection.exists) misses it on a fresh connection; a test-server
  quirk, not an engine defect.
- Error Reading Action applies only when the file cannot be read or the
  dispatch throws (FileReceiver.processFile); a preprocessor error gets
  the After Processing Action.
- Store Received Objects in Directory is unused (MirthDcmRcv.onCStoreRQ
  writes no file).
- TCP Listener Receive Timeout closes an idle connection only when Keep
  Connection Open is No (TcpReceiver continues on a timeout otherwise).
- Database Reader JavaScript post-process: Once with Aggregate No runs
  from afterPoll with neither variable, and a returned List never reaches
  the aggregate post-process (DatabaseReceiverScript.afterPoll,
  DatabaseReceiver.processResultList).
- JMS classes load from the connector's own library resources
  (JmsClient). A durable subscription needs a Client ID with JNDI on too;
  the Administrator checks it only with JNDI off (JmsConnectorPanel).
- A JavaScript Writer script that throws is ERROR and never queued
  (JavaScriptDispatcher). HL7 v2 response validation gives ERROR for AE,
  AR and a control ID mismatch, and QUEUED for a blank reply or an
  invalid ACK (HL7v2ResponseValidator).

Velocity replacement, run on the same 4.7.0 build: ${name} is left as
literal text in a JavaScript step, a Mapper mapping and a Message Builder
mapping, while an HTTP Sender URL and content on the same message are
replaced; text with no $ is used exactly as typed
(ValueReplacer.hasReplaceableValues); ${COUNT} gave 1, 2 on one message,
3, 4 on the next and 1, 2 again after a redeploy. In Database Reader and
Writer SQL each ${} is bound as a JDBC parameter
(JdbcUtils.extractParameters): a quoted '${a}' fails, and an unresolved
one is bound as its own text.

Runtime pass over the other walked pages, on the same 4.7.0 build:
- Deploying a channel that is already deployed redeploys it: its
  undeploy and deploy scripts run again.
- A var declared in a filter rule is undefined in the transformer; a
  variable assigned without var is visible there.
- tmp exists in the source and destination filters when the connector's
  transformer has an outbound template. destinationSet exists in the
  Database Reader post-process when it has the processed message
  (DatabaseReceiverScript.UpdateTask).
- Raw storage keeps the source map (Channel: insertConnectorMessage with
  storeMaps || isRawDurable).
- Mapper and Message Builder Default Value and String Replacement are
  pasted into the script as JavaScript (MapperStep, MessageBuilderStep):
  an unquoted default makes the step error, an unquoted regular
  expression stops the deploy.
- A cron schedule that can never fire, a past year or one more than 100
  years ahead, leaves the channel stopped (Quartz SchedulerException).
- Rule Builder and Destination Set Filter values are JavaScript
  expressions compared as text, not regular expressions
  (RuleBuilderRule, DestinationSetFilterStep: field == value,
  field.indexOf(value)): an unquoted ADT errors every message with
  ReferenceError, and 'A.T' never matches ADT.
- Source map keys checked per connector on the engine; the JMS and
  Database Readers add none, but every source map holds the engine's
  destinationSet key. A JavaScript Reader returning a JavaScript array
  makes one message holding the array's text.
- A response transformer runs only when the response is not empty or
  its inbound data type is Raw (ResponseTransformerExecutor.isActive).
  With HL7 v2 it did not run for a refused connection, an HTTP 500
  with an empty body or a JavaScript Writer that throws, and did run
  for an HTTP 500 with a body (HttpDispatcher keeps the body as the
  response for every status). With Raw it ran in every case.
- Debug Channel opens the Rhino debugger window in the server process
  (MirthMain, MirthSwingGui). On a server started as shipped, with
  -Djava.awt.headless=true, the debug deploy fails with
  java.awt.HeadlessException and the channel is not deployed. The
  page points to the JavaScript Debugger community extension
  (gibson9583/oie-debugger), which runs in the Web Administrator.
- Configuration pages: password.graceperiod 0 = unlimited and -1 = none
  (DefaultUserController), reuseperiod 0 = off and -1 = never
  (PasswordRequirementsChecker), lockout only when retrylimit > 0
  (LoginRequirementsChecker), server.startuplocksleep waits on another
  server's startup lock row (DefaultMigrationController). A stored
  password hash is salt plus digest; verification tries the digest.*
  then the digest.fallback.* settings (Digester). On a Derby install
  appdata/ holds mirthdb/, keystore.jks, extension.properties,
  server.id and temp/, and configuration.properties once the
  Configuration Map is first saved (observed). The API's default
  security headers were checked on the engine.
- Operations pages run on the engine. A source connector that fails to
  start raises no alert (a TCP Listener on a port in use stopped with
  no alert while a transformer error did alert); no source or
  destination error event is dispatched from a start path. Raw storage
  keeps the source map, custom metadata and attachments
  (DonkeyEngineController storage settings). A logger line carries the
  thread, naming the script and channel, but no message ID, and the
  server does not reload log4j2.properties while running.
- Security pages: vulnerability reports go to the Eclipse Foundation
  Security Team (security@eclipse-foundation.org), matching the engine's
  SECURITY.md after OpenIntegrationEngine/engine#492; the
  openintegrationengine.org mailboxes are retired. Security Posture and
  Hardening is cut down to verifiable facts and the checklist, with no
  cloud provider assumed: no ClassShutter anywhere in the server, the
  listening ports, where TLS terminates, and the repository's Renovate
  and Gradle verification settings. The HTTPS listener accepted TLS 1.2
  and 1.3, and any non-blank X-Requested-With passed while a missing
  one got 400. TLS Manager 1.1.0 and Simple Channel History 2.9.0 were
  installed and run on 4.7.0.

Claim-by-claim check (2026-11-02 to 2026-11-05)
Every factual statement on the 53 pages was listed as a claim, 3,572 in
all, and each was settled one way: run on a 4.7.0 engine wherever it
could be run, read from the 4.7.0 client or server source only where it
cannot be run here (with the reason recorded), or taken from the 4.7.0
release notes, an upstream issue or pull request, or a standard. 103 were
wrong and are corrected. The Swing Administrator and the Web
Administrator were driven for real (under Xvfb, and with Playwright),
with Web Support, RBAC, TLS Manager and Simple Channel History installed.
Advice that is not a checkable fact was kept, reworded or cut. The
Windows and macOS halves of claims were not tested; they are left for
other contributors.

Behaviour broken in 4.7.0 is described as it should work, with a box:
exportmessages in the CLI (fixed by OpenIntegrationEngine/engine#539, not
yet in a release) and DICOM over TLS on Java 17 and 21
(OpenIntegrationEngine/engine#556). Four more are stated plainly until
issues are filed: the DICOM Sender's Priority sends High and Low swapped
against DICOM PS3.7; the DICOM A-RELEASE-RP timeout is never read; the
debugger's F8 does not step out while the source pane has focus; Remove
All Messages on a channel that is not deployed skips the two-at-a-time
delete limit.

Signed-off-by: Finnegan's Owner <44065187+pacmano1@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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.

5 participants