ARTEMIS-3757 - allow system and env var substution of properties config, respect order of file loaded properties and add generic enum converter

This commit is contained in:
Gary Tully 2022-04-04 17:17:38 +01:00 committed by Bruscino Domenico Francesco
parent 4ff5d95b2c
commit a739b9f068
8 changed files with 218 additions and 70 deletions

View File

@ -17,23 +17,6 @@
package org.apache.activemq.artemis.utils.critical;
import org.apache.activemq.artemis.utils.uri.BeanSupport;
import org.apache.commons.beanutils.Converter;
public enum CriticalAnalyzerPolicy {
HALT, SHUTDOWN, LOG;
static {
// for URI support on ClusterConnection
BeanSupport.registerConverter(new CriticalAnalyzerPolicyConverter(), CriticalAnalyzerPolicy.class);
}
static class CriticalAnalyzerPolicyConverter implements Converter {
@Override
public <T> T convert(Class<T> type, Object value) {
return type.cast(CriticalAnalyzerPolicy.valueOf(value.toString()));
}
}
}

View File

@ -30,6 +30,7 @@ import java.util.Properties;
import java.util.Set;
import org.apache.commons.beanutils.BeanUtilsBean;
import org.apache.commons.beanutils.ConversionException;
import org.apache.commons.beanutils.Converter;
public class BeanSupport {
@ -40,6 +41,25 @@ public class BeanSupport {
static {
// This is to customize the BeanUtils to use Fluent Properties as well
beanUtils.getPropertyUtils().addBeanIntrospector(new FluentPropertyBeanIntrospectorWithIgnores());
// generic converter from String to enum type via valueOf with no default
registerConverter(new Converter() {
@Override
public <T> T convert(Class<T> type, Object value) {
if (value == null) {
return null;
}
if (String.class.equals(type) || Object.class.equals(type)) {
return type.cast(value.toString());
}
if (type.isEnum()) {
return (T) Enum.valueOf((Class<Enum>)type, value.toString());
}
throw new ConversionException("Can't convert value '" + value
+ "' to type " + type);
}
}, String.class);
}
public static void registerConverter(Converter converter, Class type) {

View File

@ -52,6 +52,12 @@ public final class XMLUtil {
// Utility class
}
public static String CONSIDER_OS_ENV_PROP = "org.apache.activemq.artemis.utils.considerOsEnv";
private static final boolean considerOsEnv;
static {
considerOsEnv = Boolean.parseBoolean(System.getProperty(CONSIDER_OS_ENV_PROP, "true"));
}
public static Element streamToElement(InputStream inputStream) throws Exception {
try (Reader reader = new InputStreamReader(inputStream)) {
return XMLUtil.readerToElement(reader);
@ -271,7 +277,14 @@ public final class XMLUtil {
val = parts[1].trim();
}
String sysProp = System.getProperty(prop, val);
String sysProp = System.getProperty(prop);
if (sysProp == null && considerOsEnv) {
sysProp = System.getenv(prop);
}
if (sysProp == null) {
sysProp = val;
}
// interesting choice to replace with val == "" with no match!
logger.debug("replacing " + subString + " with " + sysProp);
xml = xml.replace(subString, sysProp);
}

View File

@ -99,6 +99,7 @@ import org.apache.activemq.artemis.core.settings.impl.ResourceLimitSettings;
import org.apache.activemq.artemis.utils.ByteUtil;
import org.apache.activemq.artemis.utils.Env;
import org.apache.activemq.artemis.utils.ObjectInputStreamWithClassLoader;
import org.apache.activemq.artemis.utils.XMLUtil;
import org.apache.activemq.artemis.utils.critical.CriticalAnalyzerPolicy;
import org.apache.activemq.artemis.utils.uri.BeanSupport;
import org.apache.commons.beanutils.BeanUtilsBean;
@ -464,13 +465,27 @@ public class ConfigurationImpl implements Configuration, Serializable {
@Override
public Configuration parseProperties(String fileUrlToProperties) throws Exception {
// system property overrides
// system property overrides location of file(s)
fileUrlToProperties = System.getProperty(ActiveMQDefaultConfiguration.BROKER_PROPERTIES_SYSTEM_PROPERTY_NAME, fileUrlToProperties);
if (fileUrlToProperties != null) {
Properties brokerProperties = new Properties();
try (FileInputStream fileInputStream = new FileInputStream(fileUrlToProperties); BufferedInputStream reader = new BufferedInputStream(fileInputStream)) {
brokerProperties.load(reader);
parsePrefixedProperties(brokerProperties, null);
for (String fileUrl : fileUrlToProperties.split(",")) {
Properties brokerProperties = new Properties() {
final LinkedHashMap<Object, Object> orderedMap = new LinkedHashMap<>();
@Override
public Object put(Object key, Object value) {
return orderedMap.put(key.toString(), value.toString());
}
@Override
public Set<Map.Entry<Object, Object>> entrySet() {
return orderedMap.entrySet();
}
};
try (FileInputStream fileInputStream = new FileInputStream(fileUrl); BufferedInputStream reader = new BufferedInputStream(fileInputStream)) {
brokerProperties.load(reader);
parsePrefixedProperties(brokerProperties, null);
}
}
}
parsePrefixedProperties(System.getProperties(), systemPropertyPrefix);
@ -478,7 +493,7 @@ public class ConfigurationImpl implements Configuration, Serializable {
}
public void parsePrefixedProperties(Properties properties, String prefix) throws Exception {
Map<String, Object> beanProperties = new HashMap<>();
Map<String, Object> beanProperties = new LinkedHashMap<>();
synchronized (properties) {
String key = null;
@ -490,8 +505,10 @@ public class ConfigurationImpl implements Configuration, Serializable {
}
key = entry.getKey().toString().substring(prefix.length());
}
logger.debug("Setting up config, " + key + "=" + entry.getValue());
beanProperties.put(key, entry.getValue());
String value = XMLUtil.replaceSystemPropsInString(entry.getValue().toString());
key = XMLUtil.replaceSystemPropsInString(key);
logger.debug("Property config, " + key + "=" + value);
beanProperties.put(key, value);
}
}

View File

@ -184,7 +184,7 @@ public class ClusterConnectionControlImpl extends AbstractControl implements Clu
}
clearIO();
try {
return configuration.getMessageLoadBalancingType().getType();
return configuration.getMessageLoadBalancingType().toString();
} finally {
blockOnIO();
}

View File

@ -16,46 +16,6 @@
*/
package org.apache.activemq.artemis.core.server.cluster.impl;
import org.apache.activemq.artemis.utils.uri.BeanSupport;
import org.apache.commons.beanutils.Converter;
public enum MessageLoadBalancingType {
OFF("OFF"), STRICT("STRICT"), ON_DEMAND("ON_DEMAND"), OFF_WITH_REDISTRIBUTION("OFF_WITH_REDISTRIBUTION");
static {
// for URI support on ClusterConnection
BeanSupport.registerConverter(new MessageLoadBalancingTypeConverter(), MessageLoadBalancingType.class);
}
static class MessageLoadBalancingTypeConverter implements Converter {
@Override
public <T> T convert(Class<T> type, Object value) {
return type.cast(MessageLoadBalancingType.getType(value.toString()));
}
}
private String type;
MessageLoadBalancingType(final String type) {
this.type = type;
}
public String getType() {
return type;
}
public static MessageLoadBalancingType getType(String string) {
if (string.equals(OFF.getType())) {
return MessageLoadBalancingType.OFF;
} else if (string.equals(STRICT.getType())) {
return MessageLoadBalancingType.STRICT;
} else if (string.equals(ON_DEMAND.getType())) {
return MessageLoadBalancingType.ON_DEMAND;
} else if (string.equals(OFF_WITH_REDISTRIBUTION.getType())) {
return MessageLoadBalancingType.OFF_WITH_REDISTRIBUTION;
} else {
return null;
}
}
OFF, STRICT, ON_DEMAND, OFF_WITH_REDISTRIBUTION;
}

View File

@ -17,9 +17,15 @@
package org.apache.activemq.artemis.core.config.impl;
import java.io.File;
import java.io.FileOutputStream;
import java.io.PrintWriter;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Properties;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;
import org.apache.activemq.artemis.ArtemisConstants;
import org.apache.activemq.artemis.api.config.ActiveMQDefaultConfiguration;
@ -31,9 +37,13 @@ import org.apache.activemq.artemis.core.config.amqpBrokerConnectivity.AMQPBroker
import org.apache.activemq.artemis.core.config.amqpBrokerConnectivity.AMQPMirrorBrokerConnectionElement;
import org.apache.activemq.artemis.core.config.ha.LiveOnlyPolicyConfiguration;
import org.apache.activemq.artemis.core.server.JournalType;
import org.apache.activemq.artemis.core.server.cluster.impl.MessageLoadBalancingType;
import org.apache.activemq.artemis.core.server.plugin.impl.LoggingActiveMQServerPlugin;
import org.apache.activemq.artemis.core.server.routing.KeyType;
import org.apache.activemq.artemis.core.settings.impl.ResourceLimitSettings;
import org.apache.activemq.artemis.tests.util.ActiveMQTestBase;
import org.apache.activemq.artemis.utils.RandomUtil;
import org.apache.activemq.artemis.utils.critical.CriticalAnalyzerPolicy;
import org.jboss.logging.Logger;
import org.junit.Assert;
import org.junit.Before;
@ -601,6 +611,7 @@ public class ConfigurationImplTest extends ActiveMQTestBase {
Properties properties = new Properties();
properties.put("connectionRouters.joe.localTargetFilter", "LF");
properties.put("connectionRouters.joe.keyFilter", "TF");
properties.put("connectionRouters.joe.keyType", "SOURCE_IP");
properties.put("acceptorConfigurations.tcp.params.HOST", "LOCALHOST");
properties.put("acceptorConfigurations.tcp.params.PORT", "61616");
@ -626,6 +637,7 @@ public class ConfigurationImplTest extends ActiveMQTestBase {
Assert.assertEquals(1, configuration.getConnectionRouters().size());
Assert.assertEquals("LF", configuration.getConnectionRouters().get(0).getLocalTargetFilter());
Assert.assertEquals("TF", configuration.getConnectionRouters().get(0).getKeyFilter());
Assert.assertEquals(KeyType.SOURCE_IP, configuration.getConnectionRouters().get(0).getKeyType());
Assert.assertEquals(2, configuration.getAcceptorConfigurations().size());
@ -708,6 +720,149 @@ public class ConfigurationImplTest extends ActiveMQTestBase {
Assert.assertEquals(25 * 1024, configuration.getGlobalMaxSize());
}
@Test
public void testSystemPropValueReplaced() throws Exception {
ConfigurationImpl configuration = new ConfigurationImpl();
Properties properties = new Properties();
final String homeFromDefault = "default-home";
final String homeFromEnv = System.getenv("HOME");
properties.put("name", "${HOME:" + homeFromDefault + "}");
configuration.parsePrefixedProperties(properties, null);
if (homeFromEnv != null) {
Assert.assertEquals(homeFromEnv, configuration.getName());
} else {
// if $HOME is not set for some platform
Assert.assertEquals(homeFromDefault, configuration.getName());
}
}
@Test
public void testSystemPropValueNoMatch() throws Exception {
ConfigurationImpl configuration = new ConfigurationImpl();
Properties properties = new Properties();
properties.put("name", "vv-${SOME_RANDOM_VV}");
configuration.parsePrefixedProperties(properties, null);
Assert.assertEquals("vv-", configuration.getName());
}
@Test
public void testSystemPropValueNonExistWithDefault() throws Exception {
ConfigurationImpl configuration = new ConfigurationImpl();
Properties properties = new Properties();
properties.put("name", "vv-${SOME_RANDOM_VV:y}");
configuration.parsePrefixedProperties(properties, null);
Assert.assertEquals("vv-y", configuration.getName());
}
@Test
public void testSystemPropKeyReplacement() throws Exception {
ConfigurationImpl configuration = new ConfigurationImpl();
Properties properties = new Properties();
final String newKeyName = RandomUtil.randomString();
final String valueFromSysProp = "VV";
System.setProperty(newKeyName, valueFromSysProp);
try {
properties.put("connectorConfigurations.KEY-${" + newKeyName + "}.name", "y");
configuration.parsePrefixedProperties(properties, null);
Assert.assertNotNull("configured new key from prop", configuration.connectorConfigs.get("KEY-" + valueFromSysProp));
Assert.assertEquals("y", configuration.connectorConfigs.get("KEY-" + valueFromSysProp).getName());
} finally {
System.clearProperty(newKeyName);
}
}
@Test
public void testEnumConversion() throws Exception {
ConfigurationImpl configuration = new ConfigurationImpl();
Properties properties = new Properties();
properties.put("clusterConfiguration.cc.name", "cc");
properties.put("clusterConfigurations.cc.messageLoadBalancingType", "OFF_WITH_REDISTRIBUTION");
properties.put("criticalAnalyzerPolicy", "SHUTDOWN");
configuration.parsePrefixedProperties(properties, null);
Assert.assertEquals("cc", configuration.getClusterConfigurations().get(0).getName());
Assert.assertEquals(MessageLoadBalancingType.OFF_WITH_REDISTRIBUTION, configuration.getClusterConfigurations().get(0).getMessageLoadBalancingType());
Assert.assertEquals(CriticalAnalyzerPolicy.SHUTDOWN, configuration.getCriticalAnalyzerPolicy());
}
@Test
public void testPropertiesReaderRespectsOrderFromFile() throws Exception {
File tmpFile = File.createTempFile("ordered-props-test", "");
FileOutputStream fileOutputStream = new FileOutputStream(tmpFile);
PrintWriter printWriter = new PrintWriter(fileOutputStream);
LinkedList<String> insertionOrderedKeys = new LinkedList<>();
char ascii = 'a';
for (int i = 0; i < 26; i++, ascii++) {
printWriter.println("resourceLimitSettings." + i + ".maxConnections=100");
insertionOrderedKeys.addLast(String.valueOf(i));
printWriter.println("resourceLimitSettings." + ascii + ".maxConnections=100");
insertionOrderedKeys.addLast(String.valueOf(ascii));
}
printWriter.flush();
fileOutputStream.flush();
fileOutputStream.close();
final AtomicReference<String> errorAt = new AtomicReference<>();
ConfigurationImpl configuration = new ConfigurationImpl();
configuration.setResourceLimitSettings(new HashMap<String, ResourceLimitSettings>() {
@Override
public ResourceLimitSettings put(String key, ResourceLimitSettings value) {
if (!(key.equals(insertionOrderedKeys.remove()))) {
errorAt.set(key);
fail("Expected to see props applied in insertion order!, errorAt:" + errorAt.get());
}
return super.put(key, value);
}
});
configuration.parseProperties(tmpFile.getAbsolutePath());
assertNull("no errors in insertion order, errorAt:" + errorAt.get(), errorAt.get());
}
@Test
public void testPropertiesFiles() throws Exception {
LinkedList<String> files = new LinkedList<>();
LinkedList<String> names = new LinkedList<>();
names.addLast("one");
names.addLast("two");
for (String suffix : names) {
File tmpFile = File.createTempFile("props-test", suffix);
FileOutputStream fileOutputStream = new FileOutputStream(tmpFile);
PrintWriter printWriter = new PrintWriter(fileOutputStream);
printWriter.println("name=" + suffix);
printWriter.flush();
fileOutputStream.flush();
fileOutputStream.close();
files.addLast(tmpFile.getAbsolutePath());
}
final AtomicReference<String> errorAt = new AtomicReference<>();
ConfigurationImpl configuration = new ConfigurationImpl() {
@Override
public ConfigurationImpl setName(String name) {
if (!(name.equals(names.remove()))) {
fail("Expected names from files in order");
}
return super.setName(name);
}
};
configuration.parseProperties(files.stream().collect(Collectors.joining(",")));
assertEquals("second won", "two", configuration.getName());
}
@Test
public void testNameWithDotsSurroundWithDollarDollar() throws Throwable {
ConfigurationImpl configuration = new ConfigurationImpl();

View File

@ -75,7 +75,7 @@ public class ClusterConnectionControlTest extends ManagementTestBase {
Assert.assertEquals(clusterConnectionConfig1.getDiscoveryGroupName(), clusterConnectionControl.getDiscoveryGroupName());
Assert.assertEquals(clusterConnectionConfig1.getRetryInterval(), clusterConnectionControl.getRetryInterval());
Assert.assertEquals(clusterConnectionConfig1.isDuplicateDetection(), clusterConnectionControl.isDuplicateDetection());
Assert.assertEquals(clusterConnectionConfig1.getMessageLoadBalancingType().getType(), clusterConnectionControl.getMessageLoadBalancingType());
Assert.assertEquals(clusterConnectionConfig1.getMessageLoadBalancingType().toString(), clusterConnectionControl.getMessageLoadBalancingType());
Assert.assertEquals(clusterConnectionConfig1.getMaxHops(), clusterConnectionControl.getMaxHops());
Assert.assertEquals(0L, clusterConnectionControl.getMessagesPendingAcknowledgement());
Assert.assertEquals(0L, clusterConnectionControl.getMessagesAcknowledged());
@ -111,7 +111,7 @@ public class ClusterConnectionControlTest extends ManagementTestBase {
Assert.assertEquals(clusterConnectionConfig2.getDiscoveryGroupName(), clusterConnectionControl.getDiscoveryGroupName());
Assert.assertEquals(clusterConnectionConfig2.getRetryInterval(), clusterConnectionControl.getRetryInterval());
Assert.assertEquals(clusterConnectionConfig2.isDuplicateDetection(), clusterConnectionControl.isDuplicateDetection());
Assert.assertEquals(clusterConnectionConfig2.getMessageLoadBalancingType().getType(), clusterConnectionControl.getMessageLoadBalancingType());
Assert.assertEquals(clusterConnectionConfig2.getMessageLoadBalancingType().toString(), clusterConnectionControl.getMessageLoadBalancingType());
Assert.assertEquals(clusterConnectionConfig2.getMaxHops(), clusterConnectionControl.getMaxHops());
Object[] connectorPairs = clusterConnectionControl.getStaticConnectors();