getPropertyNames() {
54 | return getProperties().keySet();
55 | }
56 |
57 | @Override
58 | public int getOrdinal() {
59 | return ordinal;
60 | }
61 |
62 | /**
63 | * Init method e.g. for initializing the ordinal.
64 | * This method can be used from a subclass to determine
65 | * the ordinal value
66 | *
67 | * @param defaultOrdinal the default value for the ordinal if not set via configuration
68 | */
69 | protected void initOrdinal(int defaultOrdinal) {
70 | ordinal = defaultOrdinal;
71 |
72 | String configuredOrdinalString = getValue(CONFIG_ORDINAL);
73 |
74 | try {
75 | if (configuredOrdinalString != null) {
76 | ordinal = Integer.parseInt(configuredOrdinalString.trim());
77 | }
78 | } catch (NumberFormatException e) {
79 | log.log(Level.WARNING, e,
80 | () -> "The configured config-ordinal isn't a valid integer. Invalid value: " + configuredOrdinalString);
81 | }
82 | }
83 |
84 | private Config createConfig(){
85 | return ConfigProviderResolver.instance()
86 | .getBuilder()
87 | .addDefaultSources()
88 | .build();
89 | }
90 |
91 | }
92 |
--------------------------------------------------------------------------------
/configsource-base/src/main/java/org/microprofileext/config/source/base/EnabledConfigSource.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2018 Derek P. Moore.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package org.microprofileext.config.source.base;
17 |
18 | import java.util.Collections;
19 | import java.util.Map;
20 | import org.eclipse.microprofile.config.Config;
21 |
22 | /**
23 | * A config source that can be disabled by class or by instance (all vs each).
24 | *
25 | * Instance keys take precedence over class keys, so individual sources can be
26 | * turned back on if all sources have been turned off.
27 | *
28 | * Config sources are enabled by default.
29 | *
30 | * @author Derek P. Moore
31 | */
32 | public abstract class EnabledConfigSource extends BaseConfigSource {
33 |
34 | /**
35 | * Called to return the properties in this config source when it is enabled
36 | * @return the map containing the properties in this config source
37 | */
38 | abstract protected Map getPropertiesIfEnabled();
39 |
40 | /**
41 | * Return the properties, unless disabled return empty
42 | * @return the map containing the properties in this config source or empty
43 | * if disabled
44 | */
45 | @Override
46 | public Map getProperties() {
47 | return isEnabled() ? getPropertiesIfEnabled() : Collections.emptyMap();
48 | }
49 |
50 | protected boolean isEnabled() {
51 | Config cnf = getConfig();
52 | return cnf.getOptionalValue(getInstanceEnableKey(), Boolean.class)
53 | .orElse(cnf.getOptionalValue(getClassEnableKey(), Boolean.class)
54 | .orElse(true));
55 | }
56 |
57 | protected String getClassKeyPrefix(){
58 | return getClass().getSimpleName();
59 | }
60 |
61 | private String getClassEnableKey(){
62 | return getClassKeyPrefix() + ".enabled";
63 | }
64 |
65 | private String getInstanceEnableKey(){
66 | return getName() + ".enabled";
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/configsource-base/src/main/java/org/microprofileext/config/source/base/ExpiringMap.java:
--------------------------------------------------------------------------------
1 | package org.microprofileext.config.source.base;
2 |
3 | import java.util.Map;
4 | import java.util.concurrent.ConcurrentHashMap;
5 | import java.util.function.Consumer;
6 |
7 | /**
8 | * A simple map with an expiring policy. Note: this map can also store null
9 | * values!
10 | *
11 | * @param key
12 | * @param value
13 | */
14 | public class ExpiringMap {
15 | long validity;
16 | private Map> cache = new ConcurrentHashMap<>();
17 |
18 | public ExpiringMap(long validity) {
19 | this.validity = validity;
20 | }
21 |
22 | /**
23 | * Similar to Map::computeIfAbsent, but the mapping function can throw an
24 | * exception, which can be handled with a consumer.
25 | *
26 | * @param key key
27 | * @param action mapping function
28 | * @param onException exception handler
29 | * @return
30 | */
31 | public V getOrCompute(K key, CheckedFunction action, Consumer onException) {
32 | TimedEntry entry = cache.get(key);
33 | if (entry == null || entry.isExpired()) {
34 | try {
35 | V value = action.apply(key);
36 | put(key, value);
37 | return value;
38 | } catch (Exception e) {
39 | onException.accept(key);
40 | }
41 | }
42 | // if the entry was never cached, then it will be null
43 | return entry != null ? entry.get() : null;
44 | }
45 |
46 | public void put(K key, V value) {
47 | cache.put(key, new TimedEntry(value));
48 | }
49 |
50 | /**
51 | * Access underlying map, use with care
52 | *
53 | * @return
54 | */
55 | public Map> getMap() {
56 | return cache;
57 | }
58 |
59 | @FunctionalInterface
60 | public interface CheckedFunction {
61 | R apply(T t) throws Exception;
62 | }
63 |
64 | public class TimedEntry {
65 | private final E value;
66 | private final long timestamp;
67 |
68 | public TimedEntry(E value) {
69 | this.value = value;
70 | this.timestamp = System.currentTimeMillis();
71 | }
72 |
73 | public E get() {
74 | return value;
75 | }
76 |
77 | public boolean isExpired() {
78 | return (timestamp + validity) < System.currentTimeMillis();
79 | }
80 | }
81 |
82 | }
83 |
--------------------------------------------------------------------------------
/configsource-consul/README.md:
--------------------------------------------------------------------------------
1 | [Back to config-ext](https://github.com/microprofile-extensions/config-ext/blob/master/README.md)
2 |
3 | # Consul Config Source
4 |
5 | [](https://maven-badges.herokuapp.com/maven-central/org.microprofile-ext.config-ext/configsource-consul)
6 | [](https://www.javadoc.io/doc/org.microprofile-ext.config-ext/configsource-consul)
7 |
8 | Use [consul](https://consul.io/) to get config values. You can define the server details of the consul server using MicroProfile Config.
9 | Values are cached to reduce calls to consul. This config source does not support config events (yet).
10 |
11 | ## Usage
12 |
13 | ```xml
14 |
15 |
16 | org.microprofile-ext.config-ext
17 | configsource-consul
18 | XXXXXX
19 | runtime
20 |
21 |
22 | ```
23 |
24 | ## Configure options
25 |
26 | configsource.consul.host (defaults to localhost)
27 | configsource.consul.prefix (default no prefix)
28 | configsource.consul.validity (default 30s)
29 |
30 |
31 | You can disable the config source by setting this config:
32 |
33 | ConsulConfigSource.enabled=false
34 |
35 | ## Links
36 | * https://github.com/rikcarve/consulkv-maven-plugin
37 | * https://microprofile.io/project/eclipse/microprofile-config
38 |
--------------------------------------------------------------------------------
/configsource-consul/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 4.0.0
4 |
5 | org.microprofile-ext
6 | config-ext
7 | 3.0.2-SNAPSHOT
8 |
9 |
10 | org.microprofile-ext.config-ext
11 | configsource-consul
12 |
13 | Microprofile Config Extensions :: Consul config source
14 | A config that gets the values from an Consul server
15 |
16 |
17 | 5.9.3
18 |
19 |
20 |
21 |
22 | com.ecwid.consul
23 | consul-api
24 | 1.4.5
25 |
26 |
27 | ${project.groupId}
28 | configsource-base
29 | ${project.version}
30 |
31 |
32 |
33 | org.junit.jupiter
34 | junit-jupiter
35 | ${junit.jupiter.version}
36 | test
37 |
38 |
39 | org.mockito
40 | mockito-core
41 | 5.3.1
42 | test
43 |
44 |
45 | org.mock-server
46 | mockserver-junit-jupiter
47 | 5.15.0
48 | test
49 |
50 |
51 |
52 |
53 |
--------------------------------------------------------------------------------
/configsource-consul/src/main/java/org/microprofileext/config/source/consul/Configuration.java:
--------------------------------------------------------------------------------
1 | package org.microprofileext.config.source.consul;
2 |
3 | import org.eclipse.microprofile.config.Config;
4 | import org.eclipse.microprofile.config.spi.ConfigProviderResolver;
5 |
6 | public class Configuration {
7 |
8 | private Config config = ConfigProviderResolver.instance()
9 | .getBuilder()
10 | .addDefaultSources()
11 | .build();
12 |
13 | private String consulHost = getConfigValue("configsource.consul.host", "");
14 | private String consulHostList = getConfigValue("configsource.consul.hosts", "");
15 | private int consulPort = Integer.valueOf(getConfigValue("configsource.consul.port", "8500"));
16 | private long validity = Long.valueOf(getConfigValue("configsource.consul.validity", "30")) * 1000L;
17 | private String prefix = addSlash(getConfigValue("configsource.consul.prefix", ""));
18 | private String token = getConfigValue("configsource.consul.token", null);
19 | private boolean listAll = Boolean.valueOf(getConfigValue("configsource.consul.list-all", "false"));
20 |
21 | public long getValidity() {
22 | return validity;
23 | }
24 |
25 | public String getPrefix() {
26 | return prefix;
27 | }
28 |
29 | public String getToken() {
30 | return token;
31 | }
32 |
33 | public String getConsulHost() {
34 | return consulHost;
35 | }
36 |
37 | public String getConsulHostList() {
38 | return consulHostList;
39 | }
40 |
41 | public int getConsulPort() {
42 | return consulPort;
43 | }
44 |
45 | public boolean listAll() {
46 | return listAll;
47 | }
48 |
49 | private String getConfigValue(String key, String defaultValue) {
50 | return config.getOptionalValue(key, String.class).orElse(defaultValue);
51 | }
52 |
53 | private String addSlash(String envOrSystemProperty) {
54 | return envOrSystemProperty.isEmpty() ? "" : envOrSystemProperty + "/";
55 | }
56 |
57 | }
58 |
--------------------------------------------------------------------------------
/configsource-consul/src/main/java/org/microprofileext/config/source/consul/ConsulClientWrapper.java:
--------------------------------------------------------------------------------
1 | package org.microprofileext.config.source.consul;
2 |
3 | import com.ecwid.consul.v1.ConsulClient;
4 | import com.ecwid.consul.v1.Response;
5 | import com.ecwid.consul.v1.kv.model.GetValue;
6 |
7 | import java.io.UnsupportedEncodingException;
8 | import java.net.URLEncoder;
9 | import java.nio.charset.StandardCharsets;
10 | import java.util.AbstractMap.SimpleEntry;
11 | import java.util.Arrays;
12 | import java.util.List;
13 | import java.util.Map.Entry;
14 | import java.util.function.Supplier;
15 | import java.util.logging.Level;
16 | import java.util.logging.Logger;
17 | import java.util.stream.Collectors;
18 |
19 | public class ConsulClientWrapper {
20 | private static final Logger log = Logger.getLogger(ConsulClientWrapper.class.getName());
21 |
22 | private String host;
23 | private List peers = null;
24 | private int port;
25 | private String token;
26 | ConsulClient client = null;
27 |
28 | public ConsulClientWrapper(String host, String hosts, int port, String token) {
29 | this.host = host;
30 | if (hosts != null && !hosts.isEmpty()) {
31 | peers = Arrays.asList(hosts.split(","));
32 | }
33 | this.port = port;
34 | this.token = token;
35 | }
36 |
37 | public ConsulClient getClient() {
38 | initConsulClient();
39 | return client;
40 | }
41 |
42 | public String getValue(String key) {
43 | try {
44 | String encodedKey = URLEncoder.encode(key, StandardCharsets.UTF_8.name());
45 | GetValue value = retry(2, () -> getClient().getKVValue(encodedKey, token).getValue(), () -> forceReconnect());
46 | return value == null ? null : value.getDecodedValue();
47 | } catch (UnsupportedEncodingException e) {
48 | throw new RuntimeException(e);
49 | } catch (Exception e) {
50 | forceReconnect();
51 | throw e;
52 | }
53 | }
54 |
55 | public List> getKeyValuePairs(String prefix) {
56 | try {
57 | String encodedPrefix = URLEncoder.encode(prefix, StandardCharsets.UTF_8.name());
58 | List values = retry(2, () -> getClient().getKVValues(encodedPrefix, token).getValue(), () -> forceReconnect());
59 | return values.stream()
60 | .map(v -> new SimpleEntry(v.getKey(), v.getDecodedValue()))
61 | .collect(Collectors.toList());
62 | } catch (UnsupportedEncodingException e) {
63 | throw new RuntimeException(e);
64 | } catch (Exception e) {
65 | forceReconnect();
66 | throw e;
67 | }
68 | }
69 |
70 | void initConsulClient() {
71 | if (peers == null) {
72 | client = new ConsulClient(host, port);
73 | peers = client.getStatusPeers().getValue().stream()
74 | .map(s -> s.split(":")[0])
75 | .collect(Collectors.toList());
76 | }
77 | if (client == null) {
78 | client = getClientToAnyConsulHost();
79 | }
80 | }
81 |
82 | private ConsulClient getClientToAnyConsulHost() {
83 | return peers.stream()
84 | .map(host -> new ConsulClient(host, port))
85 | .filter(this::isConsulReachable)
86 | .findAny()
87 | .orElseThrow(() -> new RuntimeException("No Consul host could be reached."));
88 | }
89 |
90 | private boolean isConsulReachable(ConsulClient client) {
91 | try {
92 | Response leader = client.getStatusLeader();
93 | log.log(Level.INFO, () -> "Successfully established connection to Consul. Current cluster leader is " + leader.getValue());
94 | } catch (Exception e) {
95 | log.log(Level.INFO, () -> "Could not establish connection to consul: " + e.getMessage());
96 | return false;
97 | }
98 | return true;
99 | }
100 |
101 | private T retry(int maxRetries, Supplier supplier, Runnable onFailedAttempt) {
102 | int retries = 0;
103 | RuntimeException lastException = null;
104 | while (retries <= maxRetries) {
105 | try {
106 | return supplier.get();
107 | } catch (RuntimeException e) {
108 | lastException = e;
109 | onFailedAttempt.run();
110 | retries++;
111 | }
112 | }
113 | throw lastException;
114 | }
115 |
116 | private void forceReconnect() {
117 | client = null;
118 | }
119 |
120 | }
121 |
--------------------------------------------------------------------------------
/configsource-consul/src/main/java/org/microprofileext/config/source/consul/ConsulConfigSource.java:
--------------------------------------------------------------------------------
1 | package org.microprofileext.config.source.consul;
2 |
3 | import org.microprofileext.config.source.base.EnabledConfigSource;
4 | import org.microprofileext.config.source.base.ExpiringMap;
5 |
6 | import java.util.List;
7 | import java.util.Map;
8 | import java.util.Map.Entry;
9 | import java.util.Optional;
10 | import java.util.logging.Level;
11 | import java.util.logging.Logger;
12 | import java.util.stream.Collectors;
13 |
14 | public class ConsulConfigSource extends EnabledConfigSource {
15 |
16 | private static final Logger log = Logger.getLogger(ConsulConfigSource.class.getName());
17 |
18 | private static final String DEFAULT_CONSUL_CONFIGSOURCE_ORDINAL = "550";
19 |
20 | Configuration config = new Configuration();
21 | ExpiringMap cache = new ExpiringMap<>(config.getValidity());
22 | boolean isDisabled = config.getConsulHost().isEmpty() && config.getConsulHostList().isEmpty();
23 | ConsulClientWrapper client = new ConsulClientWrapper(config.getConsulHost(), config.getConsulHostList(), config.getConsulPort(), config.getToken());
24 |
25 | public ConsulConfigSource() {
26 | log.info("Loading [consul] MicroProfile ConfigSource");
27 | super.initOrdinal(550);
28 | }
29 |
30 | @Override
31 | public Map getPropertiesIfEnabled() {
32 | // only query for values if explicitly enabled
33 | if (!isDisabled && config.listAll()) {
34 | List> values = client.getKeyValuePairs(config.getPrefix());
35 | values.forEach(v -> cache.put(v.getKey(), v.getValue()));
36 | }
37 | return cache.getMap().entrySet()
38 | .stream()
39 | .filter(e -> e.getValue().get() != null)
40 | .collect(Collectors.toMap(
41 | e -> e.getKey(),
42 | e -> e.getValue().get()));
43 | }
44 |
45 | @Override
46 | public String getValue(String propertyName) {
47 | if (isDisabled) {
48 | return null;
49 | }
50 | String value = cache.getOrCompute(propertyName,
51 | p -> client.getValue(config.getPrefix() + propertyName),
52 | p -> log.log(Level.FINE, () -> "consul getKV failed for key " + p));
53 | // use default if config_ordinal not found
54 | if (CONFIG_ORDINAL.equals(propertyName)) {
55 | return Optional.ofNullable(value).orElse(DEFAULT_CONSUL_CONFIGSOURCE_ORDINAL);
56 | }
57 | return value;
58 | }
59 |
60 | @Override
61 | public String getName() {
62 | return "ConsulConfigSource";
63 | }
64 |
65 | @Override
66 | public int getOrdinal() {
67 | return getConfig().getOptionalValue(CONFIG_ORDINAL, Integer.class).orElse(550);
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/configsource-consul/src/main/resources/META-INF/services/org.eclipse.microprofile.config.spi.ConfigSource:
--------------------------------------------------------------------------------
1 | org.microprofileext.config.source.consul.ConsulConfigSource
--------------------------------------------------------------------------------
/configsource-consul/src/test/java/org/microprofileext/config/source/consul/ConfigurationTest.java:
--------------------------------------------------------------------------------
1 | package org.microprofileext.config.source.consul;
2 |
3 | import org.junit.jupiter.api.Test;
4 |
5 | import static org.junit.jupiter.api.Assertions.assertEquals;
6 | import static org.junit.jupiter.api.Assertions.assertNull;
7 |
8 | public class ConfigurationTest {
9 |
10 | @Test
11 | public void testGetValidity() throws Exception {
12 | Configuration config = new Configuration();
13 | assertEquals(30000, config.getValidity());
14 | }
15 |
16 | @Test
17 | public void testGetValidity_fromSys() throws Exception {
18 | System.setProperty("configsource.consul.validity", "10");
19 | Configuration config = new Configuration();
20 | assertEquals(10000, config.getValidity());
21 | System.clearProperty("configsource.consul.validity");
22 | }
23 |
24 | @Test
25 | public void testGetPrefix() throws Exception {
26 | Configuration config = new Configuration();
27 | assertEquals("", config.getPrefix());
28 | }
29 |
30 | @Test
31 | public void testGetPrefix_withSlash() throws Exception {
32 | System.setProperty("configsource.consul.prefix", "jax");
33 | Configuration config = new Configuration();
34 | assertEquals("jax/", config.getPrefix());
35 | System.clearProperty("configsource.consul.prefix");
36 | }
37 |
38 | @Test
39 | public void testGetPrefix_withSubstitution() throws Exception {
40 | System.setProperty("configsource.consul.prefix", "applications/${appname}");
41 | System.setProperty("appname", "jax");
42 | Configuration config = new Configuration();
43 | assertEquals("applications/jax/", config.getPrefix());
44 | System.clearProperty("configsource.consul.prefix");
45 | System.clearProperty("appName");
46 | }
47 |
48 | @Test
49 | public void testGetConsulHost() throws Exception {
50 | Configuration config = new Configuration();
51 | assertEquals("", config.getConsulHost());
52 | }
53 |
54 | @Test
55 | public void testGetConsulHost_fromSys() throws Exception {
56 | System.setProperty("configsource.consul.host", "jax");
57 | Configuration config = new Configuration();
58 | assertEquals("jax", config.getConsulHost());
59 | System.clearProperty("configsource.consul.host");
60 | }
61 |
62 | @Test
63 | public void testGetToken() throws Exception {
64 | System.setProperty("configsource.consul.token", "token");
65 | Configuration config = new Configuration();
66 | assertEquals("token", config.getToken());
67 | System.clearProperty("configsource.consul.token");
68 | }
69 |
70 | @Test
71 | public void testGetToken_not_configured() throws Exception {
72 | Configuration config = new Configuration();
73 | assertNull(config.getToken());
74 | }
75 |
76 | @Test
77 | public void testGetConsulHost_fromSys_withSubstitution() throws Exception {
78 | System.setProperty("configsource.consul.host", "${docker.host}");
79 | System.setProperty("docker.host", "sub");
80 | Configuration config = new Configuration();
81 | assertEquals("sub", config.getConsulHost());
82 | System.clearProperty("configsource.consul.host");
83 | System.clearProperty("docker.host");
84 | }
85 |
86 | }
87 |
--------------------------------------------------------------------------------
/configsource-consul/src/test/java/org/microprofileext/config/source/consul/ConsulClientWrapperTest.java:
--------------------------------------------------------------------------------
1 | package org.microprofileext.config.source.consul;
2 |
3 | import com.ecwid.consul.v1.OperationException;
4 | import org.apache.http.HttpStatus;
5 | import org.junit.jupiter.api.BeforeEach;
6 | import org.junit.jupiter.api.Test;
7 | import org.junit.jupiter.api.extension.ExtendWith;
8 | import org.mockserver.integration.ClientAndServer;
9 | import org.mockserver.junit.jupiter.MockServerExtension;
10 |
11 | import java.util.List;
12 | import java.util.Map.Entry;
13 |
14 | import static org.junit.jupiter.api.Assertions.*;
15 | import static org.mockserver.model.HttpRequest.request;
16 | import static org.mockserver.model.HttpResponse.response;
17 |
18 | @ExtendWith(MockServerExtension.class)
19 | public class ConsulClientWrapperTest {
20 |
21 | ConsulClientWrapper clientWrapper;
22 |
23 | ClientAndServer clientServer;
24 |
25 | @BeforeEach
26 | public void init(ClientAndServer client) {
27 | clientServer = client;
28 | clientServer.reset();
29 | clientServer.when(request().withPath("/v1/status/leader")).respond(response().withBody("localhost"));
30 | clientServer.when(request().withPath("/v1/status/peers")).respond(response().withBody("[\"localhost:8300\"]"));
31 | clientServer.when(request().withPath("/v1/kv/test")).respond(response().withBody("[{\"LockIndex\":0,\"Key\":\"test\",\"Flags\":0,\"Value\":\"aGVsbG8=\",\"CreateIndex\":1,\"ModifyIndex\":2}]"));
32 | clientServer.when(request().withPath("/v1/kv/testFromToken").withQueryStringParameter("token", "tokenValue")).respond(response().withBody("[{\"LockIndex\":0,\"Key\":\"testFromToken\",\"Flags\":0,\"Value\":\"aGVsbG8=\",\"CreateIndex\":1,\"ModifyIndex\":2}]"));
33 | clientServer.when(request().withPath("/v1/kv/%25dev.property.somevalue.%22com.test.app.config%22")).respond(response().withBody("[{\"LockIndex\":0,\"Key\":\"testFromToken\",\"Flags\":0,\"Value\":\"aGVsbG8=\",\"CreateIndex\":1,\"ModifyIndex\":2}]"));
34 | clientServer.when(request().withPath("/v1/kv/myapp")).respond(response().withBody("[{\"LockIndex\":0,\"Key\":\"test\",\"Flags\":0,\"Value\":\"aGVsbG8=\",\"CreateIndex\":1,\"ModifyIndex\":2}]"));
35 | clientWrapper = new ConsulClientWrapper("localhost", null, clientServer.getLocalPort(), null);
36 | }
37 |
38 | @Test
39 | public void testGetClient_singleHost() {
40 | assertNotNull(clientWrapper.getClient());
41 | }
42 |
43 | @Test
44 | public void testGetClient_peers() {
45 | clientWrapper = new ConsulClientWrapper(null, "localhost", clientServer.getLocalPort(), null);
46 | assertNotNull(clientWrapper.getClient());
47 | }
48 |
49 | @Test
50 | public void testGetClient_hosts_1stnotAvail() {
51 | clientWrapper = new ConsulClientWrapper(null, "localhost2,localhost", clientServer.getLocalPort(), null);
52 | assertNotNull(clientWrapper.getClient());
53 | }
54 |
55 | @Test
56 | public void testGetValue_with_token_found() {
57 | clientWrapper = new ConsulClientWrapper("localhost", null, clientServer.getLocalPort(), "tokenValue");
58 | String value = clientWrapper.getValue("testFromToken");
59 | assertEquals("hello", value);
60 | }
61 |
62 | @Test
63 | public void testGetValue_found() {
64 | String value = clientWrapper.getValue("test");
65 | assertEquals("hello", value);
66 | }
67 |
68 | @Test
69 | public void testGetValue_keys_that_need_url_encoding() {
70 | String value = clientWrapper.getValue("%dev.property.somevalue.\"com.test.app.config\"");
71 | assertEquals("hello", value);
72 | }
73 |
74 | @Test
75 | public void testGetValue_not_found() {
76 | String value = clientWrapper.getValue("nope");
77 | assertNull(value);
78 | }
79 |
80 | @Test
81 | public void testGetKeyValuePairs() {
82 | List> value = clientWrapper.getKeyValuePairs("myapp");
83 | assertEquals(1, value.size());
84 | }
85 |
86 | @Test
87 | public void testGetValue_force_reconnect() {
88 | clientServer.clear(request().withPath("/v1/kv/test"));
89 | clientServer.when(request().withPath("/v1/kv/test")).respond(response().withStatusCode(HttpStatus.SC_SERVICE_UNAVAILABLE));
90 | assertThrows(OperationException.class, () -> clientWrapper.getValue("test"));
91 | clientServer.clear(request().withPath("/v1/kv/test"));
92 | clientServer.when(request().withPath("/v1/kv/test")).respond(response().withBody("[{\"LockIndex\":0,\"Key\":\"test\",\"Flags\":0,\"Value\":\"aGVsbG8=\",\"CreateIndex\":1,\"ModifyIndex\":2}]"));
93 | String value = clientWrapper.getValue("test");
94 | assertEquals("hello", value);
95 | }
96 |
97 | }
98 |
--------------------------------------------------------------------------------
/configsource-consul/src/test/java/org/microprofileext/config/source/consul/ConsulConfigSourceTest.java:
--------------------------------------------------------------------------------
1 | package org.microprofileext.config.source.consul;
2 |
3 | import org.junit.jupiter.api.BeforeEach;
4 | import org.junit.jupiter.api.Test;
5 |
6 | import java.util.AbstractMap.SimpleEntry;
7 | import java.util.Arrays;
8 |
9 | import static org.junit.jupiter.api.Assertions.*;
10 | import static org.mockito.ArgumentMatchers.anyString;
11 | import static org.mockito.Mockito.*;
12 |
13 | class ConsulConfigSourceTest {
14 |
15 | private ConsulConfigSource configSource;
16 |
17 | @BeforeEach
18 | public void init() {
19 | System.setProperty("configsource.consul.host", "localhost");
20 | configSource = new ConsulConfigSource();
21 | configSource.config = new Configuration();
22 | configSource.client = mock(ConsulClientWrapper.class);
23 | }
24 |
25 | @Test
26 | void testGetName() {
27 | assertEquals("ConsulConfigSource", configSource.getName());
28 | }
29 |
30 | @Test
31 | void testGetProperties_empty() {
32 | ConsulConfigSource configSource = new ConsulConfigSource();
33 | assertTrue(configSource.getProperties().isEmpty());
34 | }
35 |
36 | @Test
37 | void testGetProperties_one_from_cache() {
38 | when(configSource.client.getValue(anyString())).thenReturn("hello");
39 | configSource.getValue("test");
40 | assertEquals(1, configSource.getProperties().size());
41 | }
42 |
43 | @Test
44 | void testGetProperties_from_consul() {
45 | System.setProperty("configsource.consul.list-all", "true");
46 | configSource.config = new Configuration();
47 | when(configSource.client.getKeyValuePairs(anyString())).thenReturn(Arrays.asList(new SimpleEntry("test", "hello")));
48 | assertEquals(1, configSource.getProperties().size());
49 | System.clearProperty("configsource.consul.list-all");
50 | }
51 |
52 | @Test
53 | void testGetProperties_with_null() {
54 | when(configSource.client.getValue(anyString())).thenReturn(null);
55 | configSource.getValue("test");
56 | assertEquals(0, configSource.getProperties().size());
57 | }
58 |
59 | @Test
60 | void testGetValue_disabled_return_null() {
61 | System.clearProperty("configsource.consul.host");
62 | assertNull(configSource.getValue("test"));
63 | }
64 |
65 | @Test
66 | void testGetValue_null() {
67 | when(configSource.client.getValue(anyString())).thenReturn(null);
68 | assertNull(configSource.getValue("test"));
69 | }
70 |
71 | @Test
72 | void testGetValue() {
73 | when(configSource.client.getValue(anyString())).thenReturn("hello");
74 | assertEquals("hello", configSource.getValue("test"));
75 | }
76 |
77 | @Test
78 | void testGetValue_token_configured() {
79 | System.setProperty("configsource.consul.token", "token");
80 | configSource.config = new Configuration();
81 | when(configSource.client.getValue(anyString())).thenReturn("hello");
82 | assertEquals("hello", configSource.getValue("test"));
83 | System.clearProperty("configsource.consul.token");
84 | }
85 |
86 | @Test
87 | void testGetValue_cache() {
88 | when(configSource.client.getValue(anyString())).thenReturn("hello");
89 | configSource.getValue("test");
90 | configSource.getValue("test");
91 | verify(configSource.client, times(1)).getValue(anyString());
92 | }
93 |
94 | @Test
95 | void testGetValue_exception() {
96 | when(configSource.client.getValue(anyString())).thenThrow(RuntimeException.class);
97 | assertNull(configSource.getValue("test"));
98 | }
99 |
100 | @Test
101 | void testOrdinal_default() {
102 | when(configSource.client.getValue(anyString())).thenReturn(null);
103 | assertEquals(550, configSource.getOrdinal());
104 | }
105 |
106 | //@Test
107 | //void testOrdinal_overwrite() {
108 | // when(configSource.client.getValue(anyString())).thenReturn("200");
109 | // assertEquals(200, configSource.getOrdinal());
110 | //}
111 |
112 | }
113 |
--------------------------------------------------------------------------------
/configsource-db/README.md:
--------------------------------------------------------------------------------
1 | [Back to config-ext](https://github.com/microprofile-extensions/config-ext/blob/master/README.md)
2 |
3 | # Database config source
4 |
5 | [](https://maven-badges.herokuapp.com/maven-central/org.microprofile-ext.config-ext/configsource-db)
6 | [](https://www.javadoc.io/doc/org.microprofile-ext.config-ext/configsource-db)
7 |
8 | An Eclipse MicroProfile config extension which uses a database as source.
9 |
10 | ## Overview
11 | The eclipse microprofile config framework is a simple yet powerful configuration framework for Java EE. But most implementations only provide the system/env properties or property files as configuration source. This small library provides an ConfigSource implementation which reads the values from the default datasource. For performance reasons, the config values are cached.
12 |
13 | > This config source expects JNDI & JDBC to be available. The datasource are looked up
14 |
15 | ## Usage
16 | ```xml
17 |
18 | org.microprofile-ext.config-ext
19 | configsource-db
20 | 1.x
21 |
22 | ```
23 |
24 | ## Configuration
25 | Currently there are 5 values you can configure, either through Java system properties or environment variables:
26 | * **configsource.db.datasource** override ee default datasource by setting JNDI name of the datasource
27 | * **configsource.db.table** table name for configuration records, default value is "configuration"
28 | * **configsource.db.key-column** name of the column containing the key, default value is "key"
29 | * **configsource.db.value-column** name of the column containing the value, default value is "value"
30 | * **configsource.db.validity** how long to cache values (in seconds), default is 30s
31 |
32 | ### Events
33 |
34 | This config source fires CDI Events on changes
35 |
36 | Read more about [Config Events](https://github.com/microprofile-extensions/config-ext/blob/master/config-events/README.md)
37 |
38 | You can disable this with the `configsource.db.notifyOnChanges` property:
39 |
40 | configsource.db.notifyOnChanges=false
41 |
42 | ## Links
43 | * https://microprofile.io/project/eclipse/microprofile-config
44 | * https://github.com/rikcarve/consulkv-maven-plugin
45 | * https://github.com/rikcarve/mp-config-consul
46 | * https://github.com/microprofile-extensions
47 |
48 |
--------------------------------------------------------------------------------
/configsource-db/pom.xml:
--------------------------------------------------------------------------------
1 |
2 | 4.0.0
3 |
4 | org.microprofile-ext
5 | config-ext
6 | 3.0.2-SNAPSHOT
7 |
8 |
9 | org.microprofile-ext.config-ext
10 | configsource-db
11 |
12 | Microprofile Config Extensions :: Database config source
13 | A config that get the values from a datasource
14 |
15 |
16 | 5.9.3
17 |
18 |
19 |
20 |
21 | ${project.groupId}
22 | configsource-base
23 | ${project.version}
24 |
25 |
26 |
27 | org.junit.jupiter
28 | junit-jupiter
29 | ${junit.jupiter.version}
30 | test
31 |
32 |
33 | org.mockito
34 | mockito-core
35 | 5.3.1
36 | test
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 | org.apache.maven.plugins
45 | maven-surefire-plugin
46 | 3.1.0
47 |
48 |
49 | org.jacoco
50 | jacoco-maven-plugin
51 | 0.8.10
52 |
53 |
54 |
55 | prepare-agent
56 |
57 |
58 |
59 | report
60 | test
61 |
62 | report
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
--------------------------------------------------------------------------------
/configsource-db/src/main/java/org/microprofileext/config/source/db/DatasourceConfigSource.java:
--------------------------------------------------------------------------------
1 | package org.microprofileext.config.source.db;
2 |
3 | import org.eclipse.microprofile.config.Config;
4 | import org.microprofileext.config.source.base.EnabledConfigSource;
5 | import org.microprofileext.config.source.base.ExpiringMap;
6 |
7 | import java.sql.SQLException;
8 | import java.util.HashMap;
9 | import java.util.Map;
10 | import java.util.logging.Logger;
11 |
12 | public class DatasourceConfigSource extends EnabledConfigSource {
13 |
14 | private static final Logger log = Logger.getLogger(DatasourceConfigSource.class.getName());
15 |
16 | private static final String VALIDITY_KEY = "configsource.db.validity";
17 | private static final String CONFIGSOURCE_ORDINAL_KEY = "configsource.db.ordinal";
18 |
19 | private Config config;
20 | Repository repository = null;
21 | ExpiringMap cache = null;
22 |
23 | public DatasourceConfigSource() {
24 | config = getConfig();
25 | log.info("Loading [db] MicroProfile ConfigSource");
26 | super.initOrdinal(450);
27 | }
28 |
29 | @Override
30 | protected Map getPropertiesIfEnabled() {
31 | initRepository();
32 | try {
33 | return repository.getAllConfigValues();
34 | } catch (SQLException e) {
35 | log.info("query failed: " + e.getMessage());
36 | clearRepository();
37 | }
38 | return new HashMap<>();
39 | }
40 |
41 | @Override
42 | public String getValue(String propertyName) {
43 | initRepository();
44 | initCache();
45 |
46 | return cache.getOrCompute(propertyName, p -> repository.getConfigValue(p), (e) -> clearRepository());
47 | }
48 |
49 | @Override
50 | public String getName() {
51 | return "DatasourceConfigSource";
52 | }
53 |
54 | @Override
55 | public int getOrdinal() {
56 | return config.getOptionalValue(CONFIGSOURCE_ORDINAL_KEY, Integer.class).orElse(450);
57 | }
58 |
59 | private void initRepository() {
60 | if (repository == null) {
61 | // late initialization is needed because of the EE datasource.
62 | repository = new Repository(config);
63 | }
64 | }
65 |
66 | void clearRepository() {
67 | repository = null;
68 | }
69 |
70 | private void initCache() {
71 | if (cache == null) {
72 | long validity = config.getOptionalValue(VALIDITY_KEY, Long.class).orElse(30000L);
73 | cache = new ExpiringMap<>(validity);
74 | }
75 | }
76 |
77 | }
78 |
--------------------------------------------------------------------------------
/configsource-db/src/main/java/org/microprofileext/config/source/db/Repository.java:
--------------------------------------------------------------------------------
1 | package org.microprofileext.config.source.db;
2 |
3 | import java.sql.PreparedStatement;
4 | import java.sql.ResultSet;
5 | import java.sql.SQLException;
6 | import java.util.HashMap;
7 | import java.util.Map;
8 | import java.util.logging.Level;
9 | import java.util.logging.Logger;
10 |
11 | import javax.naming.InitialContext;
12 | import javax.naming.NamingException;
13 | import javax.sql.DataSource;
14 |
15 | import org.eclipse.microprofile.config.Config;
16 |
17 | public class Repository {
18 | private static final Logger log = Logger.getLogger(Repository.class.getName());
19 |
20 | private static final String KEY_PREFIX = "configsource.db.";
21 | private static final String KEY_DATASOURCE = KEY_PREFIX + "datasource";
22 | private static final String KEY_TABLE = KEY_PREFIX + "table";
23 | private static final String KEY_KEY_COLUMN = KEY_PREFIX + "key-column";
24 | private static final String KEY_VALUE_COLUMN = KEY_PREFIX + "value-column";
25 |
26 | PreparedStatement selectOne = null;
27 | PreparedStatement selectAll = null;
28 |
29 | public Repository(Config config) {
30 | DataSource datasource = getDatasource(config.getOptionalValue(KEY_DATASOURCE, String.class).orElse("java:comp/DefaultDataSource"));
31 | String table = config.getOptionalValue(KEY_TABLE, String.class).orElse("configuration");
32 | String keyColumn = config.getOptionalValue(KEY_KEY_COLUMN, String.class).orElse("key");
33 | String valueColumn = config.getOptionalValue(KEY_VALUE_COLUMN, String.class).orElse("value");
34 | if (datasource != null) {
35 | String queryOne = "select " + valueColumn + " from " +table + " where " + keyColumn + " = ?";
36 | String queryAll = "select " + keyColumn + ", " + valueColumn + " from " + table;
37 | try {
38 | selectOne = datasource.getConnection().prepareStatement(queryOne);
39 | selectAll = datasource.getConnection().prepareStatement(queryAll);
40 | } catch (SQLException e) {
41 | log.log(Level.FINE, () -> "Configuration query could not be prepared: " + e.getMessage());
42 | }
43 | }
44 | }
45 |
46 | public synchronized Map getAllConfigValues() throws SQLException {
47 | Map result = new HashMap<>();
48 | if (selectAll != null) {
49 | try (ResultSet rs = selectAll.executeQuery()) {
50 | while (rs.next()) {
51 | result.put(rs.getString(1), rs.getString(2));
52 | }
53 | }
54 | }
55 | return result;
56 | }
57 |
58 | public synchronized String getConfigValue(String key) throws SQLException {
59 | if (selectOne != null) {
60 | selectOne.setString(1, key);
61 | try (ResultSet rs = selectOne.executeQuery()) {
62 | if (rs.next()) {
63 | return rs.getString(1);
64 | }
65 | }
66 | } else {
67 | throw new SQLException("datasource not initialized");
68 | }
69 | return null;
70 | }
71 |
72 | private DataSource getDatasource(String jndi) {
73 | try {
74 | return InitialContext.doLookup(jndi);
75 | } catch (NamingException e) {
76 | log.log(Level.WARNING, () -> "Could not get datasource: " + e.getMessage());
77 | return null;
78 | }
79 | }
80 |
81 | }
82 |
--------------------------------------------------------------------------------
/configsource-db/src/main/resources/META-INF/services/org.eclipse.microprofile.config.spi.ConfigSource:
--------------------------------------------------------------------------------
1 | org.microprofileext.config.source.db.DatasourceConfigSource
2 |
--------------------------------------------------------------------------------
/configsource-db/src/test/java/org/microprofileext/config/source/db/DatasourceConfigSourceTest.java:
--------------------------------------------------------------------------------
1 | package org.microprofileext.config.source.db;
2 |
3 | import org.junit.jupiter.api.BeforeEach;
4 | import org.junit.jupiter.api.Test;
5 | import org.microprofileext.config.source.base.ExpiringMap;
6 | import org.mockito.Mockito;
7 |
8 | import java.sql.SQLException;
9 | import java.util.Collections;
10 |
11 | import static org.junit.jupiter.api.Assertions.*;
12 | import static org.mockito.ArgumentMatchers.anyString;
13 | import static org.mockito.Mockito.*;
14 |
15 | class DatasourceConfigSourceTest {
16 |
17 | private DatasourceConfigSource configSource;
18 |
19 | @BeforeEach
20 | public void init() {
21 | configSource = Mockito.spy(new DatasourceConfigSource());
22 | configSource.repository = mock(Repository.class);
23 | configSource.cache = new ExpiringMap(30000);
24 | }
25 |
26 | @Test
27 | void testGetOrdinal() {
28 | assertTrue(configSource.getOrdinal() > 100);
29 | }
30 |
31 | @Test
32 | void testGetProperties_empty() {
33 | assertTrue(configSource.getProperties().isEmpty());
34 | }
35 |
36 | @Test
37 | public void testGetProperties_null() throws Exception {
38 | when(configSource.repository.getConfigValue(anyString())).thenReturn(null);
39 | configSource.getValue("test");
40 | assertTrue(configSource.getProperties().isEmpty());
41 | }
42 |
43 | @Test
44 | public void testGetProperties_one() throws Exception {
45 | when(configSource.repository.getAllConfigValues()).thenReturn(Collections.singletonMap("test", "value"));
46 | configSource.getValue("test");
47 | assertEquals(1, configSource.getProperties().size());
48 | }
49 |
50 | @Test
51 | public void testGetValue() throws Exception {
52 | when(configSource.repository.getConfigValue(anyString())).thenReturn("123");
53 | assertEquals("123", configSource.getValue("test"));
54 | }
55 |
56 | @Test
57 | public void testGetValue_cache() throws Exception {
58 | when(configSource.repository.getConfigValue(anyString())).thenReturn("123");
59 | configSource.getValue("test");
60 | configSource.getValue("test");
61 | verify(configSource.repository, times(1)).getConfigValue(anyString());
62 | }
63 |
64 | @Test
65 | public void testGetValue_exception() throws Exception {
66 | when(configSource.repository.getConfigValue(anyString())).thenThrow(SQLException.class);
67 | assertNull(configSource.getValue("test"));
68 | verify(configSource, times(1)).clearRepository();
69 | }
70 |
71 | @Test
72 | public void testGetValue_exception_cache() throws Exception {
73 |
74 | // negative value, otherwise two serial "getValue" will have the same timestamp
75 | // and the entry is not yet expired.
76 | configSource.cache = new ExpiringMap(-5);
77 |
78 | when(configSource.repository.getConfigValue(anyString())).thenReturn("123");
79 | assertEquals("123", configSource.getValue("test"));
80 | when(configSource.repository.getConfigValue(anyString())).thenThrow(SQLException.class);
81 | assertEquals("123", configSource.getValue("test")); // load from cache, also when the entry is already expired.
82 | verify(configSource, times(1)).clearRepository();
83 |
84 | }
85 |
86 | }
87 |
--------------------------------------------------------------------------------
/configsource-db/src/test/java/org/microprofileext/config/source/db/RepositoryTest.java:
--------------------------------------------------------------------------------
1 | package org.microprofileext.config.source.db;
2 |
3 | import org.eclipse.microprofile.config.Config;
4 | import org.junit.jupiter.api.BeforeEach;
5 | import org.junit.jupiter.api.Test;
6 | import org.mockito.Mockito;
7 |
8 | import java.sql.PreparedStatement;
9 | import java.sql.ResultSet;
10 | import java.sql.SQLException;
11 | import java.util.Optional;
12 |
13 | import static org.junit.jupiter.api.Assertions.*;
14 | import static org.mockito.Mockito.mock;
15 | import static org.mockito.Mockito.when;
16 |
17 | class RepositoryTest {
18 |
19 | Repository repository;
20 |
21 | @BeforeEach
22 | public void init() {
23 | Config config = mock(Config.class);
24 | when(config.getOptionalValue(Mockito.anyString(), Mockito.any())).thenReturn(Optional.empty());
25 | repository = new Repository(config);
26 | }
27 |
28 | @Test
29 | void testGetConfigValue_exception() throws SQLException {
30 | repository.selectOne = mock(PreparedStatement.class);
31 | when(repository.selectOne.executeQuery()).thenThrow(SQLException.class);
32 | assertThrows(SQLException.class, () -> repository.getConfigValue("test"));
33 | }
34 |
35 | @Test
36 | void testGetConfigValue_none() throws SQLException {
37 | repository.selectOne = mock(PreparedStatement.class);
38 | ResultSet rs = mock(ResultSet.class);
39 | when(rs.next()).thenReturn(false);
40 | when(repository.selectOne.executeQuery()).thenReturn(rs);
41 | assertNull(repository.getConfigValue("test"));
42 | }
43 |
44 | @Test
45 | void testGetConfigValue_no_stmt() throws SQLException {
46 | assertThrows(SQLException.class, () -> repository.getConfigValue("test"));
47 | }
48 |
49 | @Test
50 | void testGetConfigValue() throws SQLException {
51 | repository.selectOne = mock(PreparedStatement.class);
52 | ResultSet rs = mock(ResultSet.class);
53 | when(rs.next()).thenReturn(true);
54 | when(rs.getString(1)).thenReturn("value");
55 | when(repository.selectOne.executeQuery()).thenReturn(rs);
56 | assertEquals("value", repository.getConfigValue("test"));
57 | }
58 |
59 | @Test
60 | void testGetAllConfigValues_no_stmt() throws SQLException {
61 | assertEquals(0, repository.getAllConfigValues().size());
62 | }
63 |
64 | @Test
65 | void testGetAllConfigValues_exception() throws SQLException {
66 | repository.selectAll = mock(PreparedStatement.class);
67 | when(repository.selectAll.executeQuery()).thenThrow(SQLException.class);
68 | assertThrows(SQLException.class, () -> repository.getAllConfigValues().size());
69 | }
70 |
71 | @Test
72 | void testGetAllConfigValues() throws SQLException {
73 | repository.selectAll = mock(PreparedStatement.class);
74 | ResultSet rs = mock(ResultSet.class);
75 | when(rs.next()).thenReturn(true).thenReturn(false);
76 | when(rs.getString(1)).thenReturn("test");
77 | when(rs.getString(2)).thenReturn("value");
78 | when(repository.selectAll.executeQuery()).thenReturn(rs);
79 | assertEquals(1, repository.getAllConfigValues().size());
80 | }
81 |
82 | }
83 |
--------------------------------------------------------------------------------
/configsource-etcd/README.md:
--------------------------------------------------------------------------------
1 | [Back to config-ext](https://github.com/microprofile-extensions/config-ext/blob/master/README.md)
2 |
3 | # Etcd Config Source
4 |
5 | [](https://maven-badges.herokuapp.com/maven-central/org.microprofile-ext.config-ext/configsource-etcd)
6 | [](https://www.javadoc.io/doc/org.microprofile-ext.config-ext/configsource-etcd)
7 |
8 | Use [etcd](https://coreos.com/etcd/) to get config values. You can define the server details of the etcd server using MicroProfile Config:
9 |
10 | ## Usage
11 |
12 | ```xml
13 |
14 |
15 | org.microprofile-ext.config-ext
16 | configsource-etcd
17 | XXXXXX
18 | runtime
19 |
20 |
21 | ```
22 |
23 | ## Configure options
24 |
25 | configsource.etcd.scheme=http (default)
26 | configsource.etcd.host=localhost (default)
27 | configsource.etcd.port=2379 (default)
28 | configsource.etcd.user (default no user)
29 | configsource.etcd.password (default no password)
30 | configsource.etcd.authority (default no authority)
31 |
32 |
33 | You can disable the config source by setting this config:
34 |
35 | EtcdConfigSource.enabled=false
36 |
--------------------------------------------------------------------------------
/configsource-etcd/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 4.0.0
4 |
5 | org.microprofile-ext
6 | config-ext
7 | 3.0.2-SNAPSHOT
8 |
9 |
10 | org.microprofile-ext.config-ext
11 | configsource-etcd
12 |
13 | Microprofile Config Extensions :: Etcd config source
14 | A config that get the values from an Etcd server
15 |
16 |
17 |
18 |
19 | com.coreos
20 | jetcd-core
21 | 0.0.2
22 |
23 |
24 |
25 | ${project.groupId}
26 | configsource-base
27 | ${project.version}
28 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/configsource-etcd/src/main/java/org/microprofileext/config/source/etcd/EtcdConfigSource.java:
--------------------------------------------------------------------------------
1 | package org.microprofileext.config.source.etcd;
2 |
3 | import com.coreos.jetcd.Client;
4 | import com.coreos.jetcd.ClientBuilder;
5 | import com.coreos.jetcd.data.ByteSequence;
6 | import com.coreos.jetcd.data.KeyValue;
7 | import com.coreos.jetcd.kv.GetResponse;
8 | import java.util.HashMap;
9 | import java.util.List;
10 | import java.util.Map;
11 | import java.util.concurrent.CompletableFuture;
12 | import java.util.concurrent.ExecutionException;
13 | import java.util.logging.Level;
14 | import java.util.logging.Logger;
15 | import org.microprofileext.config.source.base.EnabledConfigSource;
16 |
17 | /**
18 | * Etcd config source
19 | * @author Phillip Kruger
20 | *
21 | * TODO: Add watcher
22 | * ByteSequence bsKey = ByteSequence.fromString(EMPTY);
23 | * Watch.Watcher watch = this.client.getWatchClient().watch(bsKey);
24 | * watch.listen();
25 | */
26 | public class EtcdConfigSource extends EnabledConfigSource {
27 |
28 | private static final Logger log = Logger.getLogger(EtcdConfigSource.class.getName());
29 |
30 | private static final String NAME = "EtcdConfigSource";
31 |
32 | private static final String KEY_PREFIX = "configsource.etcd.";
33 |
34 | private static final String KEY_SCHEME = KEY_PREFIX + "scheme";
35 | private static final String DEFAULT_SCHEME = "http";
36 |
37 | private static final String KEY_HOST = KEY_PREFIX + "host";
38 | private static final String DEFAULT_HOST = "localhost";
39 |
40 | private static final String KEY_PORT = KEY_PREFIX + "port";
41 | private static final Integer DEFAULT_PORT = 2379;
42 |
43 | private static final String KEY_USER = KEY_PREFIX + "user";
44 | private static final String KEY_PASSWORD = KEY_PREFIX + "password";
45 | private static final String KEY_AUTHORITY = KEY_PREFIX + "authority";
46 |
47 | private Client client = null;
48 |
49 | public EtcdConfigSource(){
50 | super.initOrdinal(320);
51 | }
52 |
53 | @Override
54 | public Map getPropertiesIfEnabled() {
55 | Map m = new HashMap<>();
56 |
57 | ByteSequence bsKey = ByteSequence.fromString(EMPTY);
58 |
59 | CompletableFuture getFuture = getClient().getKVClient().get(bsKey);
60 | try {
61 | GetResponse response = getFuture.get();
62 | List kvs = response.getKvs();
63 |
64 | for(KeyValue kv:kvs){
65 | String key = kv.getKey().toStringUtf8();
66 | String value = kv.getValue().toStringUtf8();
67 | m.put(key, value);
68 | }
69 | } catch (InterruptedException | ExecutionException ex) {
70 | log.log(Level.FINEST, "Can not get all config keys and values from etcd Config source: {1}", new Object[]{ex.getMessage()});
71 | }
72 |
73 | return m;
74 | }
75 |
76 | @Override
77 | public String getValue(String key) {
78 | if (key.startsWith(KEY_PREFIX)) {
79 | // in case we are about to configure ourselves we simply ignore that key
80 | return null;
81 | }
82 | if(super.isEnabled()){
83 | ByteSequence bsKey = ByteSequence.fromString(key);
84 | CompletableFuture getFuture = getClient().getKVClient().get(bsKey);
85 | try {
86 | GetResponse response = getFuture.get();
87 | String value = toString(response);
88 | return value;
89 | } catch (InterruptedException | ExecutionException ex) {
90 | log.log(Level.FINEST, "Can not get config value for [{0}] from etcd Config source: {1}", new Object[]{key, ex.getMessage()});
91 | }
92 | }
93 | return null;
94 | }
95 |
96 | @Override
97 | public String getName() {
98 | return NAME;
99 | }
100 |
101 | private String toString(GetResponse response){
102 | if(response.getCount()>0){
103 | return response.getKvs().get(0).getValue().toStringUtf8();
104 | }
105 | return null;
106 | }
107 |
108 | private Client getClient(){
109 | if(this.client == null ){
110 | log.info("Loading [etcd] MicroProfile ConfigSource");
111 |
112 | String scheme = getConfig().getOptionalValue(KEY_SCHEME, String.class).orElse(DEFAULT_SCHEME);
113 | String host = getConfig().getOptionalValue(KEY_HOST, String.class).orElse(DEFAULT_HOST);
114 | Integer port = getConfig().getOptionalValue(KEY_PORT, Integer.class).orElse(DEFAULT_PORT);
115 |
116 | String user = getConfig().getOptionalValue(KEY_USER, String.class).orElse(null);
117 | String password = getConfig().getOptionalValue(KEY_PASSWORD, String.class).orElse(null);
118 | String authority = getConfig().getOptionalValue(KEY_AUTHORITY, String.class).orElse(null);
119 |
120 | String endpoint = String.format("%s://%s:%d",scheme,host,port);
121 | log.log(Level.INFO, "Using [{0}] as etcd server endpoint", endpoint);
122 |
123 | ClientBuilder clientBuilder = Client.builder().endpoints(endpoint);
124 | if(user!=null){
125 | ByteSequence bsUser = ByteSequence.fromString(user);
126 | clientBuilder = clientBuilder.user(bsUser);
127 | }
128 | if(password!=null){
129 | ByteSequence bsPassword = ByteSequence.fromString(password);
130 | clientBuilder = clientBuilder.password(bsPassword);
131 | }
132 | if(authority!=null){
133 | clientBuilder = clientBuilder.authority(authority);
134 | }
135 |
136 | this.client = clientBuilder.build();
137 | }
138 | return this.client;
139 | }
140 |
141 | private static final String EMPTY = "";
142 |
143 | }
144 |
--------------------------------------------------------------------------------
/configsource-etcd/src/main/resources/META-INF/services/org.eclipse.microprofile.config.spi.ConfigSource:
--------------------------------------------------------------------------------
1 | org.microprofileext.config.source.etcd.EtcdConfigSource
--------------------------------------------------------------------------------
/configsource-filebase/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 4.0.0
4 |
5 | org.microprofile-ext
6 | config-ext
7 | 3.0.2-SNAPSHOT
8 |
9 |
10 | org.microprofile-ext.config-ext
11 | configsource-filebase
12 |
13 | Microprofile Config Extensions :: Filebase config source
14 | A config that provides default behavior for file based sources
15 |
16 |
17 |
18 | ${project.groupId}
19 | configsource-base
20 | ${project.version}
21 |
22 |
23 | ${project.groupId}
24 | config-events
25 | ${project.version}
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/configsource-filebase/src/main/java/org/microprofileext/config/source/base/file/FileResourceWatcher.java:
--------------------------------------------------------------------------------
1 | package org.microprofileext.config.source.base.file;
2 |
3 | import java.io.IOException;
4 | import java.net.MalformedURLException;
5 | import java.net.URISyntaxException;
6 | import java.net.URL;
7 | import java.nio.file.FileSystems;
8 | import java.nio.file.Path;
9 | import java.nio.file.Paths;
10 | import java.nio.file.StandardWatchEventKinds;
11 | import java.nio.file.WatchEvent;
12 | import java.nio.file.WatchKey;
13 | import java.nio.file.WatchService;
14 | import java.util.ArrayList;
15 | import java.util.HashMap;
16 | import java.util.List;
17 | import java.util.Map;
18 | import java.util.concurrent.Executors;
19 | import java.util.concurrent.ScheduledExecutorService;
20 | import java.util.concurrent.TimeUnit;
21 | import java.util.logging.Level;
22 | import java.util.logging.Logger;
23 |
24 | /**
25 | * Watching files for changes
26 | * @author Phillip Kruger
27 | */
28 | public class FileResourceWatcher {
29 |
30 | private static final Logger log = Logger.getLogger(FileResourceWatcher.class.getName());
31 |
32 | private WatchService watcher;
33 |
34 | private final Map directoryWatchers = new HashMap<>();
35 | private final Map> filterMap = new HashMap<>();
36 | private final long pollInterval;
37 | private final Reloadable reloadable;
38 |
39 | private final ScheduledExecutorService scheduledThreadPool = Executors.newSingleThreadScheduledExecutor();
40 |
41 | public FileResourceWatcher(Reloadable reloadable, long pollInterval){
42 | this.reloadable = reloadable;
43 | this.pollInterval = pollInterval;
44 |
45 | try {
46 | this.watcher = FileSystems.getDefault().newWatchService();
47 | } catch (IOException ex) {
48 | log.log(Level.SEVERE, null, ex);
49 | }
50 | }
51 |
52 | public void startWatching(URL url){
53 | try {
54 | Path path = Paths.get(url.toURI());
55 | Path dir = path.getParent();
56 | String filename = path.getFileName().toString();
57 | startWatching(dir,filename);
58 | } catch (URISyntaxException ex) {
59 | log.log(Level.WARNING, "Can not watch url [" + url +"]", ex);
60 | }
61 | }
62 |
63 | private void startWatching(Path path,String filter){
64 | if(filterMap.containsKey(path)){
65 | // Already watching this directory
66 | if(!filterMap.get(path).contains(filter))filterMap.get(path).add(filter);
67 | }else {
68 | // New folder to monitor
69 | List filters = new ArrayList<>();
70 | filters.add(filter);
71 | filterMap.put(path, filters);
72 |
73 | try {
74 | WatchKey key = path.register(watcher, StandardWatchEventKinds.ENTRY_MODIFY);
75 | directoryWatchers.put(key, path);
76 | // Here start Runable
77 | scheduledThreadPool.schedule(new Poller(),this.pollInterval,TimeUnit.SECONDS);
78 | } catch (IOException ex) {
79 | log.log(Level.WARNING, "Could not register directory [{0}] to watch for changes - {1}", new Object[]{path, ex.getMessage()});
80 | }
81 | }
82 | }
83 |
84 | @SuppressWarnings("unchecked")
85 | private WatchEvent cast(WatchEvent> event) {
86 | return (WatchEvent)event;
87 | }
88 |
89 | class Poller implements Runnable{
90 |
91 | @Override
92 | public void run() {
93 | WatchKey key;
94 | try {
95 | key = watcher.take();
96 | } catch (InterruptedException x) {
97 | return;
98 | }
99 |
100 | Path d = directoryWatchers.get(key);
101 | if (d != null) {
102 |
103 | for (WatchEvent> event: key.pollEvents()) {
104 | WatchEvent.Kind kind = event.kind();
105 | if (kind == StandardWatchEventKinds.OVERFLOW)continue;
106 |
107 | WatchEvent ev = cast(event);
108 | Path name = ev.context();
109 | List filters = filterMap.get(d);
110 |
111 | if(filters.contains(name.toString())){
112 | Path child = d.resolve(name);
113 | try {
114 | reloadable.reload(child.toUri().toURL());
115 | } catch (MalformedURLException ex) {
116 | log.log(Level.SEVERE, null, ex);
117 | }
118 | }
119 | }
120 | }
121 |
122 | boolean valid = key.reset();
123 | if (!valid)directoryWatchers.remove(key);
124 |
125 | this.run();
126 | }
127 | }
128 | }
--------------------------------------------------------------------------------
/configsource-filebase/src/main/java/org/microprofileext/config/source/base/file/Reloadable.java:
--------------------------------------------------------------------------------
1 | package org.microprofileext.config.source.base.file;
2 |
3 | import java.net.URL;
4 |
5 | /**
6 | * Marks a class as reloadable
7 | * @author Phillip Kruger
8 | */
9 | public interface Reloadable {
10 | public void reload(URL url);
11 | }
12 |
--------------------------------------------------------------------------------
/configsource-filebase/src/main/java/org/microprofileext/config/source/base/file/WebResourceWatcher.java:
--------------------------------------------------------------------------------
1 | package org.microprofileext.config.source.base.file;
2 |
3 | import java.io.IOException;
4 | import java.net.HttpURLConnection;
5 | import java.net.URL;
6 | import java.util.HashMap;
7 | import java.util.Map;
8 | import java.util.Set;
9 | import java.util.concurrent.Executors;
10 | import java.util.concurrent.ScheduledExecutorService;
11 | import java.util.concurrent.TimeUnit;
12 | import java.util.logging.Level;
13 | import java.util.logging.Logger;
14 |
15 | /**
16 | * Watching web resources for changes
17 | * @author Phillip Kruger
18 | */
19 | public class WebResourceWatcher {
20 |
21 | private static final Logger log = Logger.getLogger(WebResourceWatcher.class.getName());
22 |
23 | private final long pollInterval;
24 | private final Reloadable reloadable;
25 |
26 | private final ScheduledExecutorService scheduledThreadPool = Executors.newSingleThreadScheduledExecutor();
27 |
28 | private final Map urlsToWatch = new HashMap<>();
29 |
30 | public WebResourceWatcher(Reloadable reloadable, long pollInterval){
31 | this.reloadable = reloadable;
32 | this.pollInterval = pollInterval;
33 | scheduledThreadPool.scheduleAtFixedRate(new Poller(),this.pollInterval,this.pollInterval,TimeUnit.SECONDS);
34 | }
35 |
36 | public void startWatching(URL url){
37 | if(!urlsToWatch.containsKey(url)){
38 | long lastModified = getLastModified(url);
39 | if(lastModified>0){
40 | urlsToWatch.put(url,lastModified);
41 | }else{
42 | log.log(Level.WARNING, "Can not poll {0} for changes, lastModified not implemented", url);
43 | }
44 | }
45 | }
46 |
47 | private long getLastModified(URL url){
48 | HttpURLConnection con = null;
49 | try {
50 | con = (HttpURLConnection) url.openConnection();
51 | con.setRequestMethod(HEAD);
52 | con.setConnectTimeout(5000);
53 | con.setReadTimeout(5000);
54 | return con.getLastModified();
55 | } catch (IOException ex) {
56 | log.log(Level.SEVERE, ex.getMessage());
57 | } finally {
58 | if(con!=null)con.disconnect();
59 | }
60 |
61 | return -1;
62 | }
63 |
64 | private static final String HEAD = "HEAD";
65 |
66 | class Poller implements Runnable{
67 |
68 | @Override
69 | public void run() {
70 | Set urls = urlsToWatch.keySet();
71 | for(URL url : urls){
72 | long lastModified = getLastModified(url);
73 | if(lastModified!=urlsToWatch.get(url)){
74 | urlsToWatch.put(url, lastModified);
75 | reloadable.reload(url);
76 | }
77 | }
78 | }
79 | }
80 | }
--------------------------------------------------------------------------------
/configsource-json/README.md:
--------------------------------------------------------------------------------
1 | [Back to config-ext](https://github.com/microprofile-extensions/config-ext/blob/master/README.md)
2 |
3 | # Json Config Source
4 |
5 | [](https://maven-badges.herokuapp.com/maven-central/org.microprofile-ext.config-ext/configsource-json)
6 | [](https://www.javadoc.io/doc/org.microprofile-ext.config-ext/configsource-json)
7 |
8 | This source gets values from some json file(s).
9 |
10 | ## Usage
11 |
12 | ```xml
13 |
14 |
15 | org.microprofile-ext.config-ext
16 | configsource-json
17 | XXXXX
18 | runtime
19 |
20 |
21 | ```
22 |
23 | ## Example:
24 |
25 | ```json
26 |
27 | {
28 | "location": {
29 | "protocol": "http",
30 | "host": "localhost",
31 | "port": 8080,
32 | "path": "/some/path",
33 | "jedis": [
34 | "Yoda",
35 | "Qui-Gon Jinn",
36 | "Obi-Wan Kenobi",
37 | "Luke Skywalker"
38 | ]
39 | }
40 | }
41 |
42 | ```
43 |
44 | will create the following properties:
45 |
46 | ```property
47 |
48 | "location.protocol": "http"
49 | "location.host": "localhost"
50 | "location.port": "8080"
51 | "location.path": "/some/path"
52 | "location.jedis": "Yoda, Qui-Gon Jinn, Obi-Wan Kenobi, Luke Skywalker"
53 |
54 | ```
55 |
56 |
57 | You can `inject` the jedis using any of the following:
58 |
59 | ```java
60 |
61 | @Inject
62 | @ConfigProperty(name = "location.jedis")
63 | String jedisAsString;
64 |
65 | @Inject
66 | @ConfigProperty(name = "location.jedis")
67 | List jedisAsList;
68 |
69 | @Inject
70 | @ConfigProperty(name = "location.jedis")
71 | Set jedisAsSet;
72 |
73 | @Inject
74 | @ConfigProperty(name = "location.jedis")
75 | String[] jedisAsArray;
76 |
77 | ```
78 |
79 | ## Configure options
80 |
81 | ### Url(s)
82 |
83 | By default the config source will look for a file called `application.json`. You can set the location(s) of the files:
84 |
85 | configsource.json.url=
86 |
87 | example:
88 |
89 | configsource.json.url=file:/tmp/myconfig.json
90 |
91 | You can also add more than one location by comma-separating the location:
92 |
93 | configsource.json.url=file:/tmp/myconfig.json,http://localhost/myconfig.json
94 |
95 | The latest files will override properties in previous files. As example, if using above configuration, property `foo=bar` in `file:/tmp/myconfig.json` will be override if it's added to `http://localhost/myconfig.json`.
96 |
97 | ### Detecting changes.
98 |
99 | You can watch the resource for changes. This feature is disabled by default. To enable:
100 |
101 | configsource.json.pollForChanges=true
102 |
103 | By default it will poll every **5 seconds**. You can change that, example to poll every 5 minutes:
104 |
105 | configsource.json.pollInterval=300
106 |
107 | ### Events
108 |
109 | This config source fires CDI Events on changes (if above detecting for changes is enabled).
110 |
111 | Read more about [Config Events](https://github.com/microprofile-extensions/config-ext/blob/master/config-events/README.md)
112 |
113 | You can disable this with the `configsource.json.notifyOnChanges` property:
114 |
115 | configsource.json.notifyOnChanges=false
116 |
117 | If you added more than one resource as source, the event will only fire if the resulting file change also changed the global source change, as one file takes priority over the other.
118 |
119 | ### Key separator
120 |
121 | By default the separator used in the key is a DOT (.) example:
122 |
123 | ```property
124 |
125 | "location.protocol": "http"
126 | ```
127 |
128 | You can change this by setting `configsource.json.keyseparator` to the desired separator, example:
129 |
130 | configsource.json.keyseparator=_
131 |
132 | will create:
133 |
134 | ```property
135 |
136 | "location_protocol": "http"
137 | ```
138 |
--------------------------------------------------------------------------------
/configsource-json/application.json:
--------------------------------------------------------------------------------
1 | {
2 | "test": {
3 | "property": "a-string-value"
4 | },
5 | "deepList":{
6 | "level1":[
7 | "l1",
8 | "l2"
9 | ]
10 | },
11 | "listTest":[
12 | "item1",
13 | "item2",
14 | "item3,stillItem3"
15 | ]
16 | }
--------------------------------------------------------------------------------
/configsource-json/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 4.0.0
4 |
5 | org.microprofile-ext
6 | config-ext
7 | 3.0.2-SNAPSHOT
8 |
9 |
10 | org.microprofile-ext.config-ext
11 | configsource-json
12 |
13 | Microprofile Config Extensions :: Json config source
14 | A config that get the values from a json file
15 |
16 |
17 |
18 | ${project.groupId}
19 | configsource-filebase
20 | ${project.version}
21 |
22 |
23 |
24 |
25 | org.glassfish
26 | jakarta.json
27 | 2.0.1
28 | test
29 |
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/configsource-json/src/main/resources/META-INF/services/org.eclipse.microprofile.config.spi.ConfigSource:
--------------------------------------------------------------------------------
1 | org.microprofileext.config.source.json.JsonConfigSource
--------------------------------------------------------------------------------
/configsource-json/src/test/java/org/microprofileext/config/source/json/DisabledWhenEnabledKeyIsFalseTest.java:
--------------------------------------------------------------------------------
1 | package org.microprofileext.config.source.json;
2 |
3 | import java.util.NoSuchElementException;
4 | import jakarta.enterprise.inject.spi.Extension;
5 | import jakarta.inject.Inject;
6 | import org.eclipse.microprofile.config.Config;
7 | import org.eclipse.microprofile.config.spi.ConfigSource;
8 | import org.jboss.arquillian.container.test.api.Deployment;
9 | import org.jboss.arquillian.junit.Arquillian;
10 | import org.jboss.shrinkwrap.api.ShrinkWrap;
11 | import org.jboss.shrinkwrap.api.spec.JavaArchive;
12 | import org.junit.Test;
13 | import org.junit.runner.RunWith;
14 |
15 | /**
16 | * @author Phillip Kruger
17 | */
18 | @RunWith(Arquillian.class)
19 | public class DisabledWhenEnabledKeyIsFalseTest {
20 |
21 | @Inject
22 | Config config;
23 |
24 | @Deployment
25 | public static JavaArchive createDeployment() {
26 | return ShrinkWrap.create(JavaArchive.class)
27 | .addPackages(true, Config.class.getPackage())
28 | .addAsServiceProviderAndClasses(ConfigSource.class, JsonConfigSource.class)
29 | .addAsResource(DisabledWhenEnabledKeyIsFalseTest.class.getClassLoader().getResource("config-disabled.properties"), "META-INF/microprofile-config.properties")
30 | .addAsManifestResource("META-INF/beans.xml");
31 | }
32 |
33 | @Test(expected = NoSuchElementException.class)
34 | public void testPropertyFailsWhenExplicitlyDisabled() {
35 | config.getValue("test.property", String.class);
36 | }
37 |
38 | }
39 |
--------------------------------------------------------------------------------
/configsource-json/src/test/java/org/microprofileext/config/source/json/EnabledWhenEnabledKeyIsMissingTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2018 Derek P. Moore.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package org.microprofileext.config.source.json;
17 |
18 | import jakarta.enterprise.inject.spi.Extension;
19 | import jakarta.inject.Inject;
20 | import static org.assertj.core.api.Assertions.assertThat;
21 | import org.eclipse.microprofile.config.Config;
22 | import org.eclipse.microprofile.config.spi.ConfigSource;
23 | import org.jboss.arquillian.container.test.api.Deployment;
24 | import org.jboss.arquillian.junit.Arquillian;
25 | import org.jboss.shrinkwrap.api.ShrinkWrap;
26 | import org.jboss.shrinkwrap.api.spec.JavaArchive;
27 | import org.junit.Test;
28 | import org.junit.runner.RunWith;
29 |
30 | /**
31 | * @author Phillip Kruger
32 | */
33 | @RunWith(Arquillian.class)
34 | public class EnabledWhenEnabledKeyIsMissingTest {
35 |
36 | @Inject
37 | Config config;
38 |
39 | @Deployment
40 | public static JavaArchive createDeployment() {
41 | return ShrinkWrap.create(JavaArchive.class)
42 | .addPackages(true, Config.class.getPackage())
43 | .addAsServiceProviderAndClasses(ConfigSource.class, JsonConfigSource.class)
44 | .addAsResource(EnabledWhenEnabledKeyIsMissingTest.class.getClassLoader().getResource("config-empty.properties"), "META-INF/microprofile-config.properties")
45 | .addAsManifestResource("META-INF/beans.xml");
46 | }
47 |
48 | @Test
49 | public void testPropertyLoadsWhenNotExplicitlyEnabled() {
50 | assertThat(config.getOptionalValue("test.property", String.class)).get()
51 | .isEqualTo("a-string-value")
52 | .as("test.property in application.properties is set to a-string-value");
53 | }
54 |
55 | }
56 |
--------------------------------------------------------------------------------
/configsource-json/src/test/java/org/microprofileext/config/source/json/EnabledWhenEnabledKeyIsTrueTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2018 Derek P. Moore.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package org.microprofileext.config.source.json;
17 |
18 | import jakarta.enterprise.inject.spi.Extension;
19 | import jakarta.inject.Inject;
20 | import static org.assertj.core.api.Assertions.assertThat;
21 | import org.eclipse.microprofile.config.Config;
22 | import org.eclipse.microprofile.config.spi.ConfigSource;
23 | import org.jboss.arquillian.container.test.api.Deployment;
24 | import org.jboss.arquillian.junit.Arquillian;
25 | import org.jboss.shrinkwrap.api.ShrinkWrap;
26 | import org.jboss.shrinkwrap.api.spec.JavaArchive;
27 | import org.junit.Test;
28 | import org.junit.runner.RunWith;
29 |
30 | /**
31 | * @author Phillip Kruger
32 | */
33 | @RunWith(Arquillian.class)
34 | public class EnabledWhenEnabledKeyIsTrueTest {
35 |
36 | @Inject
37 | Config config;
38 |
39 | @Deployment
40 | public static JavaArchive createDeployment() {
41 | return ShrinkWrap.create(JavaArchive.class)
42 | .addPackages(true, Config.class.getPackage())
43 | .addAsServiceProviderAndClasses(ConfigSource.class, JsonConfigSource.class)
44 | .addAsResource(EnabledWhenEnabledKeyIsTrueTest.class.getClassLoader().getResource("config-enabled.properties"), "META-INF/microprofile-config.properties")
45 | .addAsManifestResource("META-INF/beans.xml");
46 | }
47 |
48 | @Test
49 | public void testPropertyLoadsWhenExplicitlyEnabled() {
50 | assertThat(config.getOptionalValue("test.property", String.class)).get()
51 | .isEqualTo("a-string-value")
52 | .as("test.property in application.properties is set to a-string-value");
53 | }
54 |
55 | }
56 |
--------------------------------------------------------------------------------
/configsource-json/src/test/java/org/microprofileext/config/source/json/ListTest.java:
--------------------------------------------------------------------------------
1 | package org.microprofileext.config.source.json;
2 |
3 | import java.util.List;
4 | import java.util.Set;
5 | import jakarta.enterprise.inject.spi.Extension;
6 | import jakarta.inject.Inject;
7 | import org.eclipse.microprofile.config.Config;
8 | import org.eclipse.microprofile.config.inject.ConfigProperty;
9 | import org.eclipse.microprofile.config.spi.ConfigSource;
10 | import org.jboss.arquillian.container.test.api.Deployment;
11 | import org.jboss.arquillian.junit.Arquillian;
12 | import org.jboss.shrinkwrap.api.ShrinkWrap;
13 | import org.jboss.shrinkwrap.api.spec.JavaArchive;
14 | import org.junit.Assert;
15 | import org.junit.Test;
16 | import org.junit.runner.RunWith;
17 |
18 | /**
19 | * @author Phillip Kruger
20 | */
21 | @RunWith(Arquillian.class)
22 | public class ListTest {
23 |
24 | @Inject
25 | @ConfigProperty(name = "listTest")
26 | String stringList;
27 |
28 | @Inject
29 | @ConfigProperty(name = "listTest")
30 | List listList;
31 |
32 | @Inject
33 | @ConfigProperty(name = "listTest")
34 | Set setList;
35 |
36 | @Inject
37 | @ConfigProperty(name = "listTest")
38 | String[] arrayList;
39 |
40 | @Inject
41 | @ConfigProperty(name = "deepList.level1")
42 | List deepList;
43 |
44 | @Deployment
45 | public static JavaArchive createDeployment() {
46 | return ShrinkWrap.create(JavaArchive.class)
47 | .addPackages(true, Config.class.getPackage())
48 | .addAsServiceProviderAndClasses(ConfigSource.class, JsonConfigSource.class)
49 | .addAsManifestResource("META-INF/beans.xml");
50 | }
51 |
52 | @Test
53 | public void testStringList() {
54 | Assert.assertEquals("item1,item2,item3\\,stillItem3", stringList);
55 | }
56 |
57 | @Test
58 | public void testListList() {
59 | Assert.assertNotNull(listList);
60 | Assert.assertEquals(3, listList.size());
61 |
62 | }
63 |
64 | @Test
65 | public void testSetList() {
66 | Assert.assertNotNull(setList);
67 | Assert.assertEquals(3, setList.size());
68 | }
69 |
70 | @Test
71 | public void testArrayList() {
72 | Assert.assertNotNull(arrayList);
73 | Assert.assertEquals(3, arrayList.length);
74 | }
75 |
76 | @Test
77 | public void testDeepList(){
78 | Assert.assertNotNull(deepList);
79 | Assert.assertEquals(2, deepList.size());
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/configsource-json/src/test/resources/META-INF/beans.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
--------------------------------------------------------------------------------
/configsource-json/src/test/resources/config-disabled.properties:
--------------------------------------------------------------------------------
1 | JsonConfigSource.enabled=false
2 |
--------------------------------------------------------------------------------
/configsource-json/src/test/resources/config-empty.properties:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/configsource-json/src/test/resources/config-enabled.properties:
--------------------------------------------------------------------------------
1 | JsonConfigSource.enabled=true
2 |
--------------------------------------------------------------------------------
/configsource-memory/README.md:
--------------------------------------------------------------------------------
1 | [Back to config-ext](https://github.com/microprofile-extensions/config-ext/blob/master/README.md)
2 |
3 | # Memory config source
4 |
5 | [](https://maven-badges.herokuapp.com/maven-central/org.microprofile-ext.config-ext/configsource-memory)
6 | [](https://www.javadoc.io/doc/org.microprofile-ext.config-ext/configsource-memory)
7 |
8 | This source gets and sets values in memory. Useful when you want to change config during runtime.
9 |
10 | ## Usage
11 |
12 | ```xml
13 |
14 |
15 | org.microprofile-ext.config-ext
16 | configsource-memory
17 | XXXX
18 |
19 |
20 | ```
21 |
22 | ## Info
23 |
24 | You can do this by using the REST API to change the config values:
25 |
26 | ```
27 |
28 | GET /microprofile-ext/memoryconfigsource/sources - list all config sources
29 | GET /microprofile-ext/memoryconfigsource/all - get all configurations
30 | GET /microprofile-ext/memoryconfigsource/key/{key} - get the configured value for {key}
31 | PUT /microprofile-ext/memoryconfigsource/key/{key} - set the value for {key}
32 | DELETE /microprofile-ext/memoryconfigsource/key/{key} - delete the configured value for {key}
33 |
34 | ```
35 |
36 | ### Curl Examples
37 |
38 | Add a property to `some.key` with value `some value`:
39 |
40 | ```
41 | curl -X PUT "http://localhost:8080/config-example/api/microprofile-ext/memoryconfigsource/key/some.key" -H "accept: */*" -H "Content-Type: text/plain" -d "some value"
42 | ```
43 |
44 | Get the property `some.key`:
45 |
46 | ```
47 | curl -X GET "http://localhost:8080/config-example/api/microprofile-ext/memoryconfigsource/key/some.key" -H "accept: */*"
48 | ```
49 |
50 | Get the property `some.key` but only at the `SystemProperty` source:
51 |
52 | ```
53 | curl -X GET "http://localhost:8080/config-example/api/microprofile-ext/memoryconfigsource/key/some.key?configsource=SysPropConfigSource" -H "accept: */*"
54 | ```
55 |
56 | Delete the property `some.key`
57 |
58 | ```
59 | curl -X DELETE "http://localhost:8080/config-example/api/microprofile-ext/memoryconfigsource/key/some.key" -H "accept: */*"
60 | ```
61 |
62 | ## Events
63 |
64 | This config source fires CDI Events on PUT and DELETE:
65 |
66 | Read more about [Config Events](https://github.com/microprofile-extensions/config-ext/blob/master/config-events/README.md)
67 |
68 | You can disable this with the `MemoryConfigSource.notifyOnChanges` property
69 |
70 | ## Configure options
71 |
72 | You can disable the config source by setting this config:
73 |
74 | MemoryConfigSource.enabled=false
75 |
76 | You can disable the change notification eventson changes by setting this config:
77 |
78 | MemoryConfigSource.notifyOnChanges=false
79 |
80 | 
81 |
--------------------------------------------------------------------------------
/configsource-memory/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 4.0.0
4 |
5 | org.microprofile-ext
6 | config-ext
7 | 3.0.2-SNAPSHOT
8 |
9 |
10 | org.microprofile-ext.config-ext
11 | configsource-memory
12 |
13 | Microprofile Config Extensions :: Memory config source
14 | A config that get the values from memory
15 |
16 |
17 |
18 | ${project.groupId}
19 | configsource-base
20 | ${project.version}
21 |
22 |
23 | ${project.groupId}
24 | config-events
25 | ${project.version}
26 |
27 |
28 | ${project.groupId}
29 | configsource-providers
30 | ${project.version}
31 |
32 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/configsource-memory/screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microprofile-extensions/config-ext/c1487f15f9d4246a9cd655ca72c5b43b8196dfa1/configsource-memory/screenshot.png
--------------------------------------------------------------------------------
/configsource-memory/src/main/java/org/microprofileext/config/source/memory/MemoryConfigSource.java:
--------------------------------------------------------------------------------
1 | package org.microprofileext.config.source.memory;
2 |
3 | import java.util.HashMap;
4 | import java.util.Map;
5 | import java.util.logging.Logger;
6 | import org.microprofileext.config.source.base.EnabledConfigSource;
7 |
8 | /**
9 | * In memory config source. Use the REST Endpoint to populate values
10 | * @author Phillip Kruger
11 | */
12 | public class MemoryConfigSource extends EnabledConfigSource {
13 |
14 | private static final Logger log = Logger.getLogger(MemoryConfigSource.class.getName());
15 |
16 | public static final String NAME = "MemoryConfigSource";
17 | private static final Map PROPERTIES = new HashMap<>();
18 |
19 | public MemoryConfigSource(){
20 | log.info("Loading [memory] MicroProfile ConfigSource");
21 | super.initOrdinal(900);
22 | }
23 |
24 | @Override
25 | public Map getPropertiesIfEnabled() {
26 | return PROPERTIES;
27 | }
28 |
29 | @Override
30 | public String getValue(String key) {
31 | if(PROPERTIES.containsKey(key)){
32 | return PROPERTIES.get(key);
33 | }
34 | return null;
35 | }
36 |
37 | @Override
38 | public String getName() {
39 | return NAME;
40 | }
41 | }
--------------------------------------------------------------------------------
/configsource-memory/src/main/resources/META-INF/beans.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
--------------------------------------------------------------------------------
/configsource-memory/src/main/resources/META-INF/services/org.eclipse.microprofile.config.spi.ConfigSource:
--------------------------------------------------------------------------------
1 | org.microprofileext.config.source.memory.MemoryConfigSource
--------------------------------------------------------------------------------
/configsource-properties/README.md:
--------------------------------------------------------------------------------
1 | [Back to config-ext](https://github.com/microprofile-extensions/config-ext/blob/master/README.md)
2 |
3 | # Properties Config Source
4 |
5 | [](https://maven-badges.herokuapp.com/maven-central/org.microprofile-ext.config-ext/configsource-properties)
6 | [](https://www.javadoc.io/doc/org.microprofile-ext.config-ext/configsource-properties)
7 |
8 | This source gets values from some properties file(s).
9 |
10 | ## Usage
11 |
12 | ```xml
13 |
14 |
15 | org.microprofile-ext.config-ext
16 | configsource-properties
17 | XXXXX
18 | runtime
19 |
20 |
21 | ```
22 |
23 | ## Example:
24 |
25 | ```properties
26 |
27 | somekey=somevalue
28 | location.protocol=http
29 | location.host=localhost
30 | location.port=8080
31 | location.path=/some/path
32 | location.jedis=Yoda, Qui-Gon Jinn, Obi-Wan Kenobi, Luke Skywalker
33 |
34 | ```
35 |
36 |
37 | You can `inject` the jedis using any of the following:
38 |
39 | ```java
40 |
41 | @Inject
42 | @ConfigProperty(name = "location.jedis")
43 | String jedisAsString;
44 |
45 | @Inject
46 | @ConfigProperty(name = "location.jedis")
47 | List jedisAsList;
48 |
49 | @Inject
50 | @ConfigProperty(name = "location.jedis")
51 | Set jedisAsSet;
52 |
53 | @Inject
54 | @ConfigProperty(name = "location.jedis")
55 | String[] jedisAsArray;
56 |
57 | ```
58 |
59 | ## Configure options
60 |
61 | ### Url(s)
62 |
63 | By default the config source will look for a file called `application.properties`. You can set the location(s) of the files:
64 |
65 | configsource.properties.url=
66 |
67 | example:
68 |
69 | configsource.properties.url=file:/tmp/myconfig.properties
70 |
71 | You can also add more than one location by comma-separating the location:
72 |
73 | configsource.properties.url=file:/tmp/myconfig.properties,http://localhost/myconfig.properties
74 |
75 | The latest files will override properties in previous files. As example, if using above configuration, property `foo=bar` in `file:/tmp/myconfig.properties` will be override if it's added to `http://localhost/myconfig.properties`.
76 |
77 | ### Detecting changes.
78 |
79 | You can watch the resource for changes. This feature is disabled by default. To enable:
80 |
81 | configsource.properties.pollForChanges=true
82 |
83 | By default it will poll every **5 seconds**. You can change that, example to poll every 5 minutes:
84 |
85 | configsource.properties.pollInterval=300
86 |
87 | ### Events
88 |
89 | This config source fires CDI Events on changes (if above detecting for changes is enabled).
90 |
91 | Read more about [Config Events](https://github.com/microprofile-extensions/config-ext/blob/master/config-events/README.md)
92 |
93 | You can disable this with the `configsource.properties.notifyOnChanges` property:
94 |
95 | configsource.properties.notifyOnChanges=false
96 |
97 | If you added more than one resource as source, the event will only fire if the resulting file change also changed the global source change, as one file takes priority over the other.
98 |
99 | ### Key separator
100 |
101 | By default the separator used in the key is a DOT (.) example:
102 |
103 | ```property
104 |
105 | "location.protocol": "http"
106 | ```
107 |
108 | You can change this by setting `configsource.properties.keyseparator` to the desired separator, example:
109 |
110 | configsource.properties.keyseparator=_
111 |
112 | will create:
113 |
114 | ```property
115 |
116 | "location_protocol": "http"
117 | ```
118 |
--------------------------------------------------------------------------------
/configsource-properties/application.properties:
--------------------------------------------------------------------------------
1 | test.property=a-string-value
2 | deepList.level1=l1,l2
3 | listTest=item1,item2,item3\\,stillItem3
--------------------------------------------------------------------------------
/configsource-properties/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 4.0.0
4 |
5 | org.microprofile-ext
6 | config-ext
7 | 3.0.2-SNAPSHOT
8 |
9 |
10 | org.microprofile-ext.config-ext
11 | configsource-properties
12 |
13 | Microprofile Config Extensions :: Properties config source
14 | A config that gets its values from a properties file(s) at a URL
15 |
16 |
17 |
18 | ${project.groupId}
19 | configsource-filebase
20 | ${project.version}
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/configsource-properties/src/main/java/org/microprofileext/config/source/properties/PropertiesConfigSource.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Licensed to the Apache Software Foundation (ASF) under one or more
3 | * contributor license agreements. See the NOTICE file distributed with
4 | * this work for additional information regarding copyright ownership.
5 | * The ASF licenses this file to You under the Apache License, Version 2.0
6 | * (the "License"); you may not use this file except in compliance with
7 | * the License. You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | */
17 | package org.microprofileext.config.source.properties;
18 |
19 | import java.io.IOException;
20 | import java.io.InputStream;
21 | import java.util.Map;
22 | import java.util.Properties;
23 | import java.util.Set;
24 | import java.util.logging.Level;
25 | import java.util.logging.Logger;
26 | import org.microprofileext.config.source.base.file.AbstractUrlBasedSource;
27 |
28 | /**
29 | * Properties config source
30 | * @author Mark Struberg
31 | * @author Derek P. Moore
32 | * @author Phillip Kruger
33 | */
34 | public class PropertiesConfigSource extends AbstractUrlBasedSource {
35 |
36 | private static final Logger log = Logger.getLogger(PropertiesConfigSource.class.getName());
37 |
38 | @Override
39 | protected String getFileExtension() {
40 | return "properties";
41 | }
42 |
43 | @Override @SuppressWarnings("unchecked")
44 | protected Map toMap(InputStream inputStream) {
45 | Properties props = new Properties();
46 | try {
47 | props.load(inputStream);
48 | if(!super.getKeySeparator().equalsIgnoreCase(DOT))props = changeKeySeparator(props);
49 | } catch (IOException e) {
50 | log.log(Level.WARNING, "Unable to load properties [{0}]", e.getMessage());
51 | }
52 | return (Map) props;
53 | }
54 |
55 | private Properties changeKeySeparator(Properties props){
56 | Properties nprops = new Properties();
57 | Set