From cba21a2021db0597011adc384a25ddbfa03c2110 Mon Sep 17 00:00:00 2001 From: Ezio Caffi Date: Thu, 16 Jul 2026 09:28:52 +0200 Subject: [PATCH 1/3] Treat blank channel scripts as absent instead of compiling 'null' body (#344) Signed-off-by: Ezio Caffi --- .../util/javascript/JavaScriptUtil.java | 7 + .../util/javascript/JavaScriptUtilTest.java | 133 ++++++++++++++++++ 2 files changed, 140 insertions(+) create mode 100644 server/src/test/java/com/mirth/connect/server/util/javascript/JavaScriptUtilTest.java diff --git a/server/src/main/java/com/mirth/connect/server/util/javascript/JavaScriptUtil.java b/server/src/main/java/com/mirth/connect/server/util/javascript/JavaScriptUtil.java index 631ecb40b6..79e0d7702d 100644 --- a/server/src/main/java/com/mirth/connect/server/util/javascript/JavaScriptUtil.java +++ b/server/src/main/java/com/mirth/connect/server/util/javascript/JavaScriptUtil.java @@ -710,6 +710,13 @@ public static boolean compileAndAddScript(String channelId, MirthContextFactory // Note: If the defaultScript is NULL, this means that the script should // always be inserted without being compared. + // A null or blank script does nothing; treat it as absent instead of + // compiling a wrapper whose body is the literal string "null" (MIRTH/OIE #344) + if (StringUtils.isBlank(script)) { + compiledScriptCache.removeCompiledScript(scriptId); + return false; + } + boolean scriptInserted = false; String generatedScript = null; diff --git a/server/src/test/java/com/mirth/connect/server/util/javascript/JavaScriptUtilTest.java b/server/src/test/java/com/mirth/connect/server/util/javascript/JavaScriptUtilTest.java new file mode 100644 index 0000000000..1d9b55d463 --- /dev/null +++ b/server/src/test/java/com/mirth/connect/server/util/javascript/JavaScriptUtilTest.java @@ -0,0 +1,133 @@ +/* + * Copyright (c) Mirth Corporation. All rights reserved. + * + * http://www.mirthcorp.com + * + * The software in this package is published under the terms of the MPL license a copy of which has + * been included with this distribution in the LICENSE.txt file. + */ + +package com.mirth.connect.server.util.javascript; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.io.File; +import java.lang.reflect.Field; +import java.net.URL; +import java.net.URLClassLoader; +import java.util.HashSet; + +import org.junit.After; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; + +import com.google.inject.AbstractModule; +import com.google.inject.Guice; +import com.google.inject.Injector; +import com.mirth.connect.model.codetemplates.ContextType; +import com.mirth.connect.server.builders.JavaScriptBuilder; +import com.mirth.connect.server.controllers.CodeTemplateController; +import com.mirth.connect.server.controllers.ConfigurationController; +import com.mirth.connect.server.controllers.ControllerFactory; +import com.mirth.connect.server.controllers.EventController; +import com.mirth.connect.server.controllers.ExtensionController; +import com.mirth.connect.server.util.CompiledScriptCache; + +public class JavaScriptUtilTest { + + private static final String SCRIPT_ID = "JavaScriptUtilTest-script"; + + private static ClassLoader originalContextClassLoader; + + @BeforeClass + public static void setUpBeforeClass() throws Exception { + /* + * mirth.properties isn't on the unit test classpath, but JavaScriptScopeUtil's static init + * requires it to be resolvable via the context classloader. Point the context classloader + * at the real conf/ dir; restored after the class. + */ + originalContextClassLoader = Thread.currentThread().getContextClassLoader(); + URL confDir = new File("conf").toURI().toURL(); + Thread.currentThread().setContextClassLoader(new URLClassLoader(new URL[] { confDir }, originalContextClassLoader)); + + // Same mocked ControllerFactory pattern as FileReceiverTest, so this class is + // self-sufficient regardless of which test classes ran (and injected) before it. + ControllerFactory controllerFactory = mock(ControllerFactory.class); + + EventController eventController = mock(EventController.class); + when(controllerFactory.createEventController()).thenReturn(eventController); + + ConfigurationController configurationController = mock(ConfigurationController.class); + when(controllerFactory.createConfigurationController()).thenReturn(configurationController); + + ExtensionController extensionController = mock(ExtensionController.class); + when(controllerFactory.createExtensionController()).thenReturn(extensionController); + + CodeTemplateController codeTemplateController = mock(CodeTemplateController.class); + when(controllerFactory.createCodeTemplateController()).thenReturn(codeTemplateController); + + Injector injector = Guice.createInjector(new AbstractModule() { + @Override + protected void configure() { + requestStaticInjection(ControllerFactory.class); + bind(ControllerFactory.class).toInstance(controllerFactory); + } + }); + injector.getInstance(ControllerFactory.class); + + /* + * JavaScriptBuilder captures its controllers in static fields at class-load time. If an + * earlier test class loaded it with a mocked factory that left them null, repair them so + * generateGlobalSealedScript/appendCodeTemplates don't NPE. + */ + setJavaScriptBuilderStaticField("extensionController", extensionController); + setJavaScriptBuilderStaticField("codeTemplateController", codeTemplateController); + } + + @AfterClass + public static void tearDownAfterClass() { + Thread.currentThread().setContextClassLoader(originalContextClassLoader); + } + + private static void setJavaScriptBuilderStaticField(String fieldName, Object value) throws Exception { + Field field = JavaScriptBuilder.class.getDeclaredField(fieldName); + field.setAccessible(true); + field.set(null, value); + } + + private MirthContextFactory contextFactory() { + return new MirthContextFactory(new URL[0], new HashSet<>(), false); + } + + @After + public void cleanup() { + CompiledScriptCache.getInstance().removeCompiledScript(SCRIPT_ID); + } + + @Test + public void compileAndAddScriptWithNullScriptDoesNotCompile() throws Exception { + boolean inserted = JavaScriptUtil.compileAndAddScript("channelId", contextFactory(), SCRIPT_ID, null, ContextType.CHANNEL_PREPROCESSOR); + assertFalse(inserted); + assertNull(CompiledScriptCache.getInstance().getCompiledScript(SCRIPT_ID)); + } + + @Test + public void compileAndAddScriptWithBlankScriptDoesNotCompile() throws Exception { + boolean inserted = JavaScriptUtil.compileAndAddScript("channelId", contextFactory(), SCRIPT_ID, " \n", ContextType.CHANNEL_PREPROCESSOR); + assertFalse(inserted); + assertNull(CompiledScriptCache.getInstance().getCompiledScript(SCRIPT_ID)); + } + + @Test + public void compileAndAddScriptWithRealScriptCompiles() throws Exception { + boolean inserted = JavaScriptUtil.compileAndAddScript("channelId", contextFactory(), SCRIPT_ID, "var x = 1; return 'x';", ContextType.CHANNEL_PREPROCESSOR); + assertTrue(inserted); + assertNotNull(CompiledScriptCache.getInstance().getCompiledScript(SCRIPT_ID)); + } +} From 3960aabfb9ae0f298394c6df0f59088b8052f8e1 Mon Sep 17 00:00:00 2001 From: Ezio Caffi Date: Thu, 16 Jul 2026 09:28:52 +0200 Subject: [PATCH 2/3] Ignore undefined preprocessor script results (#344) Signed-off-by: Ezio Caffi --- .../util/javascript/JavaScriptUtil.java | 4 +- .../util/javascript/JavaScriptUtilTest.java | 51 +++++++++++++++++++ 2 files changed, 53 insertions(+), 2 deletions(-) diff --git a/server/src/main/java/com/mirth/connect/server/util/javascript/JavaScriptUtil.java b/server/src/main/java/com/mirth/connect/server/util/javascript/JavaScriptUtil.java index 79e0d7702d..99e2301033 100644 --- a/server/src/main/java/com/mirth/connect/server/util/javascript/JavaScriptUtil.java +++ b/server/src/main/java/com/mirth/connect/server/util/javascript/JavaScriptUtil.java @@ -197,7 +197,7 @@ public static String executePreprocessorScripts(JavaScriptTask task, Con } } - if (result != null) { + if (result != null && !(result instanceof Undefined)) { String resultString = (String) Context.jsToJava(result, java.lang.String.class); // Set the processed message in case something goes wrong in the channel processor. Also update the global result so the channel processor uses the updated message @@ -226,7 +226,7 @@ public static String executePreprocessorScripts(JavaScriptTask task, Con } } - if (result != null) { + if (result != null && !(result instanceof Undefined)) { String resultString = (String) Context.jsToJava(result, java.lang.String.class); // Set the processed message if there was a result. diff --git a/server/src/test/java/com/mirth/connect/server/util/javascript/JavaScriptUtilTest.java b/server/src/test/java/com/mirth/connect/server/util/javascript/JavaScriptUtilTest.java index 1d9b55d463..7e4675127c 100644 --- a/server/src/test/java/com/mirth/connect/server/util/javascript/JavaScriptUtilTest.java +++ b/server/src/test/java/com/mirth/connect/server/util/javascript/JavaScriptUtilTest.java @@ -9,6 +9,7 @@ package com.mirth.connect.server.util.javascript; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; @@ -20,6 +21,7 @@ import java.lang.reflect.Field; import java.net.URL; import java.net.URLClassLoader; +import java.util.HashMap; import java.util.HashSet; import org.junit.After; @@ -30,6 +32,8 @@ import com.google.inject.AbstractModule; import com.google.inject.Guice; import com.google.inject.Injector; +import com.mirth.connect.donkey.model.message.ConnectorMessage; +import com.mirth.connect.donkey.model.message.MessageContent; import com.mirth.connect.model.codetemplates.ContextType; import com.mirth.connect.server.builders.JavaScriptBuilder; import com.mirth.connect.server.controllers.CodeTemplateController; @@ -37,6 +41,7 @@ import com.mirth.connect.server.controllers.ControllerFactory; import com.mirth.connect.server.controllers.EventController; import com.mirth.connect.server.controllers.ExtensionController; +import com.mirth.connect.server.controllers.ScriptController; import com.mirth.connect.server.util.CompiledScriptCache; public class JavaScriptUtilTest { @@ -130,4 +135,50 @@ public void compileAndAddScriptWithRealScriptCompiles() throws Exception { assertTrue(inserted); assertNotNull(CompiledScriptCache.getInstance().getCompiledScript(SCRIPT_ID)); } + + private static final String CHANNEL_ID = "JavaScriptUtilTest-channel"; + + private ConnectorMessage messageWithRaw() { + ConnectorMessage message = mock(ConnectorMessage.class); + MessageContent rawContent = mock(MessageContent.class); + when(message.getRaw()).thenReturn(rawContent); + when(rawContent.getContent()).thenReturn("MSH|^~\\&|X"); + when(message.getChannelId()).thenReturn(CHANNEL_ID); + return message; + } + + private JavaScriptTask task(MirthContextFactory contextFactory) { + return new JavaScriptTask<>(contextFactory, "JavaScriptUtilTest") { + @Override + public Object doCall() { + return null; + } + }; + } + + @Test + public void preprocessorReturningNothingYieldsNullNotUndefined() throws Exception { + String scriptId = ScriptController.getScriptId(ScriptController.PREPROCESSOR_SCRIPT_KEY, CHANNEL_ID); + MirthContextFactory contextFactory = contextFactory(); + try { + JavaScriptUtil.compileAndAddScript(CHANNEL_ID, contextFactory, scriptId, "var unused = 1;", ContextType.CHANNEL_PREPROCESSOR); + String result = JavaScriptUtil.executePreprocessorScripts(task(contextFactory), messageWithRaw(), new HashMap<>(), null); + assertNull(result); + } finally { + CompiledScriptCache.getInstance().removeCompiledScript(scriptId); + } + } + + @Test + public void preprocessorReturningStringYieldsThatString() throws Exception { + String scriptId = ScriptController.getScriptId(ScriptController.PREPROCESSOR_SCRIPT_KEY, CHANNEL_ID); + MirthContextFactory contextFactory = contextFactory(); + try { + JavaScriptUtil.compileAndAddScript(CHANNEL_ID, contextFactory, scriptId, "return 'processed';", ContextType.CHANNEL_PREPROCESSOR); + String result = JavaScriptUtil.executePreprocessorScripts(task(contextFactory), messageWithRaw(), new HashMap<>(), null); + assertEquals("processed", result); + } finally { + CompiledScriptCache.getInstance().removeCompiledScript(scriptId); + } + } } From 762e0723b72ba89430812a91318aca5969886e2e Mon Sep 17 00:00:00 2001 From: Ezio Caffi Date: Sat, 25 Jul 2026 19:32:49 +0200 Subject: [PATCH 3/3] Address review feedback on undefined checks and test setup (#351) - Use Undefined.isUndefined() instead of instanceof, which also covers SCRIPTABLE_UNDEFINED; apply the same idiom to the postprocessor and attachment-script result handling for consistency - Load mirth.properties from test resources instead of pointing the context classloader at conf/ - Replace reflection on JavaScriptBuilder's static controllers with a @VisibleForTesting setter (public: the test lives in a different package) Signed-off-by: Ezio Caffi --- .../server/builders/JavaScriptBuilder.java | 7 ++ .../util/javascript/JavaScriptUtil.java | 8 +- .../util/javascript/JavaScriptUtilTest.java | 31 +---- server/src/test/resources/mirth.properties | 114 ++++++++++++++++++ 4 files changed, 127 insertions(+), 33 deletions(-) create mode 100644 server/src/test/resources/mirth.properties diff --git a/server/src/main/java/com/mirth/connect/server/builders/JavaScriptBuilder.java b/server/src/main/java/com/mirth/connect/server/builders/JavaScriptBuilder.java index b31f32ab59..65a34f6d83 100644 --- a/server/src/main/java/com/mirth/connect/server/builders/JavaScriptBuilder.java +++ b/server/src/main/java/com/mirth/connect/server/builders/JavaScriptBuilder.java @@ -27,6 +27,7 @@ import com.mirth.connect.model.MetaData; import com.mirth.connect.model.Rule; import com.mirth.connect.model.Step; +import com.google.common.annotations.VisibleForTesting; import com.mirth.connect.model.Transformer; import com.mirth.connect.model.codetemplates.CodeTemplate; import com.mirth.connect.model.codetemplates.CodeTemplateLibrary; @@ -45,6 +46,12 @@ public class JavaScriptBuilder { private static ExtensionController extensionController = ControllerFactory.getFactory().createExtensionController(); private static CodeTemplateController codeTemplateController = ControllerFactory.getFactory().createCodeTemplateController(); + @VisibleForTesting + public static void setControllersForTesting(ExtensionController ec, CodeTemplateController ctc) { + extensionController = ec; + codeTemplateController = ctc; + } + /* * Generates the global JavaScript contained in all new scopes created */ diff --git a/server/src/main/java/com/mirth/connect/server/util/javascript/JavaScriptUtil.java b/server/src/main/java/com/mirth/connect/server/util/javascript/JavaScriptUtil.java index 99e2301033..592b899e5b 100644 --- a/server/src/main/java/com/mirth/connect/server/util/javascript/JavaScriptUtil.java +++ b/server/src/main/java/com/mirth/connect/server/util/javascript/JavaScriptUtil.java @@ -129,7 +129,7 @@ public Object doCall() throws Exception { throw e; } - if (result != null) { + if (result != null && !(Undefined.isUndefined(result))) { String resultString = (String) Context.jsToJava(result, java.lang.String.class); if (resultString != null) { @@ -197,7 +197,7 @@ public static String executePreprocessorScripts(JavaScriptTask task, Con } } - if (result != null && !(result instanceof Undefined)) { + if (result != null && !(Undefined.isUndefined(result))) { String resultString = (String) Context.jsToJava(result, java.lang.String.class); // Set the processed message in case something goes wrong in the channel processor. Also update the global result so the channel processor uses the updated message @@ -226,7 +226,7 @@ public static String executePreprocessorScripts(JavaScriptTask task, Con } } - if (result != null && !(result instanceof Undefined)) { + if (result != null && !(Undefined.isUndefined(result))) { String resultString = (String) Context.jsToJava(result, java.lang.String.class); // Set the processed message if there was a result. @@ -330,7 +330,7 @@ private static Response getPostprocessorResponse(Object result) { // TODO: is it okay that we use Status.SENT here? response = new Response(Status.SENT, object.toString()); } - } else if ((result != null) && !(result instanceof Undefined)) { + } else if ((result != null) && !(Undefined.isUndefined(result))) { // This branch will catch all objects that aren't Response, NativeJavaObject, Undefined, or null // Assume it's a string, and return a successful response // TODO: is it okay that we use Status.SENT here? diff --git a/server/src/test/java/com/mirth/connect/server/util/javascript/JavaScriptUtilTest.java b/server/src/test/java/com/mirth/connect/server/util/javascript/JavaScriptUtilTest.java index 7e4675127c..90221a55f8 100644 --- a/server/src/test/java/com/mirth/connect/server/util/javascript/JavaScriptUtilTest.java +++ b/server/src/test/java/com/mirth/connect/server/util/javascript/JavaScriptUtilTest.java @@ -17,15 +17,11 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; -import java.io.File; -import java.lang.reflect.Field; import java.net.URL; -import java.net.URLClassLoader; import java.util.HashMap; import java.util.HashSet; import org.junit.After; -import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Test; @@ -48,19 +44,8 @@ public class JavaScriptUtilTest { private static final String SCRIPT_ID = "JavaScriptUtilTest-script"; - private static ClassLoader originalContextClassLoader; - @BeforeClass - public static void setUpBeforeClass() throws Exception { - /* - * mirth.properties isn't on the unit test classpath, but JavaScriptScopeUtil's static init - * requires it to be resolvable via the context classloader. Point the context classloader - * at the real conf/ dir; restored after the class. - */ - originalContextClassLoader = Thread.currentThread().getContextClassLoader(); - URL confDir = new File("conf").toURI().toURL(); - Thread.currentThread().setContextClassLoader(new URLClassLoader(new URL[] { confDir }, originalContextClassLoader)); - + public static void setUpBeforeClass() { // Same mocked ControllerFactory pattern as FileReceiverTest, so this class is // self-sufficient regardless of which test classes ran (and injected) before it. ControllerFactory controllerFactory = mock(ControllerFactory.class); @@ -91,19 +76,7 @@ protected void configure() { * earlier test class loaded it with a mocked factory that left them null, repair them so * generateGlobalSealedScript/appendCodeTemplates don't NPE. */ - setJavaScriptBuilderStaticField("extensionController", extensionController); - setJavaScriptBuilderStaticField("codeTemplateController", codeTemplateController); - } - - @AfterClass - public static void tearDownAfterClass() { - Thread.currentThread().setContextClassLoader(originalContextClassLoader); - } - - private static void setJavaScriptBuilderStaticField(String fieldName, Object value) throws Exception { - Field field = JavaScriptBuilder.class.getDeclaredField(fieldName); - field.setAccessible(true); - field.set(null, value); + JavaScriptBuilder.setControllersForTesting(extensionController, codeTemplateController); } private MirthContextFactory contextFactory() { diff --git a/server/src/test/resources/mirth.properties b/server/src/test/resources/mirth.properties new file mode 100644 index 0000000000..cbc3a21cec --- /dev/null +++ b/server/src/test/resources/mirth.properties @@ -0,0 +1,114 @@ +# Mirth Connect configuration file + +# directories +dir.appdata = appdata +dir.tempdata = ${dir.appdata}/temp + +# ports +http.port = 8080 +https.port = 8443 + +# password requirements +password.minlength = 0 +password.minupper = 0 +password.minlower = 0 +password.minnumeric = 0 +password.minspecial = 0 +password.retrylimit = 0 +password.lockoutperiod = 0 +password.expiration = 0 +password.graceperiod = 0 +password.reuseperiod = 0 +password.reuselimit = 0 + +# Only used for migration purposes, do not modify +version = 4.6.0 + +# keystore +keystore.path = ${dir.appdata}/keystore.jks +keystore.storepass = 81uWxplDtB +keystore.keypass = 81uWxplDtB +keystore.type = JCEKS + +# server +http.contextpath = / +server.url = + +http.host = 0.0.0.0 +https.host = 0.0.0.0 + +https.client.protocols = TLSv1.3,TLSv1.2 +https.server.protocols = TLSv1.3,TLSv1.2,SSLv2Hello +https.ciphersuites = TLS_CHACHA20_POLY1305_SHA256,TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256,TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256,TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256,TLS_AES_256_GCM_SHA384,TLS_AES_128_GCM_SHA256,TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,TLS_RSA_WITH_AES_256_GCM_SHA384,TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384,TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384,TLS_DHE_RSA_WITH_AES_256_GCM_SHA384,TLS_DHE_DSS_WITH_AES_256_GCM_SHA384,TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,TLS_RSA_WITH_AES_128_GCM_SHA256,TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256,TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256,TLS_DHE_RSA_WITH_AES_128_GCM_SHA256,TLS_DHE_DSS_WITH_AES_128_GCM_SHA256,TLS_EMPTY_RENEGOTIATION_INFO_SCSV +https.ephemeraldhkeysize = 2048 + +# If set to true, the Connect REST API will require all incoming requests to contain an "X-Requested-With" header. +# This protects against Cross-Site Request Forgery (CSRF) security vulnerabilities. +server.api.require-requested-with = true + +# CORS headers +server.api.accesscontrolalloworigin = * +server.api.accesscontrolallowcredentials = false +server.api.accesscontrolallowmethods = GET, POST, DELETE, PUT +server.api.accesscontrolallowheaders = Content-Type +server.api.accesscontrolexposeheaders = +server.api.accesscontrolmaxage = + +# Determines whether or not channels are deployed on server startup. +server.startupdeploy = true + +# Determines whether libraries in the custom-lib directory will be included on the server classpath. +# To reduce potential classpath conflicts you should create Resources and use them on specific channels/connectors instead, and then set this value to false. +server.includecustomlib = false + +# administrator +administrator.maxheapsize = 512m + +# properties file that will store the configuration map and be loaded during server startup +configurationmap.path = ${dir.appdata}/configuration.properties + +# The language version for the Rhino JavaScript engine (supported values: 1.0, 1.1, ..., 1.8, es6). +rhino.languageversion = es6 + +# options: derby, mysql, postgres, oracle, sqlserver +database = derby + +# examples: +# Derby jdbc:derby:${dir.appdata}/mirthdb;create=true +# PostgreSQL jdbc:postgresql://localhost:5432/mirthdb +# MySQL jdbc:mysql://localhost:3306/mirthdb +# Oracle jdbc:oracle:thin:@localhost:1521:DB +# SQL Server/Sybase (jTDS) jdbc:jtds:sqlserver://localhost:1433/mirthdb +# Microsoft SQL Server jdbc:sqlserver://localhost:1433;databaseName=mirthdb +# If you are using the Microsoft SQL Server driver, please also specify database.driver below +database.url = jdbc:derby:${dir.appdata}/mirthdb;create=true + +# If using a custom or non-default driver, specify it here. +# example: +# Microsoft SQL server: database.driver = com.microsoft.sqlserver.jdbc.SQLServerDriver +# (Note: the jTDS driver is used by default for sqlserver) +#database.driver = + +# Maximum number of connections allowed for the main read/write connection pool +database.max-connections = 20 +# Maximum number of connections allowed for the read-only connection pool +database-readonly.max-connections = 20 + +# database credentials +database.username = +database.password = + +#On startup, Maximum number of retries to establish database connections in case of failure +database.connection.maxretry = 2 + +#On startup, Maximum wait time in milliseconds for retry to establish database connections in case of failure +database.connection.retrywaitinmilliseconds = 10000 + +# If true, various read-only statements are separated into their own connection pool. +# By default the read-only pool will use the same connection information as the master pool, +# but you can change this with the "database-readonly" options. For example, to point the +# read-only pool to a different JDBC URL: +# +# database-readonly.url = jdbc:... +# +database.enable-read-write-split = true