diff --git a/apollo-client/src/main/java/com/ctrip/framework/apollo/internals/AbstractConfig.java b/apollo-client/src/main/java/com/ctrip/framework/apollo/internals/AbstractConfig.java index 710f773c763..1f1de7e6625 100644 --- a/apollo-client/src/main/java/com/ctrip/framework/apollo/internals/AbstractConfig.java +++ b/apollo-client/src/main/java/com/ctrip/framework/apollo/internals/AbstractConfig.java @@ -11,6 +11,7 @@ import com.ctrip.framework.apollo.tracer.Tracer; import com.ctrip.framework.apollo.tracer.spi.Transaction; import com.ctrip.framework.apollo.util.ConfigUtil; +import com.ctrip.framework.apollo.util.factory.PropertiesFactory; import com.ctrip.framework.apollo.util.function.Functions; import com.ctrip.framework.apollo.util.parser.Parsers; import com.google.common.base.Function; @@ -58,6 +59,8 @@ public abstract class AbstractConfig implements Config { private final List allCaches; private final AtomicLong m_configVersion; //indicate config version + protected PropertiesFactory propertiesFactory; + static { m_executorService = Executors.newCachedThreadPool(ApolloThreadFactory .create("Config", true)); @@ -68,6 +71,7 @@ public AbstractConfig() { m_configVersion = new AtomicLong(); m_arrayCache = Maps.newConcurrentMap(); allCaches = Lists.newArrayList(); + propertiesFactory = ApolloInjector.getInstance(PropertiesFactory.class); } @Override @@ -493,11 +497,11 @@ private boolean isConfigChangeListenerInterested(ConfigChangeListener configChan List calcPropertyChanges(String namespace, Properties previous, Properties current) { if (previous == null) { - previous = new Properties(); + previous = propertiesFactory.getPropertiesInstance(); } if (current == null) { - current = new Properties(); + current = propertiesFactory.getPropertiesInstance(); } Set previousKeys = previous.stringPropertyNames(); diff --git a/apollo-client/src/main/java/com/ctrip/framework/apollo/internals/AbstractConfigFile.java b/apollo-client/src/main/java/com/ctrip/framework/apollo/internals/AbstractConfigFile.java index a646d28ee8f..766d8b9675f 100644 --- a/apollo-client/src/main/java/com/ctrip/framework/apollo/internals/AbstractConfigFile.java +++ b/apollo-client/src/main/java/com/ctrip/framework/apollo/internals/AbstractConfigFile.java @@ -1,6 +1,8 @@ package com.ctrip.framework.apollo.internals; +import com.ctrip.framework.apollo.build.ApolloInjector; import com.ctrip.framework.apollo.enums.ConfigSourceType; +import com.ctrip.framework.apollo.util.factory.PropertiesFactory; import java.util.List; import java.util.Properties; import java.util.concurrent.ExecutorService; @@ -30,6 +32,7 @@ public abstract class AbstractConfigFile implements ConfigFile, RepositoryChange protected final String m_namespace; protected final AtomicReference m_configProperties; private final List m_listeners = Lists.newCopyOnWriteArrayList(); + protected final PropertiesFactory propertiesFactory; private volatile ConfigSourceType m_sourceType = ConfigSourceType.NONE; @@ -42,6 +45,7 @@ public AbstractConfigFile(String namespace, ConfigRepository configRepository) { m_configRepository = configRepository; m_namespace = namespace; m_configProperties = new AtomicReference<>(); + propertiesFactory = ApolloInjector.getInstance(PropertiesFactory.class); initialize(); } @@ -72,7 +76,7 @@ public synchronized void onRepositoryChange(String namespace, Properties newProp if (newProperties.equals(m_configProperties.get())) { return; } - Properties newConfigProperties = new Properties(); + Properties newConfigProperties = propertiesFactory.getPropertiesInstance(); newConfigProperties.putAll(newProperties); String oldValue = getContent(); diff --git a/apollo-client/src/main/java/com/ctrip/framework/apollo/internals/AbstractConfigRepository.java b/apollo-client/src/main/java/com/ctrip/framework/apollo/internals/AbstractConfigRepository.java index 7d378febb46..3e055b1c4f3 100644 --- a/apollo-client/src/main/java/com/ctrip/framework/apollo/internals/AbstractConfigRepository.java +++ b/apollo-client/src/main/java/com/ctrip/framework/apollo/internals/AbstractConfigRepository.java @@ -1,5 +1,7 @@ package com.ctrip.framework.apollo.internals; +import com.ctrip.framework.apollo.build.ApolloInjector; +import com.ctrip.framework.apollo.util.factory.PropertiesFactory; import java.util.List; import java.util.Properties; @@ -16,6 +18,7 @@ public abstract class AbstractConfigRepository implements ConfigRepository { private static final Logger logger = LoggerFactory.getLogger(AbstractConfigRepository.class); private List m_listeners = Lists.newCopyOnWriteArrayList(); + protected PropertiesFactory propertiesFactory = ApolloInjector.getInstance(PropertiesFactory.class); protected boolean trySync() { try { diff --git a/apollo-client/src/main/java/com/ctrip/framework/apollo/internals/DefaultConfig.java b/apollo-client/src/main/java/com/ctrip/framework/apollo/internals/DefaultConfig.java index 863963bfba6..e5af728a136 100644 --- a/apollo-client/src/main/java/com/ctrip/framework/apollo/internals/DefaultConfig.java +++ b/apollo-client/src/main/java/com/ctrip/framework/apollo/internals/DefaultConfig.java @@ -4,12 +4,12 @@ import java.io.IOException; import java.io.InputStream; import java.util.Collections; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Properties; import java.util.Set; -import java.util.HashMap; import java.util.concurrent.atomic.AtomicReference; import org.slf4j.Logger; @@ -88,7 +88,7 @@ public String getProperty(String key, String defaultValue) { // step 4: check properties file from classpath if (value == null && m_resourceProperties != null) { - value = (String) m_resourceProperties.get(key); + value = (String) m_resourceProperties.getProperty(key); } if (value == null && m_configProperties.get() == null && m_warnLogRateLimiter.tryAcquire()) { @@ -115,7 +115,7 @@ public ConfigSourceType getSourceType() { private Set stringPropertyNames(Properties properties) { //jdk9以下版本Properties#enumerateStringProperties方法存在性能问题,keys() + get(k) 重复迭代, jdk9之后改为entrySet遍历. - Map h = new HashMap<>(); + Map h = new LinkedHashMap<>(); for (Map.Entry e : properties.entrySet()) { Object k = e.getKey(); Object v = e.getValue(); @@ -133,7 +133,7 @@ public synchronized void onRepositoryChange(String namespace, Properties newProp } ConfigSourceType sourceType = m_configRepository.getSourceType(); - Properties newConfigProperties = new Properties(); + Properties newConfigProperties = propertiesFactory.getPropertiesInstance(); newConfigProperties.putAll(newProperties); Map actualChanges = updateAndCalcConfigChanges(newConfigProperties, sourceType); @@ -213,7 +213,7 @@ private Properties loadFromResource(String namespace) { Properties properties = null; if (in != null) { - properties = new Properties(); + properties = propertiesFactory.getPropertiesInstance(); try { properties.load(in); diff --git a/apollo-client/src/main/java/com/ctrip/framework/apollo/internals/DefaultInjector.java b/apollo-client/src/main/java/com/ctrip/framework/apollo/internals/DefaultInjector.java index e6922221ae1..1f20f21e95b 100644 --- a/apollo-client/src/main/java/com/ctrip/framework/apollo/internals/DefaultInjector.java +++ b/apollo-client/src/main/java/com/ctrip/framework/apollo/internals/DefaultInjector.java @@ -9,6 +9,8 @@ import com.ctrip.framework.apollo.spi.DefaultConfigRegistry; import com.ctrip.framework.apollo.tracer.Tracer; import com.ctrip.framework.apollo.util.ConfigUtil; +import com.ctrip.framework.apollo.util.factory.DefaultPropertiesFactory; +import com.ctrip.framework.apollo.util.factory.PropertiesFactory; import com.ctrip.framework.apollo.util.http.HttpUtil; import com.ctrip.framework.apollo.util.yaml.YamlParser; @@ -62,6 +64,7 @@ protected void configure() { bind(ConfigServiceLocator.class).in(Singleton.class); bind(RemoteConfigLongPollService.class).in(Singleton.class); bind(YamlParser.class).in(Singleton.class); + bind(PropertiesFactory.class).to(DefaultPropertiesFactory.class).in(Singleton.class); } } } diff --git a/apollo-client/src/main/java/com/ctrip/framework/apollo/internals/LocalFileConfigRepository.java b/apollo-client/src/main/java/com/ctrip/framework/apollo/internals/LocalFileConfigRepository.java index 7a1c8bca790..f991eb23c87 100644 --- a/apollo-client/src/main/java/com/ctrip/framework/apollo/internals/LocalFileConfigRepository.java +++ b/apollo-client/src/main/java/com/ctrip/framework/apollo/internals/LocalFileConfigRepository.java @@ -88,7 +88,7 @@ public Properties getConfig() { if (m_fileProperties == null) { sync(); } - Properties result = new Properties(); + Properties result = propertiesFactory.getPropertiesInstance(); result.putAll(m_fileProperties); return result; } @@ -117,7 +117,7 @@ public void onRepositoryChange(String namespace, Properties newProperties) { if (newProperties.equals(m_fileProperties)) { return; } - Properties newFileProperties = new Properties(); + Properties newFileProperties = propertiesFactory.getPropertiesInstance(); newFileProperties.putAll(newProperties); updateFileProperties(newFileProperties, m_upstream.getSourceType()); this.fireRepositoryChange(namespace, newProperties); @@ -191,8 +191,7 @@ private Properties loadFromLocalCacheFile(File baseDir, String namespace) throws try { in = new FileInputStream(file); - - properties = new Properties(); + properties = propertiesFactory.getPropertiesInstance(); properties.load(in); logger.debug("Loading local config file {} successfully!", file.getAbsolutePath()); } catch (IOException ex) { diff --git a/apollo-client/src/main/java/com/ctrip/framework/apollo/internals/RemoteConfigRepository.java b/apollo-client/src/main/java/com/ctrip/framework/apollo/internals/RemoteConfigRepository.java index 5d8a5ae6069..39b570515d3 100644 --- a/apollo-client/src/main/java/com/ctrip/framework/apollo/internals/RemoteConfigRepository.java +++ b/apollo-client/src/main/java/com/ctrip/framework/apollo/internals/RemoteConfigRepository.java @@ -158,7 +158,7 @@ protected synchronized void sync() { } private Properties transformApolloConfigToProperties(ApolloConfig apolloConfig) { - Properties result = new Properties(); + Properties result = propertiesFactory.getPropertiesInstance(); result.putAll(apolloConfig.getConfigurations()); return result; } diff --git a/apollo-client/src/main/java/com/ctrip/framework/apollo/internals/SimpleConfig.java b/apollo-client/src/main/java/com/ctrip/framework/apollo/internals/SimpleConfig.java index fbe118aa95a..c12150cabeb 100644 --- a/apollo-client/src/main/java/com/ctrip/framework/apollo/internals/SimpleConfig.java +++ b/apollo-client/src/main/java/com/ctrip/framework/apollo/internals/SimpleConfig.java @@ -81,7 +81,7 @@ public synchronized void onRepositoryChange(String namespace, Properties newProp if (newProperties.equals(m_configProperties)) { return; } - Properties newConfigProperties = new Properties(); + Properties newConfigProperties = propertiesFactory.getPropertiesInstance(); newConfigProperties.putAll(newProperties); List changes = calcPropertyChanges(namespace, m_configProperties, newConfigProperties); diff --git a/apollo-client/src/main/java/com/ctrip/framework/apollo/internals/YamlConfigFile.java b/apollo-client/src/main/java/com/ctrip/framework/apollo/internals/YamlConfigFile.java index 88793997635..e0a404a1a46 100644 --- a/apollo-client/src/main/java/com/ctrip/framework/apollo/internals/YamlConfigFile.java +++ b/apollo-client/src/main/java/com/ctrip/framework/apollo/internals/YamlConfigFile.java @@ -61,7 +61,7 @@ private synchronized void transformToProperties() { private Properties toProperties() { if (!this.hasContent()) { - return new Properties(); + return propertiesFactory.getPropertiesInstance(); } try { diff --git a/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/boot/ApolloApplicationContextInitializer.java b/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/boot/ApolloApplicationContextInitializer.java index bb9cf22d496..f7e71facb96 100644 --- a/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/boot/ApolloApplicationContextInitializer.java +++ b/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/boot/ApolloApplicationContextInitializer.java @@ -6,6 +6,7 @@ import com.ctrip.framework.apollo.spring.config.ConfigPropertySourceFactory; import com.ctrip.framework.apollo.spring.config.PropertySourcesConstants; import com.ctrip.framework.apollo.spring.util.SpringInjector; +import com.ctrip.framework.apollo.util.factory.PropertiesFactory; import com.google.common.base.Splitter; import com.google.common.base.Strings; import java.util.List; @@ -61,7 +62,7 @@ public class ApolloApplicationContextInitializer implements private static final Logger logger = LoggerFactory.getLogger(ApolloApplicationContextInitializer.class); private static final Splitter NAMESPACE_SPLITTER = Splitter.on(",").omitEmptyStrings().trimResults(); private static final String[] APOLLO_SYSTEM_PROPERTIES = {"app.id", ConfigConsts.APOLLO_CLUSTER_KEY, - "apollo.cacheDir", "apollo.accesskey.secret", ConfigConsts.APOLLO_META_KEY}; + "apollo.cacheDir", "apollo.accesskey.secret", ConfigConsts.APOLLO_META_KEY, PropertiesFactory.APOLLO_PROPERTY_ORDER_ENABLE}; private final ConfigPropertySourceFactory configPropertySourceFactory = SpringInjector .getInstance(ConfigPropertySourceFactory.class); diff --git a/apollo-client/src/main/java/com/ctrip/framework/apollo/util/ConfigUtil.java b/apollo-client/src/main/java/com/ctrip/framework/apollo/util/ConfigUtil.java index 4bf00c8e87e..6b38ee083dd 100644 --- a/apollo-client/src/main/java/com/ctrip/framework/apollo/util/ConfigUtil.java +++ b/apollo-client/src/main/java/com/ctrip/framework/apollo/util/ConfigUtil.java @@ -1,5 +1,7 @@ package com.ctrip.framework.apollo.util; +import static com.ctrip.framework.apollo.util.factory.PropertiesFactory.APOLLO_PROPERTY_ORDER_ENABLE; + import com.google.common.util.concurrent.RateLimiter; import java.io.File; import java.util.concurrent.TimeUnit; @@ -18,6 +20,7 @@ * @author Jason Song(song_s@ctrip.com) */ public class ConfigUtil { + private static final Logger logger = LoggerFactory.getLogger(ConfigUtil.class); private int refreshInterval = 5; private TimeUnit refreshIntervalTimeUnit = TimeUnit.MINUTES; @@ -36,6 +39,7 @@ public class ConfigUtil { private long longPollingInitialDelayInMills = 2000;//2 seconds private boolean autoUpdateInjectedSpringProperties = true; private final RateLimiter warnLogRateLimiter; + private boolean propertiesOrdered = false; public ConfigUtil() { warnLogRateLimiter = RateLimiter.create(0.017); // 1 warning log output per minute @@ -47,6 +51,7 @@ public ConfigUtil() { initMaxConfigCacheSize(); initLongPollingInitialDelayInMills(); initAutoUpdateInjectedSpringProperties(); + initPropertiesOrdered(); } /** @@ -281,12 +286,14 @@ public TimeUnit getConfigCacheExpireTimeUnit() { } private void initLongPollingInitialDelayInMills() { - String customizedLongPollingInitialDelay = System.getProperty("apollo.longPollingInitialDelayInMills"); + String customizedLongPollingInitialDelay = System + .getProperty("apollo.longPollingInitialDelayInMills"); if (!Strings.isNullOrEmpty(customizedLongPollingInitialDelay)) { try { longPollingInitialDelayInMills = Long.parseLong(customizedLongPollingInitialDelay); } catch (Throwable ex) { - logger.error("Config for apollo.longPollingInitialDelayInMills is invalid: {}", customizedLongPollingInitialDelay); + logger.error("Config for apollo.longPollingInitialDelayInMills is invalid: {}", + customizedLongPollingInitialDelay); } } } @@ -300,7 +307,8 @@ private void initAutoUpdateInjectedSpringProperties() { String enableAutoUpdate = System.getProperty("apollo.autoUpdateInjectedSpringProperties"); if (Strings.isNullOrEmpty(enableAutoUpdate)) { // 2. Get from app.properties - enableAutoUpdate = Foundation.app().getProperty("apollo.autoUpdateInjectedSpringProperties", null); + enableAutoUpdate = Foundation.app() + .getProperty("apollo.autoUpdateInjectedSpringProperties", null); } if (!Strings.isNullOrEmpty(enableAutoUpdate)) { autoUpdateInjectedSpringProperties = Boolean.parseBoolean(enableAutoUpdate.trim()); @@ -310,4 +318,25 @@ private void initAutoUpdateInjectedSpringProperties() { public boolean isAutoUpdateInjectedSpringPropertiesEnabled() { return autoUpdateInjectedSpringProperties; } + + private void initPropertiesOrdered() { + String enablePropertiesOrdered = System.getProperty(APOLLO_PROPERTY_ORDER_ENABLE); + + if (Strings.isNullOrEmpty(enablePropertiesOrdered)) { + enablePropertiesOrdered = Foundation.app().getProperty(APOLLO_PROPERTY_ORDER_ENABLE, "false"); + } + + if (!Strings.isNullOrEmpty(enablePropertiesOrdered)) { + try { + propertiesOrdered = Boolean.parseBoolean(enablePropertiesOrdered); + } catch (Throwable ex) { + logger.warn("Config for {} is invalid: {}, set default value: false", + APOLLO_PROPERTY_ORDER_ENABLE, enablePropertiesOrdered); + } + } + } + + public boolean isPropertiesOrderEnabled() { + return propertiesOrdered; + } } diff --git a/apollo-client/src/main/java/com/ctrip/framework/apollo/util/OrderedProperties.java b/apollo-client/src/main/java/com/ctrip/framework/apollo/util/OrderedProperties.java new file mode 100755 index 00000000000..c99cb95c7e6 --- /dev/null +++ b/apollo-client/src/main/java/com/ctrip/framework/apollo/util/OrderedProperties.java @@ -0,0 +1,114 @@ +package com.ctrip.framework.apollo.util; + +import java.util.Collections; +import java.util.Enumeration; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Properties; +import java.util.Set; + +/** + * An OrderedProperties instance will keep appearance order in config file. + * + * + * Warnings: 1. It should be noticed that stream APIs or JDk1.8 APIs( listed in + * https://github.com/ctripcorp/apollo/pull/2861) are not implemented here. 2. {@link Properties} + * implementation are different between JDK1.8 and later JDKs. At least, {@link Properties} have an + * individual implementation in JDK10. Hence, there should be an individual putAll method here. + * + * + * @author songdragon@zts.io + */ +public class OrderedProperties extends Properties { + + private static final long serialVersionUID = -1741073539526213291L; + private final Set propertyNames; + + public OrderedProperties() { + propertyNames = Collections.synchronizedSet(new LinkedHashSet()); + } + + @Override + public synchronized Object put(Object key, Object value) { + addPropertyName(key); + return super.put(key, value); + } + + private void addPropertyName(Object key) { + if (key instanceof String) { + propertyNames.add((String) key); + } + } + + @Override + public Set stringPropertyNames() { + return propertyNames; + } + + @Override + public Enumeration propertyNames() { + return Collections.enumeration(propertyNames); + } + + @Override + public synchronized Enumeration keys() { + return new Enumeration() { + private final Iterator i = propertyNames.iterator(); + + @Override + public boolean hasMoreElements() { + return i.hasNext(); + } + + @Override + public Object nextElement() { + return i.next(); + } + }; + } + + @Override + public Set keySet() { + return new LinkedHashSet(propertyNames); + } + + + @Override + public Set> entrySet() { + Set> original = super.entrySet(); + LinkedHashMap> entryMap = new LinkedHashMap<>(); + for (String propertyName : propertyNames) { + entryMap.put(propertyName, null); + } + + for (Entry entry : original) { + entryMap.put(entry.getKey(), entry); + } + + return new LinkedHashSet<>(entryMap.values()); + } + + @Override + public synchronized void putAll(Map t) { + super.putAll(t); + for (Object name : t.keySet()) { + addPropertyName(name); + } + } + + @Override + public synchronized void clear() { + super.clear(); + this.propertyNames.clear(); + } + + @Override + public synchronized Object remove(Object key) { + this.propertyNames.remove(key); + return super.remove(key); + } + +} diff --git a/apollo-client/src/main/java/com/ctrip/framework/apollo/util/factory/DefaultPropertiesFactory.java b/apollo-client/src/main/java/com/ctrip/framework/apollo/util/factory/DefaultPropertiesFactory.java new file mode 100644 index 00000000000..bf944ea04e4 --- /dev/null +++ b/apollo-client/src/main/java/com/ctrip/framework/apollo/util/factory/DefaultPropertiesFactory.java @@ -0,0 +1,34 @@ +package com.ctrip.framework.apollo.util.factory; + +import com.ctrip.framework.apollo.build.ApolloInjector; +import com.ctrip.framework.apollo.util.ConfigUtil; +import com.ctrip.framework.apollo.util.OrderedProperties; +import com.google.common.base.Strings; +import java.util.Properties; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Default PropertiesFactory implementation. + * + * @author songdragon@zts.io + */ +public class DefaultPropertiesFactory implements PropertiesFactory { + + private static final Logger logger = LoggerFactory.getLogger(DefaultPropertiesFactory.class); + + private ConfigUtil m_configUtil; + + public DefaultPropertiesFactory() { + m_configUtil = ApolloInjector.getInstance(ConfigUtil.class); + } + + @Override + public Properties getPropertiesInstance() { + if (m_configUtil.isPropertiesOrderEnabled()) { + return new OrderedProperties(); + } else { + return new Properties(); + } + } +} diff --git a/apollo-client/src/main/java/com/ctrip/framework/apollo/util/factory/PropertiesFactory.java b/apollo-client/src/main/java/com/ctrip/framework/apollo/util/factory/PropertiesFactory.java new file mode 100644 index 00000000000..8799ffa584e --- /dev/null +++ b/apollo-client/src/main/java/com/ctrip/framework/apollo/util/factory/PropertiesFactory.java @@ -0,0 +1,28 @@ +package com.ctrip.framework.apollo.util.factory; + +import java.util.Properties; + +/** + * Factory interface to construct Properties instances. + * + * @author songdragon@zts.io + */ +public interface PropertiesFactory { + + /** + * Configuration to keep properties order as same as line order in .yml/.yaml/.properties file. + */ + public static final String APOLLO_PROPERTY_ORDER_ENABLE = "apollo.property.order.enable"; + + /** + *
+   * Default implementation:
+   * 1. if {@link APOLLO_PROPERTY_ORDER_ENABLE} is true return a new
+   * instance of {@link com.ctrip.framework.apollo.util.OrderedProperties}.
+   * 2. else return a new instance of {@link Properties}
+   * 
+ * + * @return + */ + public Properties getPropertiesInstance(); +} diff --git a/apollo-client/src/main/java/com/ctrip/framework/apollo/util/yaml/YamlParser.java b/apollo-client/src/main/java/com/ctrip/framework/apollo/util/yaml/YamlParser.java index 3110c798a77..d324da91c1c 100644 --- a/apollo-client/src/main/java/com/ctrip/framework/apollo/util/yaml/YamlParser.java +++ b/apollo-client/src/main/java/com/ctrip/framework/apollo/util/yaml/YamlParser.java @@ -1,5 +1,7 @@ package com.ctrip.framework.apollo.util.yaml; +import com.ctrip.framework.apollo.build.ApolloInjector; +import com.ctrip.framework.apollo.util.factory.PropertiesFactory; import java.util.AbstractMap; import java.util.Collection; import java.util.Collections; @@ -25,12 +27,14 @@ public class YamlParser { private static final Logger logger = LoggerFactory.getLogger(YamlParser.class); + private PropertiesFactory propertiesFactory = ApolloInjector.getInstance(PropertiesFactory.class); + /** * Transform yaml content to properties */ public Properties yamlToProperties(String yamlContent) { Yaml yaml = createYaml(); - final Properties result = new Properties(); + final Properties result = propertiesFactory.getPropertiesInstance(); process(new MatchCallback() { @Override public void process(Properties properties, Map map) { @@ -91,7 +95,7 @@ private Map asMap(Object object) { } private boolean process(Map map, MatchCallback callback) { - Properties properties = new Properties(); + Properties properties = propertiesFactory.getPropertiesInstance(); properties.putAll(getFlattenedMap(map)); if (logger.isDebugEnabled()) { diff --git a/apollo-client/src/test/java/com/ctrip/framework/apollo/BaseIntegrationTest.java b/apollo-client/src/test/java/com/ctrip/framework/apollo/BaseIntegrationTest.java index efe795a2cd4..7eaca8e2460 100644 --- a/apollo-client/src/test/java/com/ctrip/framework/apollo/BaseIntegrationTest.java +++ b/apollo-client/src/test/java/com/ctrip/framework/apollo/BaseIntegrationTest.java @@ -32,7 +32,8 @@ /** * @author Jason Song(song_s@ctrip.com) */ -public abstract class BaseIntegrationTest{ +public abstract class BaseIntegrationTest { + private static final int PORT = findFreePort(); private static final String metaServiceUrl = "http://localhost:" + PORT; private static final String someAppName = "someAppName"; @@ -74,6 +75,7 @@ public void setUp() throws Exception { /** * init and start a jetty server, remember to call server.stop when the task is finished + * * @param handlers * @throws Exception */ @@ -112,7 +114,7 @@ protected ContextHandler mockMetaServerHandler(final boolean failedAtFirstTime) context.setHandler(new AbstractHandler() { @Override public void handle(String target, Request baseRequest, HttpServletRequest request, - HttpServletResponse response) throws IOException, ServletException { + HttpServletResponse response) throws IOException, ServletException { if (failedAtFirstTime && counter.incrementAndGet() == 1) { response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); baseRequest.setHandled(true); @@ -139,6 +141,7 @@ protected void setRefreshTimeUnit(TimeUnit refreshTimeUnit) { } public static class MockConfigUtil extends ConfigUtil { + @Override public String getAppId() { return someAppId; @@ -198,13 +201,19 @@ public TimeUnit getOnErrorRetryIntervalTimeUnit() { public long getLongPollingInitialDelayInMills() { return 0; } + + @Override + public boolean isPropertiesOrderEnabled() { + return true; + } } /** * Returns a free port number on localhost. - * - * Heavily inspired from org.eclipse.jdt.launching.SocketUtil (to avoid a dependency to JDT just because of this). - * Slightly improved with close() missing in JDT. And throws exception instead of returning -1. + *

+ * Heavily inspired from org.eclipse.jdt.launching.SocketUtil (to avoid a dependency to JDT just + * because of this). Slightly improved with close() missing in JDT. And throws exception instead + * of returning -1. * * @return a free port number on localhost * @throws IllegalStateException if unable to find a free port @@ -230,7 +239,8 @@ private static int findFreePort() { } } } - throw new IllegalStateException("Could not find a free TCP/IP port to start embedded Jetty HTTP Server on"); + throw new IllegalStateException( + "Could not find a free TCP/IP port to start embedded Jetty HTTP Server on"); } } diff --git a/apollo-client/src/test/java/com/ctrip/framework/apollo/integration/ConfigIntegrationTest.java b/apollo-client/src/test/java/com/ctrip/framework/apollo/integration/ConfigIntegrationTest.java index fb2f62fb64f..1d968e2ea84 100644 --- a/apollo-client/src/test/java/com/ctrip/framework/apollo/integration/ConfigIntegrationTest.java +++ b/apollo-client/src/test/java/com/ctrip/framework/apollo/integration/ConfigIntegrationTest.java @@ -9,9 +9,7 @@ import java.io.FileOutputStream; import java.io.IOException; import java.nio.file.Files; -import java.util.List; -import java.util.Map; -import java.util.Properties; +import java.util.*; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; @@ -19,6 +17,7 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import com.ctrip.framework.apollo.util.factory.PropertiesFactory; import org.eclipse.jetty.server.Request; import org.eclipse.jetty.server.handler.AbstractHandler; import org.eclipse.jetty.server.handler.ContextHandler; @@ -48,11 +47,13 @@ * @author Jason Song(song_s@ctrip.com) */ public class ConfigIntegrationTest extends BaseIntegrationTest { + private String someReleaseKey; private File configDir; private String defaultNamespace; private String someOtherNamespace; private RemoteConfigLongPollService remoteConfigLongPollService; + private PropertiesFactory propertiesFactory; @Before public void setUp() throws Exception { @@ -67,6 +68,9 @@ public void setUp() throws Exception { } configDir.mkdirs(); remoteConfigLongPollService = ApolloInjector.getInstance(RemoteConfigLongPollService.class); + + System.setProperty(PropertiesFactory.APOLLO_PROPERTY_ORDER_ENABLE, "true"); + propertiesFactory = ApolloInjector.getInstance(PropertiesFactory.class); } @Override @@ -74,6 +78,7 @@ public void setUp() throws Exception { public void tearDown() throws Exception { ReflectionTestUtils.invokeMethod(remoteConfigLongPollService, "stopLongPollingRefresh"); recursiveDelete(configDir); + System.clearProperty(PropertiesFactory.APOLLO_PROPERTY_ORDER_ENABLE); super.tearDown(); } @@ -110,6 +115,28 @@ public void testGetConfigWithNoLocalFileButWithRemoteConfig() throws Exception { assertEquals(someDefaultValue, config.getProperty(someNonExistedKey, someDefaultValue)); } + @Test + public void testOrderGetConfigWithNoLocalFileButWithRemoteConfig() throws Exception { + String someKey1 = "someKey1"; + String someValue1 = "someValue1"; + String someKey2 = "someKey2"; + String someValue2 = "someValue2"; + Map configurations = new LinkedHashMap<>(); + configurations.put(someKey1, someValue1); + configurations.put(someKey2, someValue2); + ApolloConfig apolloConfig = assembleApolloConfig(ImmutableMap.copyOf(configurations)); + ContextHandler handler = mockConfigServerHandler(HttpServletResponse.SC_OK, apolloConfig); + startServerWithHandlers(handler); + + Config config = ConfigService.getAppConfig(); + + Set propertyNames = config.getPropertyNames(); + Iterator it = propertyNames.iterator(); + assertEquals(someKey1, it.next()); + assertEquals(someKey2, it.next()); + + } + @Test public void testGetConfigWithLocalFileAndWithRemoteConfig() throws Exception { String someKey = "someKey"; @@ -128,6 +155,45 @@ public void testGetConfigWithLocalFileAndWithRemoteConfig() throws Exception { assertEquals(anotherValue, config.getProperty(someKey, null)); } + @Test + public void testOrderGetConfigWithLocalFileAndWithRemoteConfig() throws Exception { + String someKey = "someKey"; + String someValue = "someValue"; + String anotherValue = "anotherValue"; + + String someKey1 = "someKey1"; + String someValue1 = "someValue1"; + String anotherValue1 = "anotherValue1"; + String someKey2 = "someKey2"; + String someValue2 = "someValue2"; + + Properties properties = propertiesFactory.getPropertiesInstance(); + properties.put(someKey, someValue); + properties.put(someKey1, someValue1); + properties.put(someKey2, someValue2); + createLocalCachePropertyFile(properties); + + Map configurations = new LinkedHashMap<>(); + configurations.put(someKey, anotherValue); + configurations.put(someKey1, anotherValue1); + configurations.put(someKey2, someValue2); + ApolloConfig apolloConfig = assembleApolloConfig(ImmutableMap.copyOf(configurations)); + ContextHandler handler = mockConfigServerHandler(HttpServletResponse.SC_OK, apolloConfig); + startServerWithHandlers(handler); + + Config config = ConfigService.getAppConfig(); + + assertEquals(anotherValue, config.getProperty(someKey, null)); + + Set propertyNames = config.getPropertyNames(); + Iterator it = propertyNames.iterator(); + assertEquals(someKey, it.next()); + assertEquals(someKey1, it.next()); + assertEquals(someKey2, it.next()); + assertEquals(anotherValue1, config.getProperty(someKey1, "")); + + } + @Test public void testGetConfigWithNoLocalFileAndRemoteConfigError() throws Exception { ContextHandler handler = @@ -158,6 +224,31 @@ public void testGetConfigWithLocalFileAndRemoteConfigError() throws Exception { assertEquals(someValue, config.getProperty(someKey, null)); } + @Test + public void testOrderGetConfigWithLocalFileAndRemoteConfigError() throws Exception { + String someKey1 = "someKey1"; + String someValue1 = "someValue1"; + String someKey2 = "someKey2"; + String someValue2 = "someValue2"; + Properties properties = propertiesFactory.getPropertiesInstance(); + properties.put(someKey1, someValue1); + properties.put(someKey2, someValue2); + createLocalCachePropertyFile(properties); + + ContextHandler handler = + mockConfigServerHandler(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, null); + startServerWithHandlers(handler); + + Config config = ConfigService.getAppConfig(); + assertEquals(someValue1, config.getProperty(someKey1, null)); + assertEquals(someValue2, config.getProperty(someKey2, null)); + + Set propertyNames = config.getPropertyNames(); + Iterator it = propertyNames.iterator(); + assertEquals(someKey1, it.next()); + assertEquals(someKey2, it.next()); + } + @Test public void testGetConfigWithNoLocalFileAndRemoteMetaServiceRetry() throws Exception { String someKey = "someKey"; @@ -279,7 +370,8 @@ public void onChange(ConfigChangeEvent changeEvent) { } @Test - public void testLongPollRefreshWithMultipleNamespacesAndOnlyOneNamespaceNotified() throws Exception { + public void testLongPollRefreshWithMultipleNamespacesAndOnlyOneNamespaceNotified() + throws Exception { final String someKey = "someKey"; final String someValue = "someValue"; final String anotherValue = "anotherValue"; @@ -323,7 +415,8 @@ public void onChange(ConfigChangeEvent changeEvent) { } @Test - public void testLongPollRefreshWithMultipleNamespacesAndMultipleNamespaceNotified() throws Exception { + public void testLongPollRefreshWithMultipleNamespacesAndMultipleNamespaceNotified() + throws Exception { final String someKey = "someKey"; final String someValue = "someValue"; final String anotherValue = "anotherValue"; @@ -375,16 +468,16 @@ public void onChange(ConfigChangeEvent changeEvent) { } private ContextHandler mockPollNotificationHandler(final long pollResultTimeOutInMS, - final int statusCode, - final List result, - final boolean failedAtFirstTime) { + final int statusCode, + final List result, + final boolean failedAtFirstTime) { ContextHandler context = new ContextHandler("/notifications/v2"); context.setHandler(new AbstractHandler() { AtomicInteger counter = new AtomicInteger(0); @Override public void handle(String target, Request baseRequest, HttpServletRequest request, - HttpServletResponse response) throws IOException, ServletException { + HttpServletResponse response) throws IOException, ServletException { if (failedAtFirstTime && counter.incrementAndGet() == 1) { response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); baseRequest.setHandled(true); @@ -407,14 +500,14 @@ public void handle(String target, Request baseRequest, HttpServletRequest reques } private ContextHandler mockConfigServerHandler(final int statusCode, final ApolloConfig result, - final boolean failedAtFirstTime) { + final boolean failedAtFirstTime) { ContextHandler context = new ContextHandler("/configs/*"); context.setHandler(new AbstractHandler() { AtomicInteger counter = new AtomicInteger(0); @Override public void handle(String target, Request baseRequest, HttpServletRequest request, - HttpServletResponse response) throws IOException, ServletException { + HttpServletResponse response) throws IOException, ServletException { if (failedAtFirstTime && counter.incrementAndGet() == 1) { response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); baseRequest.setHandled(true); diff --git a/apollo-client/src/test/java/com/ctrip/framework/apollo/internals/DefaultConfigTest.java b/apollo-client/src/test/java/com/ctrip/framework/apollo/internals/DefaultConfigTest.java index 66694c5bbcb..89a33b34b6d 100644 --- a/apollo-client/src/test/java/com/ctrip/framework/apollo/internals/DefaultConfigTest.java +++ b/apollo-client/src/test/java/com/ctrip/framework/apollo/internals/DefaultConfigTest.java @@ -10,6 +10,8 @@ import static org.mockito.Mockito.when; import com.ctrip.framework.apollo.enums.ConfigSourceType; +import com.ctrip.framework.apollo.util.factory.DefaultPropertiesFactory; +import com.ctrip.framework.apollo.util.factory.PropertiesFactory; import com.google.common.collect.Maps; import com.google.common.collect.Sets; import java.io.File; @@ -57,6 +59,7 @@ public class DefaultConfigTest { public void setUp() throws Exception { MockInjector.reset(); MockInjector.setInstance(ConfigUtil.class, new MockConfigUtil()); + MockInjector.setInstance(PropertiesFactory.class, new DefaultPropertiesFactory()); someResourceDir = new File(ClassLoaderUtil.getClassPath() + "/META-INF/config"); someResourceDir.mkdirs(); diff --git a/apollo-client/src/test/java/com/ctrip/framework/apollo/internals/LocalFileConfigRepositoryTest.java b/apollo-client/src/test/java/com/ctrip/framework/apollo/internals/LocalFileConfigRepositoryTest.java index d5b609b10b0..5f6f9811245 100644 --- a/apollo-client/src/test/java/com/ctrip/framework/apollo/internals/LocalFileConfigRepositoryTest.java +++ b/apollo-client/src/test/java/com/ctrip/framework/apollo/internals/LocalFileConfigRepositoryTest.java @@ -1,5 +1,9 @@ package com.ctrip.framework.apollo.internals; +import static org.hamcrest.Matchers.both; +import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.hamcrest.Matchers.everyItem; +import static org.hamcrest.Matchers.isIn; import static org.hamcrest.core.IsEqual.equalTo; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertThat; @@ -10,11 +14,15 @@ import static org.mockito.Mockito.when; import com.ctrip.framework.apollo.enums.ConfigSourceType; +import com.ctrip.framework.apollo.util.factory.DefaultPropertiesFactory; +import com.ctrip.framework.apollo.util.factory.PropertiesFactory; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; +import java.util.Map.Entry; import java.util.Properties; +import org.hamcrest.Matcher; import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -31,6 +39,7 @@ * Created by Jason on 4/9/16. */ public class LocalFileConfigRepositoryTest { + private File someBaseDir; private String someNamespace; private ConfigRepository upstreamRepo; @@ -40,6 +49,7 @@ public class LocalFileConfigRepositoryTest { private String defaultKey; private String defaultValue; private ConfigSourceType someSourceType; + private MockConfigUtil configUtil; @Before public void setUp() throws Exception { @@ -57,7 +67,10 @@ public void setUp() throws Exception { when(upstreamRepo.getSourceType()).thenReturn(someSourceType); MockInjector.reset(); - MockInjector.setInstance(ConfigUtil.class, new MockConfigUtil()); + configUtil = new MockConfigUtil(); + MockInjector.setInstance(ConfigUtil.class, configUtil); + MockInjector.setInstance(PropertiesFactory.class, new DefaultPropertiesFactory()); + } @After @@ -110,7 +123,8 @@ public void testLoadConfigWithLocalFileAndFallbackRepo() throws Exception { Files.write(defaultKey + "=" + someValue, file, Charsets.UTF_8); - LocalFileConfigRepository localRepo = new LocalFileConfigRepository(someNamespace, upstreamRepo); + LocalFileConfigRepository localRepo = new LocalFileConfigRepository(someNamespace, + upstreamRepo); localRepo.setLocalCacheDir(someBaseDir, true); Properties properties = localRepo.getConfig(); @@ -127,9 +141,20 @@ public void testLoadConfigWithNoLocalFile() throws Exception { Properties result = localFileConfigRepository.getConfig(); - assertThat( - "LocalFileConfigRepository's properties should be the same as fallback repo's when there is no local cache", - result.entrySet(), equalTo(someProperties.entrySet())); + if (!isJDK11() || (isJDK11() && configUtil.isPropertiesOrderEnabled())) { + assertThat( + "LocalFileConfigRepository's properties should be the same as fallback repo's when there is no local cache", + result.entrySet(), equalTo(someProperties.entrySet())); + } else { + //In JDK11 Properties return EntrySet without customize equals implementation, so equalTo will check + //whether they are same object. This is why two statements are used to check items in entryset. + assertThat( + "LocalFileConfigRepository's properties should be the same as fallback repo's when there is no local cache", + result.entrySet(), everyItem(isIn(someProperties.entrySet()))); + assertThat( + "LocalFileConfigRepository's properties should be the same as fallback repo's when there is no local cache", + someProperties.entrySet(), everyItem(isIn(result.entrySet()))); + } assertEquals(someSourceType, localFileConfigRepository.getSourceType()); } @@ -148,9 +173,20 @@ public void testLoadConfigWithNoLocalFileMultipleTimes() throws Exception { Properties anotherProperties = anotherLocalRepoWithNoFallback.getConfig(); - assertThat( - "LocalFileConfigRepository should persist local cache files and return that afterwards", - someProperties.entrySet(), equalTo(anotherProperties.entrySet())); + if (!isJDK11() || (isJDK11() && configUtil.isPropertiesOrderEnabled())) { + assertThat( + "LocalFileConfigRepository should persist local cache files and return that afterwards", + someProperties.entrySet(), equalTo(anotherProperties.entrySet())); + } else { + //In JDK11 Properties return EntrySet without customize equals implementation, so equalTo will check + //whether they are same object. This is why two statements are used to check items in entryset. + assertThat( + "LocalFileConfigRepository's properties should be the same as fallback repo's when there is no local cache", + someProperties.entrySet(), everyItem(isIn(anotherProperties.entrySet()))); + assertThat( + "LocalFileConfigRepository's properties should be the same as fallback repo's when there is no local cache", + anotherProperties.entrySet(), everyItem(isIn(someProperties.entrySet()))); + } assertEquals(someSourceType, localRepo.getSourceType()); } @@ -185,6 +221,7 @@ public void testOnRepositoryChange() throws Exception { } public static class MockConfigUtil extends ConfigUtil { + @Override public String getAppId() { return someAppId; @@ -194,6 +231,10 @@ public String getAppId() { public String getCluster() { return someCluster; } + + public String getJavaVersion() { + return System.getProperty("java.version"); + } } private File createLocalCachePropertyFile(Properties properties) throws IOException { @@ -209,4 +250,8 @@ private File createLocalCachePropertyFile(Properties properties) throws IOExcept } return file; } + + private boolean isJDK11() { + return configUtil.getJavaVersion().startsWith("11."); + } } diff --git a/apollo-client/src/test/java/com/ctrip/framework/apollo/internals/PropertiesConfigFileTest.java b/apollo-client/src/test/java/com/ctrip/framework/apollo/internals/PropertiesConfigFileTest.java index bc8f43a810d..2610c3ebb70 100644 --- a/apollo-client/src/test/java/com/ctrip/framework/apollo/internals/PropertiesConfigFileTest.java +++ b/apollo-client/src/test/java/com/ctrip/framework/apollo/internals/PropertiesConfigFileTest.java @@ -7,8 +7,12 @@ import static org.mockito.Mockito.when; import com.ctrip.framework.apollo.ConfigFileChangeListener; +import com.ctrip.framework.apollo.build.MockInjector; import com.ctrip.framework.apollo.enums.PropertyChangeType; import com.ctrip.framework.apollo.model.ConfigFileChangeEvent; +import com.ctrip.framework.apollo.util.ConfigUtil; +import com.ctrip.framework.apollo.util.factory.DefaultPropertiesFactory; +import com.ctrip.framework.apollo.util.factory.PropertiesFactory; import com.google.common.util.concurrent.SettableFuture; import java.util.Properties; @@ -26,6 +30,7 @@ */ @RunWith(MockitoJUnitRunner.class) public class PropertiesConfigFileTest { + private String someNamespace; @Mock private ConfigRepository configRepository; @@ -33,6 +38,9 @@ public class PropertiesConfigFileTest { @Before public void setUp() throws Exception { someNamespace = "someName"; + MockInjector.reset(); + MockInjector.setInstance(ConfigUtil.class, new ConfigUtil()); + MockInjector.setInstance(PropertiesFactory.class, new DefaultPropertiesFactory()); } @Test diff --git a/apollo-client/src/test/java/com/ctrip/framework/apollo/internals/RemoteConfigRepositoryTest.java b/apollo-client/src/test/java/com/ctrip/framework/apollo/internals/RemoteConfigRepositoryTest.java index 0fa0c1e375a..ee1fb018458 100644 --- a/apollo-client/src/test/java/com/ctrip/framework/apollo/internals/RemoteConfigRepositoryTest.java +++ b/apollo-client/src/test/java/com/ctrip/framework/apollo/internals/RemoteConfigRepositoryTest.java @@ -1,5 +1,6 @@ package com.ctrip.framework.apollo.internals; +import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; @@ -22,6 +23,8 @@ import com.ctrip.framework.apollo.enums.ConfigSourceType; import com.ctrip.framework.apollo.exceptions.ApolloConfigException; import com.ctrip.framework.apollo.util.ConfigUtil; +import com.ctrip.framework.apollo.util.factory.DefaultPropertiesFactory; +import com.ctrip.framework.apollo.util.factory.PropertiesFactory; import com.ctrip.framework.apollo.util.http.HttpRequest; import com.ctrip.framework.apollo.util.http.HttpResponse; import com.ctrip.framework.apollo.util.http.HttpUtil; @@ -37,6 +40,7 @@ import java.util.Properties; import java.util.concurrent.TimeUnit; import javax.servlet.http.HttpServletResponse; +import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -45,12 +49,14 @@ import org.mockito.invocation.InvocationOnMock; import org.mockito.runners.MockitoJUnitRunner; import org.mockito.stubbing.Answer; +import org.springframework.test.util.ReflectionTestUtils; /** * Created by Jason on 4/9/16. */ @RunWith(MockitoJUnitRunner.class) public class RemoteConfigRepositoryTest { + @Mock private ConfigServiceLocator configServiceLocator; private String someNamespace; @@ -92,16 +98,26 @@ public void setUp() throws Exception { MockInjector.setInstance(RemoteConfigLongPollService.class, remoteConfigLongPollService); + System.setProperty(PropertiesFactory.APOLLO_PROPERTY_ORDER_ENABLE, "true"); + PropertiesFactory propertiesFactory = new DefaultPropertiesFactory(); + MockInjector.setInstance(PropertiesFactory.class, propertiesFactory); + someAppId = "someAppId"; someCluster = "someCluster"; } + @After + public void tearDown() throws Exception { + System.clearProperty(PropertiesFactory.APOLLO_PROPERTY_ORDER_ENABLE); + } + @Test public void testLoadConfig() throws Exception { String someKey = "someKey"; String someValue = "someValue"; - Map configurations = Maps.newHashMap(); + Map configurations = Maps.newLinkedHashMap(); configurations.put(someKey, someValue); + configurations.put("someKey2", "someValue2"); ApolloConfig someApolloConfig = assembleApolloConfig(configurations); when(someResponse.getStatusCode()).thenReturn(200); @@ -111,9 +127,16 @@ public void testLoadConfig() throws Exception { Properties config = remoteConfigRepository.getConfig(); - assertEquals(configurations, config); +// assertEquals(configurations, config); + assertTrue(configurations.equals(config)); assertEquals(ConfigSourceType.REMOTE, remoteConfigRepository.getSourceType()); remoteConfigLongPollService.stopLongPollingRefresh(); + + if (configUtil.isPropertiesOrderEnabled()) { + String[] actualArrays = config.keySet().toArray(new String[]{}); + String[] expectedArrays = {"someKey", "someKey2"}; + assertArrayEquals(expectedArrays, actualArrays); + } } @Test @@ -236,7 +259,8 @@ public Void answer(InvocationOnMock invocation) throws Throwable { verify(someListener, times(1)).onRepositoryChange(eq(someNamespace), captor.capture()); assertEquals(newConfigurations, captor.getValue()); - final ArgumentCaptor httpRequestArgumentCaptor = ArgumentCaptor.forClass(HttpRequest.class); + final ArgumentCaptor httpRequestArgumentCaptor = ArgumentCaptor + .forClass(HttpRequest.class); verify(httpUtil, atLeast(2)).doGet(httpRequestArgumentCaptor.capture(), eq(ApolloConfig.class)); HttpRequest request = httpRequestArgumentCaptor.getValue(); @@ -265,7 +289,8 @@ public void testAssembleQueryConfigUrl() throws Exception { when(someApolloConfig.getReleaseKey()).thenReturn(someReleaseKey); String queryConfigUrl = remoteConfigRepository - .assembleQueryConfigUrl(someUri, someAppId, someCluster, someNamespace, null, notificationMessages, + .assembleQueryConfigUrl(someUri, someAppId, someCluster, someNamespace, null, + notificationMessages, someApolloConfig); remoteConfigLongPollService.stopLongPollingRefresh(); @@ -275,7 +300,8 @@ public void testAssembleQueryConfigUrl() throws Exception { assertTrue(queryConfigUrl .contains("releaseKey=20160705193346-583078ef5716c055%2B20160705193308-31c471ddf9087c3f")); assertTrue(queryConfigUrl - .contains("messages=" + UrlEscapers.urlFormParameterEscaper().escape(gson.toJson(notificationMessages)))); + .contains("messages=" + UrlEscapers.urlFormParameterEscaper() + .escape(gson.toJson(notificationMessages)))); } private ApolloConfig assembleApolloConfig(Map configurations) { @@ -291,6 +317,7 @@ private ApolloConfig assembleApolloConfig(Map configurations) { } public static class MockConfigUtil extends ConfigUtil { + @Override public String getAppId() { return someAppId; @@ -338,9 +365,10 @@ public long getLongPollingInitialDelayInMills() { } public static class MockHttpUtil extends HttpUtil { + @Override public HttpResponse doGet(HttpRequest httpRequest, Class responseType) { - if (someResponse.getStatusCode() == 200 || someResponse.getStatusCode() == 304 ) { + if (someResponse.getStatusCode() == 200 || someResponse.getStatusCode() == 304) { return (HttpResponse) someResponse; } throw new ApolloConfigException(String.format("Http request failed due to status code: %d", diff --git a/apollo-client/src/test/java/com/ctrip/framework/apollo/internals/SimpleConfigTest.java b/apollo-client/src/test/java/com/ctrip/framework/apollo/internals/SimpleConfigTest.java index de6a1973eec..f0bde95d6c1 100644 --- a/apollo-client/src/test/java/com/ctrip/framework/apollo/internals/SimpleConfigTest.java +++ b/apollo-client/src/test/java/com/ctrip/framework/apollo/internals/SimpleConfigTest.java @@ -4,10 +4,14 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +import com.ctrip.framework.apollo.build.MockInjector; import com.ctrip.framework.apollo.enums.ConfigSourceType; +import com.ctrip.framework.apollo.util.factory.DefaultPropertiesFactory; +import com.ctrip.framework.apollo.util.factory.PropertiesFactory; import java.util.Properties; import java.util.concurrent.TimeUnit; +import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -27,6 +31,7 @@ */ @RunWith(MockitoJUnitRunner.class) public class SimpleConfigTest { + private String someNamespace; @Mock private ConfigRepository configRepository; @@ -35,6 +40,15 @@ public class SimpleConfigTest { @Before public void setUp() throws Exception { someNamespace = "someName"; + + System.setProperty(PropertiesFactory.APOLLO_PROPERTY_ORDER_ENABLE, "true"); + PropertiesFactory propertiesFactory = new DefaultPropertiesFactory(); + MockInjector.setInstance(PropertiesFactory.class, propertiesFactory); + } + + @After + public void tearDown() throws Exception { + System.clearProperty(PropertiesFactory.APOLLO_PROPERTY_ORDER_ENABLE); } @Test diff --git a/apollo-client/src/test/java/com/ctrip/framework/apollo/internals/XmlConfigFileTest.java b/apollo-client/src/test/java/com/ctrip/framework/apollo/internals/XmlConfigFileTest.java index 13552029ec8..03a36090327 100644 --- a/apollo-client/src/test/java/com/ctrip/framework/apollo/internals/XmlConfigFileTest.java +++ b/apollo-client/src/test/java/com/ctrip/framework/apollo/internals/XmlConfigFileTest.java @@ -7,12 +7,16 @@ import static org.mockito.Mockito.when; import com.ctrip.framework.apollo.ConfigFileChangeListener; +import com.ctrip.framework.apollo.build.MockInjector; import com.ctrip.framework.apollo.enums.PropertyChangeType; import com.ctrip.framework.apollo.model.ConfigFileChangeEvent; +import com.ctrip.framework.apollo.util.factory.DefaultPropertiesFactory; +import com.ctrip.framework.apollo.util.factory.PropertiesFactory; import com.google.common.util.concurrent.SettableFuture; import java.util.Properties; import java.util.concurrent.TimeUnit; +import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -27,6 +31,7 @@ */ @RunWith(MockitoJUnitRunner.class) public class XmlConfigFileTest { + private String someNamespace; @Mock private ConfigRepository configRepository; @@ -34,6 +39,15 @@ public class XmlConfigFileTest { @Before public void setUp() throws Exception { someNamespace = "someName"; + + System.setProperty(PropertiesFactory.APOLLO_PROPERTY_ORDER_ENABLE, "true"); + PropertiesFactory propertiesFactory = new DefaultPropertiesFactory(); + MockInjector.setInstance(PropertiesFactory.class, propertiesFactory); + } + + @After + public void tearDown() throws Exception { + System.clearProperty(PropertiesFactory.APOLLO_PROPERTY_ORDER_ENABLE); } @Test diff --git a/apollo-client/src/test/java/com/ctrip/framework/apollo/internals/YamlConfigFileTest.java b/apollo-client/src/test/java/com/ctrip/framework/apollo/internals/YamlConfigFileTest.java index 379b964053b..71df2f922d1 100644 --- a/apollo-client/src/test/java/com/ctrip/framework/apollo/internals/YamlConfigFileTest.java +++ b/apollo-client/src/test/java/com/ctrip/framework/apollo/internals/YamlConfigFileTest.java @@ -7,8 +7,12 @@ import com.ctrip.framework.apollo.core.ConfigConsts; import com.ctrip.framework.apollo.enums.ConfigSourceType; import com.ctrip.framework.apollo.exceptions.ApolloConfigException; +import com.ctrip.framework.apollo.util.ConfigUtil; +import com.ctrip.framework.apollo.util.factory.DefaultPropertiesFactory; +import com.ctrip.framework.apollo.util.factory.PropertiesFactory; import com.ctrip.framework.apollo.util.yaml.YamlParser; import java.util.Properties; +import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -17,6 +21,7 @@ @RunWith(MockitoJUnitRunner.class) public class YamlConfigFileTest { + private String someNamespace; @Mock private ConfigRepository configRepository; @@ -29,8 +34,17 @@ public class YamlConfigFileTest { public void setUp() throws Exception { someNamespace = "someName"; + System.setProperty(PropertiesFactory.APOLLO_PROPERTY_ORDER_ENABLE, "true"); + MockInjector.reset(); MockInjector.setInstance(YamlParser.class, yamlParser); + MockInjector.setInstance(ConfigUtil.class, new ConfigUtil()); + MockInjector.setInstance(PropertiesFactory.class, new DefaultPropertiesFactory()); + } + + @After + public void tearDown() throws Exception { + System.clearProperty(PropertiesFactory.APOLLO_PROPERTY_ORDER_ENABLE); } @Test @@ -54,6 +68,30 @@ public void testWhenHasContent() throws Exception { assertSame(yamlProperties, configFile.asProperties()); } + @Test + public void testWhenHasContentWithOrder() throws Exception { + Properties someProperties = new Properties(); + String key = ConfigConsts.CONFIG_FILE_CONTENT_KEY; + String someContent = "someKey: 'someValue'\nsomeKey2: 'someValue2'"; + someProperties.setProperty(key, someContent); + someSourceType = ConfigSourceType.LOCAL; + + Properties yamlProperties = new YamlParser().yamlToProperties(someContent); + + when(configRepository.getConfig()).thenReturn(someProperties); + when(configRepository.getSourceType()).thenReturn(someSourceType); + when(yamlParser.yamlToProperties(someContent)).thenReturn(yamlProperties); + + YamlConfigFile configFile = new YamlConfigFile(someNamespace, configRepository); + + assertSame(someContent, configFile.getContent()); + assertSame(yamlProperties, configFile.asProperties()); + + String[] actualArrays = configFile.asProperties().keySet().toArray(new String[]{}); + String[] expectedArrays = {"someKey", "someKey2"}; + assertArrayEquals(expectedArrays, actualArrays); + } + @Test public void testWhenHasNoContent() throws Exception { when(configRepository.getConfig()).thenReturn(null); @@ -78,7 +116,8 @@ public void testWhenInvalidYamlContent() throws Exception { when(configRepository.getConfig()).thenReturn(someProperties); when(configRepository.getSourceType()).thenReturn(someSourceType); - when(yamlParser.yamlToProperties(someInvalidContent)).thenThrow(new RuntimeException("some exception")); + when(yamlParser.yamlToProperties(someInvalidContent)) + .thenThrow(new RuntimeException("some exception")); YamlConfigFile configFile = new YamlConfigFile(someNamespace, configRepository); diff --git a/apollo-client/src/test/java/com/ctrip/framework/apollo/util/ConfigUtilTest.java b/apollo-client/src/test/java/com/ctrip/framework/apollo/util/ConfigUtilTest.java index fc3c5e16869..39734c6e0fb 100644 --- a/apollo-client/src/test/java/com/ctrip/framework/apollo/util/ConfigUtilTest.java +++ b/apollo-client/src/test/java/com/ctrip/framework/apollo/util/ConfigUtilTest.java @@ -2,6 +2,7 @@ import com.ctrip.framework.apollo.core.ConfigConsts; +import com.ctrip.framework.apollo.util.factory.PropertiesFactory; import java.io.File; import org.junit.After; import org.junit.Test; @@ -15,6 +16,7 @@ * @author Jason Song(song_s@ctrip.com) */ public class ConfigUtilTest { + @After public void tearDown() throws Exception { System.clearProperty(ConfigConsts.APOLLO_CLUSTER_KEY); @@ -27,6 +29,7 @@ public void tearDown() throws Exception { System.clearProperty("apollo.longPollingInitialDelayInMills"); System.clearProperty("apollo.autoUpdateInjectedSpringProperties"); System.clearProperty("apollo.cacheDir"); + System.clearProperty(PropertiesFactory.APOLLO_PROPERTY_ORDER_ENABLE); } @Test @@ -162,7 +165,8 @@ public void testCustomizeInvalidMaxConfigCacheSize() throws Exception { @Test public void testCustomizeLongPollingInitialDelayInMills() throws Exception { long someLongPollingDelayInMills = 1; - System.setProperty("apollo.longPollingInitialDelayInMills", String.valueOf(someLongPollingDelayInMills)); + System.setProperty("apollo.longPollingInitialDelayInMills", + String.valueOf(someLongPollingDelayInMills)); ConfigUtil configUtil = new ConfigUtil(); @@ -221,4 +225,16 @@ public void testDefaultLocalCacheDir() throws Exception { assertEquals("/opt/data/" + someAppId, configUtil.getDefaultLocalCacheDir()); } + + @Test + public void testCustomizePropertiesOrdered() { + boolean propertiesOrdered = true; + System.setProperty(PropertiesFactory.APOLLO_PROPERTY_ORDER_ENABLE, + String.valueOf(propertiesOrdered)); + + ConfigUtil configUtil = new ConfigUtil(); + + assertEquals(propertiesOrdered, + configUtil.isPropertiesOrderEnabled()); + } } diff --git a/apollo-client/src/test/java/com/ctrip/framework/apollo/util/OrderedPropertiesTest.java b/apollo-client/src/test/java/com/ctrip/framework/apollo/util/OrderedPropertiesTest.java new file mode 100644 index 00000000000..9da431a449d --- /dev/null +++ b/apollo-client/src/test/java/com/ctrip/framework/apollo/util/OrderedPropertiesTest.java @@ -0,0 +1,105 @@ +package com.ctrip.framework.apollo.util; + +import static org.junit.Assert.*; + +import java.util.Collection; +import java.util.Enumeration; +import java.util.Properties; +import org.junit.Before; +import org.junit.Ignore; +import org.junit.Test; + +public class OrderedPropertiesTest { + + private OrderedProperties orderedProperties; + private Properties legacyProperties; + + @Before + public void setUp() { + orderedProperties = new OrderedProperties(); + orderedProperties.setProperty("key1", "value1"); + orderedProperties.setProperty("key2", "value2"); + } + + @Test + public void testOrderedPropertiesInvokedAsLegacyProperties() { + legacyProperties = orderedProperties; + assertEquals(orderedProperties.size(), legacyProperties.size()); + + legacyProperties.put("key3", "value3"); + assertEquals(orderedProperties.size(), legacyProperties.size()); + + assertEquals(orderedProperties.getProperty("key3"), legacyProperties.getProperty("key3")); + assertEquals(orderedProperties.get("key3"), legacyProperties.get("key3")); + + assertEquals(orderedProperties.containsKey("key2"), legacyProperties.containsKey("key2")); + assertEquals(orderedProperties.containsValue("key2"), legacyProperties.containsValue("key2")); + assertEquals(orderedProperties.containsValue("value2"), + legacyProperties.containsValue("value2")); + + assertEquals(orderedProperties.entrySet(), legacyProperties.entrySet()); + assertEquals(orderedProperties.keySet(), legacyProperties.keySet()); + + } + + @Test + public void testClear() { + orderedProperties.clear(); + assertEquals(0, orderedProperties.size()); + assertTrue(orderedProperties.isEmpty()); + } + + @Test + public void testClone() { + OrderedProperties clone = (OrderedProperties) orderedProperties.clone(); + + assertNotSame(clone, orderedProperties); + assertEquals(orderedProperties, clone); + } + + @Test + public void testRemove() { + Object value1 = orderedProperties.remove("key1"); + assertEquals("value1", value1); + + value1 = orderedProperties.remove("key1"); + assertNull(value1); + + assertNull(orderedProperties.get("key1")); + assertFalse(orderedProperties.keySet().contains("key1")); + } + + @Test + public void testValues() { + Collection values = orderedProperties.values(); + assertEquals(2, values.size()); + assertTrue(values.contains("value1")); + assertTrue(values.contains("value2")); + } + + + @Test(expected = NullPointerException.class) + public void testPutNull() { + orderedProperties.put("key3", null); + } + + @Test + public void testPropertyNames() { + Enumeration propertyNames = (Enumeration) orderedProperties.propertyNames(); + assertTrue(propertyNames.nextElement().equals("key1")); + assertTrue(propertyNames.nextElement().equals("key2")); + + } + + + @Test + public void testKeys() { + Enumeration keys = orderedProperties.keys(); + assertTrue(keys.hasMoreElements()); + assertEquals("key1", keys.nextElement()); + assertTrue(keys.hasMoreElements()); + assertEquals("key2", keys.nextElement()); + + } + +}