Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions server/conf/mirth.properties
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
# Mirth Connect configuration file
#
# Put local customizations in files under conf/mirth.properties.d instead of
# editing this file, so a new release's mirth.properties can be dropped in
# without hand-merging. See conf/mirth.properties.d/mirth.properties.example.

# directories
dir.appdata = appdata
Expand Down
21 changes: 21 additions & 0 deletions server/conf/mirth.properties.d/mirth.properties.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Copy this file to a name ending in .properties (for example 10-local.properties)
# to activate it. Files in this directory named *.properties are loaded on top of
# conf/mirth.properties at startup, in lexical filename order: later files
# override earlier ones, and any file here overrides mirth.properties itself.
#
# Keeping customizations here instead of in mirth.properties means an upgrade
# can replace mirth.properties without hand-merging your changes back in.
#
# Exception: database.password and database-readonly.password are encrypted in
# place in mirth.properties; a value here stays plaintext. keystore.storepass
# and keystore.keypass are auto-generated on first boot only when BOTH are
# still the shipped defaults - overriding just one here leaves the other at
# its published default. Leave these four in mirth.properties.
#
# Files are parsed with java.util.Properties semantics. If a key appears more
# than once, the last occurrence wins; there are no multi-valued properties. For
# list properties such as https.client.protocols, use a single comma-separated
# value.

# http.port = 8081
# https.port = 8444
4 changes: 4 additions & 0 deletions server/src/main/java/com/mirth/connect/server/Mirth.java
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@
import com.mirth.connect.server.controllers.MigrationController;
import com.mirth.connect.server.controllers.ScriptController;
import com.mirth.connect.server.controllers.UsageController;
import com.mirth.connect.server.extprops.DropInProperties;
import com.mirth.connect.server.controllers.UserController;
import com.mirth.connect.server.logging.JuliToLog4JService;
import com.mirth.connect.server.logging.LogOutputStream;
Expand Down Expand Up @@ -187,7 +188,10 @@ public boolean initResources() {
try {
mirthPropertiesStream = ResourceUtil.getResourceStream(this.getClass(), "mirth.properties");
mirthProperties = PropertiesConfigurationUtil.create(mirthPropertiesStream);
mirthProperties = DropInProperties.overlay(mirthProperties, ResourceUtil.getMirthPropertiesDropInDirectory());
} catch (Exception e) {
// Reset so a failed drop-in overlay aborts startup instead of booting with a partial configuration
mirthProperties = PropertiesConfigurationUtil.create();
logger.error("could not load mirth.properties", e);
} finally {
IOUtils.closeQuietly(mirthPropertiesStream);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@
import com.mirth.connect.plugins.directoryresource.DirectoryResourceProperties;
import com.mirth.connect.server.ExtensionLoader;
import com.mirth.connect.server.mybatis.KeyValuePair;
import com.mirth.connect.server.extprops.DropInProperties;
import com.mirth.connect.server.tools.ClassPathResource;
import com.mirth.connect.server.util.DatabaseUtil;
import com.mirth.connect.server.util.PasswordRequirementsChecker;
Expand Down Expand Up @@ -166,6 +167,9 @@ public class DefaultConfigurationController extends ConfigurationController {
private static PropertiesConfiguration versionConfig = PropertiesConfigurationUtil.create();
private static FileBasedConfigurationBuilder<PropertiesConfiguration> mirthConfigBuilder = PropertiesConfigurationUtil.createBuilder();
protected static PropertiesConfiguration mirthConfig = PropertiesConfigurationUtil.create();
// The file-backed configuration that saveMirthConfig() writes. Kept separate from mirthConfig
// so that values from mirth.properties.d drop-in files are never baked into mirth.properties.
protected static PropertiesConfiguration mirthFileConfig = mirthConfig;
private static EncryptionSettings encryptionConfig;
private static DatabaseSettings databaseConfig;
private static String apiBypassword;
Expand Down Expand Up @@ -229,15 +233,19 @@ public void initialize() {
try {
// Delimiter parsing disabled by default so getString() returns the whole property, even if there are commas
mirthConfigBuilder = PropertiesConfigurationUtil.createBuilder(new File(ClassPathResource.getResourceURI("mirth.properties")));
mirthConfig = mirthConfigBuilder.getConfiguration();
mirthFileConfig = mirthConfigBuilder.getConfiguration();
// Also assign the read view now so anything reading during migration sees real values
mirthConfig = mirthFileConfig;

MigrationController.getInstance().migrateConfiguration(mirthConfig);
MigrationController.getInstance().migrateConfiguration(mirthFileConfig);
try {
mirthConfigBuilder.save();
} catch (ConfigurationException e) {
logger.error("Unable to update mirth.properties version during migration.", e);
}

mirthConfig = DropInProperties.overlay(mirthFileConfig, ResourceUtil.getMirthPropertiesDropInDirectory());

// load the server version
versionPropertiesStream = ResourceUtil.getResourceStream(this.getClass(), "version.properties");
versionConfig = PropertiesConfigurationUtil.create(versionPropertiesStream);
Expand Down Expand Up @@ -1245,11 +1253,11 @@ public void initializeSecuritySettings() {
*/
if (Arrays.equals(keyStorePassword, DEFAULT_STOREPASS.toCharArray()) && Arrays.equals(keyPassword, DEFAULT_STOREPASS.toCharArray())) {
String keyStorePasswordStr = generateNewPassword();
mirthConfig.setProperty("keystore.storepass", keyStorePasswordStr);
setMirthConfigProperty("keystore.storepass", keyStorePasswordStr);
keyStorePassword = keyStorePasswordStr.toCharArray();

String keyPasswordStr = generateNewPassword();
mirthConfig.setProperty("keystore.keypass", keyPasswordStr);
setMirthConfigProperty("keystore.keypass", keyPasswordStr);
keyPassword = keyPasswordStr.toCharArray();

saveMirthConfig();
Expand Down Expand Up @@ -1329,18 +1337,26 @@ private void updateDatabasePassword(Encryptor encryptor, String password, boolea

if (!StringUtils.startsWith(encryptedPassword, "{")) {
// Re-encrypt if still using the old-style format
mirthConfig.setProperty(readOnly ? DatabaseConstants.DATABASE_READONLY_PASSWORD : DatabaseConstants.DATABASE_PASSWORD, EncryptionSettings.ENCRYPTION_PREFIX + encryptor.encrypt(decryptedPassword));
setMirthConfigProperty(readOnly ? DatabaseConstants.DATABASE_READONLY_PASSWORD : DatabaseConstants.DATABASE_PASSWORD, EncryptionSettings.ENCRYPTION_PREFIX + encryptor.encrypt(decryptedPassword));
saveMirthConfig();
}
} else if (StringUtils.isNotBlank(password)) {
// encrypt the password and write it back to the file
String encryptedPassword = EncryptionSettings.ENCRYPTION_PREFIX + encryptor.encrypt(password);
mirthConfig.setProperty(readOnly ? DatabaseConstants.DATABASE_READONLY_PASSWORD : DatabaseConstants.DATABASE_PASSWORD, encryptedPassword);
setMirthConfigProperty(readOnly ? DatabaseConstants.DATABASE_READONLY_PASSWORD : DatabaseConstants.DATABASE_PASSWORD, encryptedPassword);

saveMirthConfig();
}
}

private void setMirthConfigProperty(String key, Object value) {
mirthConfig.setProperty(key, value);

if (mirthFileConfig != mirthConfig) {
mirthFileConfig.setProperty(key, value);
}
}

private void saveMirthConfig() throws FileNotFoundException, ConfigurationException {
/*
* Save using a FileOutputStream so that the file will be saved to the proper location, even
Expand All @@ -1350,7 +1366,7 @@ private void saveMirthConfig() throws FileNotFoundException, ConfigurationExcept
OutputStream os = new FileOutputStream(new File(confDir, "mirth.properties"));

try {
PropertiesConfigurationUtil.saveTo(mirthConfig, os);
PropertiesConfigurationUtil.saveTo(mirthFileConfig, os);
} finally {
ResourceUtil.closeResourceQuietly(os);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
/*
* 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.extprops;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
import java.util.Properties;

import org.apache.commons.configuration2.ConfigurationUtils;
import org.apache.commons.configuration2.PropertiesConfiguration;
import org.apache.commons.configuration2.ex.ConfigurationException;

/**
* Applies drop-in override files from a mirth.properties.d directory on top of an already-loaded
* configuration. Files directly under the directory whose names end in ".properties" are applied
* in lexical filename order: later files override earlier ones, and all of them override the base
* configuration. Merge semantics come from java.util.Properties.load(), where the last occurrence
* of a key wins.
*
* This class lives in the extprops package because it is shared by the launcher jar and the server
* jar, and must not reference classes outside this package and the launcher's classpath.
*/
public class DropInProperties {

/**
* Loads drop-in files into the given properties, overriding any existing values. Files that
* cannot be read are logged and skipped.
*/
public static void load(Properties properties, File dropInDir, LoggerWrapper logger) {
File[] files = listDropInFiles(dropInDir);

if (files == null) {
if (isUnlistableDirectory(dropInDir)) {
logger.error("Unable to list drop-in properties directory: " + dropInDir.getPath());
}
return;
}

for (File file : files) {
try (InputStream is = new FileInputStream(file)) {
properties.load(is);
} catch (IOException e) {
logger.error("Unable to read drop-in properties file: " + file.getPath(), e);
}
}
}

/**
* Returns a configuration with any drop-in files applied on top of the given base
* configuration. The base configuration is never modified; it is returned as-is when there are
* no drop-in files.
*/
public static PropertiesConfiguration overlay(PropertiesConfiguration base, File dropInDir) throws ConfigurationException {
File[] files = listDropInFiles(dropInDir);

if (files == null) {
if (isUnlistableDirectory(dropInDir)) {
throw new ConfigurationException("Unable to list drop-in properties directory: " + dropInDir.getPath());
}
return base;
}

if (files.length == 0) {
return base;
}

Properties dropIns = new Properties();

for (File file : files) {
try (InputStream is = new FileInputStream(file)) {
dropIns.load(is);
} catch (IOException e) {
throw new ConfigurationException("Unable to read drop-in properties file: " + file.getPath(), e);
}
}

PropertiesConfiguration merged = new PropertiesConfiguration();
ConfigurationUtils.copy(base, merged);

for (String key : dropIns.stringPropertyNames()) {
merged.setProperty(key, dropIns.getProperty(key));
}

return merged;
}

private static File[] listDropInFiles(File dropInDir) {
File[] files = dropInDir == null ? null : dropInDir.listFiles(file -> file.isFile() && file.getName().endsWith(".properties"));

if (files != null) {
Arrays.sort(files);
}

return files;
}

/** listFiles() returns null both for a missing directory and an unlistable one; only the latter deserves noise. */
private static boolean isUnlistableDirectory(File dropInDir) {
return dropInDir != null && dropInDir.isDirectory();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ private ExtensionStatuses() {
} finally {
IOUtils.closeQuietly(is);
}
DropInProperties.load(mirthProperties, new File("./conf/mirth.properties.d"), logger);
} catch (Exception e) {
logger.error("Unable to read mirth.properties.", e);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;

import com.mirth.connect.server.extprops.DropInProperties;
import com.mirth.connect.server.extprops.ExtensionStatuses;
import com.mirth.connect.server.extprops.LoggerWrapper;

Expand Down Expand Up @@ -77,6 +78,7 @@ public static void main(String[] args) {

try (FileInputStream inputStream = new FileInputStream(new File(MIRTH_PROPERTIES_FILE))) {
mirthProperties.load(inputStream);
DropInProperties.load(mirthProperties, new File(MIRTH_PROPERTIES_FILE + ".d"), logger);
includeCustomLib = mirthProperties.getProperty(PROPERTY_INCLUDE_CUSTOM_LIB);
createAppdataDir(mirthProperties);
} catch (Exception e) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
import com.mirth.connect.server.controllers.ConfigurationController;
import com.mirth.connect.server.controllers.ControllerFactory;
import com.mirth.connect.server.controllers.ExtensionController;
import com.mirth.connect.server.extprops.DropInProperties;
import com.mirth.connect.server.tools.ClassPathResource;
import com.mirth.connect.server.util.ResourceUtil;
import com.mirth.connect.util.MirthSSLUtil;
Expand Down Expand Up @@ -363,8 +364,9 @@ protected PropertiesConfiguration getMirthProperties() throws FileNotFoundExcept

InputStream mirthPropsIs = null;
try {
mirthPropsIs = ResourceUtil.getResourceStream(getClass(), "mirth.properties");
mirthPropsIs = ResourceUtil.getResourceStream(getClass(), "mirth.properties");
mirthProperties = PropertiesConfigurationUtil.create(mirthPropsIs);
mirthProperties = DropInProperties.overlay(mirthProperties, ResourceUtil.getMirthPropertiesDropInDirectory());
} finally {
ResourceUtil.closeResourceQuietly(mirthPropsIs);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,31 @@
package com.mirth.connect.server.util;

import java.io.Closeable;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;

import com.mirth.connect.server.tools.ClassPathResource;

public class ResourceUtil {

/**
* Returns the mirth.properties.d drop-in directory next to the mirth.properties resolved from
* the classpath, or null when mirth.properties does not resolve to a file on disk.
*/
public static File getMirthPropertiesDropInDirectory() {
URI uri = ClassPathResource.getResourceURI("mirth.properties");

if (uri != null && "file".equals(uri.getScheme())) {
return new File(new File(uri).getParentFile(), "mirth.properties.d");
}

return null;
}

/**
* Returns a resource as a stream by checking:
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@
import com.mirth.connect.donkey.model.message.Message;
import com.mirth.connect.donkey.model.message.RawMessage;
import com.mirth.connect.server.controllers.ConfigurationController;
import com.mirth.connect.server.extprops.DropInProperties;
import com.mirth.connect.server.extprops.LoggerWrapper;
import com.mirth.connect.server.transformers.InvalidTransformedDataException;
import com.mirth.connect.server.userutil.AlertSender;
import com.mirth.connect.server.userutil.Attachment;
Expand All @@ -44,6 +46,7 @@
import com.mirth.connect.server.userutil.VMRouter;
import com.mirth.connect.server.util.GlobalChannelVariableStoreFactory;
import com.mirth.connect.server.util.GlobalVariableStore;
import com.mirth.connect.server.util.ResourceUtil;
import com.mirth.connect.server.util.TemplateValueReplacer;
import com.mirth.connect.userutil.ImmutableConnectorMessage;
import com.mirth.connect.userutil.ImmutableMessage;
Expand All @@ -62,6 +65,7 @@ public class JavaScriptScopeUtil {
* it in interpretive mode. See MIRTH-1627 for more information.
*/
Properties properties = PropertyLoader.loadProperties("mirth");
DropInProperties.load(properties, ResourceUtil.getMirthPropertiesDropInDirectory(), new LoggerWrapper(logger));

if (MapUtils.isNotEmpty(properties) && properties.containsKey("rhino.optimizationlevel")) {
logger.debug("set Rhino context optimization level: " + rhinoOptimizationLevel);
Expand Down
Loading
Loading