├── .editorconfig ├── .gitattributes ├── .github ├── release-drafter.yml └── workflows │ ├── branch-ci.yml │ ├── pre-release-ci.yml │ └── release-ci.yml ├── .gitignore ├── .yamllint.yml ├── CHANGES.md ├── LICENSE.txt ├── README.md ├── checkstyle-suppressions.xml ├── config-examples ├── config │ ├── config.props │ └── example.props ├── pom.xml └── src │ └── main │ ├── java │ └── io │ │ └── scalecube │ │ └── config │ │ └── examples │ │ ├── ConfigRegistryExample.java │ │ ├── DemoConfig.java │ │ ├── LocalResourceConfigExample.java │ │ ├── PredicateOrderingConfigExample.java │ │ ├── PredicateShortcutsConfigExample.java │ │ ├── ReloadableLocalResourceConfigExample.java │ │ └── SourceOrderExample.java │ └── resources │ ├── config.props │ ├── log4j2.xml │ ├── order.config.props │ ├── order.override.config.props │ ├── system.config.props │ └── system.override.config.props ├── config-vault ├── pom.xml └── src │ ├── main │ └── java │ │ └── io │ │ └── scalecube │ │ └── config │ │ └── vault │ │ ├── EnvironmentVaultTokenSupplier.java │ │ ├── KubernetesVaultTokenSupplier.java │ │ ├── VaultClientTokenSupplier.java │ │ ├── VaultConfigSource.java │ │ ├── VaultInvoker.java │ │ ├── VaultInvokers.java │ │ └── VaultTokenSupplier.java │ └── test │ ├── java │ └── io │ │ └── scalecube │ │ └── config │ │ └── vault │ │ ├── VaultConfigSourceTest.java │ │ ├── VaultContainerExtension.java │ │ └── VaultInstance.java │ └── resources │ └── log4j2-test.xml ├── config ├── pom.xml └── src │ ├── main │ └── java │ │ └── io │ │ └── scalecube │ │ └── config │ │ ├── AbstractConfigProperty.java │ │ ├── AbstractSimpleConfigProperty.java │ │ ├── BooleanConfigProperty.java │ │ ├── BooleanConfigPropertyImpl.java │ │ ├── ConfigProperty.java │ │ ├── ConfigPropertyInfo.java │ │ ├── ConfigRegistry.java │ │ ├── ConfigRegistryException.java │ │ ├── ConfigRegistryImpl.java │ │ ├── ConfigRegistrySettings.java │ │ ├── ConfigSourceNotAvailableException.java │ │ ├── DoubleConfigProperty.java │ │ ├── DoubleConfigPropertyImpl.java │ │ ├── DurationConfigProperty.java │ │ ├── DurationConfigPropertyImpl.java │ │ ├── DurationParser.java │ │ ├── IntConfigProperty.java │ │ ├── IntConfigPropertyImpl.java │ │ ├── ListConfigProperty.java │ │ ├── ListConfigPropertyImpl.java │ │ ├── LongConfigProperty.java │ │ ├── LongConfigPropertyImpl.java │ │ ├── MappedObjectConfigProperty.java │ │ ├── MultimapConfigProperty.java │ │ ├── MultimapConfigPropertyImpl.java │ │ ├── ObjectConfigProperty.java │ │ ├── ObjectConfigPropertyImpl.java │ │ ├── ObjectPropertyField.java │ │ ├── ObjectPropertyParser.java │ │ ├── PropertyCallback.java │ │ ├── StringConfigProperty.java │ │ ├── StringConfigPropertyImpl.java │ │ ├── audit │ │ ├── ConfigEvent.java │ │ ├── ConfigEventListener.java │ │ └── LoggingConfigEventListener.java │ │ ├── jmx │ │ ├── JmxConfigRegistry.java │ │ └── JmxConfigRegistryMBean.java │ │ ├── keyvalue │ │ ├── KeyValueConfigEntity.java │ │ ├── KeyValueConfigName.java │ │ ├── KeyValueConfigRepository.java │ │ └── KeyValueConfigSource.java │ │ ├── source │ │ ├── ClassPathConfigSource.java │ │ ├── ConfigSource.java │ │ ├── ConfigSourceInfo.java │ │ ├── FileDirectoryConfigSource.java │ │ ├── FilteredPathConfigSource.java │ │ ├── LoadedConfigProperty.java │ │ ├── SystemEnvironmentConfigSource.java │ │ ├── SystemEnvironmentSingleVariableConfigSource.java │ │ ├── SystemEnvironmentVariablesConfigSource.java │ │ └── SystemPropertiesConfigSource.java │ │ └── utils │ │ └── ThrowableUtil.java │ └── test │ ├── java │ └── io │ │ └── scalecube │ │ └── config │ │ ├── MultimapConfigPropertyTest.java │ │ ├── ObjectConfigPropertyManyInstancesTest.java │ │ ├── ObjectConfigPropertyTest.java │ │ ├── ObjectPropertyFieldTest.java │ │ ├── SimpleConfigPropertyManyInstancesTest.java │ │ ├── SimpleConfigPropertyTest.java │ │ ├── TestUtil.java │ │ └── keyvalue │ │ └── KeyValueConfigSourceTest.java │ └── resources │ └── log4j2-test.xml └── pom.xml /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | indent_style = space 7 | indent_size = 2 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | *.txt text 4 | *.sh text eol=lf 5 | *.html text eol=lf diff=html 6 | *.css text eol=lf 7 | *.js text eol=lf 8 | *.jpg -text 9 | *.pdf -text 10 | *.java text diff=java 11 | -------------------------------------------------------------------------------- /.github/release-drafter.yml: -------------------------------------------------------------------------------- 1 | template: | 2 | ## What’s Changed 3 | 4 | $CHANGES 5 | -------------------------------------------------------------------------------- /.github/workflows/branch-ci.yml: -------------------------------------------------------------------------------- 1 | name: Branch CI 2 | 3 | on: 4 | push: 5 | paths-ignore: 6 | - '.github/workflows/**' 7 | - '*.md' 8 | - '*.txt' 9 | branches-ignore: 10 | - 'release*' 11 | 12 | jobs: 13 | build: 14 | name: Branch CI 15 | runs-on: ubuntu-latest 16 | steps: 17 | - uses: actions/checkout@v4 18 | - uses: actions/cache@v3 19 | with: 20 | path: ~/.m2/repository 21 | key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} 22 | restore-keys: | 23 | ${{ runner.os }}-maven- 24 | - name: Set up JDK 25 | uses: actions/setup-java@v4 26 | with: 27 | java-version: 17 28 | distribution: zulu 29 | server-id: github 30 | server-username: GITHUB_ACTOR 31 | server-password: GITHUB_TOKEN 32 | - name: Maven Build 33 | run: mvn clean install -DskipTests=true -Dmaven.javadoc.skip=true -B -V 34 | env: 35 | GITHUB_TOKEN: ${{ secrets.ORGANIZATION_TOKEN }} 36 | - name: Maven Verify 37 | run: mvn verify -B 38 | -------------------------------------------------------------------------------- /.github/workflows/pre-release-ci.yml: -------------------------------------------------------------------------------- 1 | name: Pre-release CI 2 | 3 | on: 4 | release: 5 | types: [prereleased] 6 | 7 | jobs: 8 | build: 9 | name: Pre-release CI 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v4 13 | - uses: actions/cache@v3 14 | with: 15 | path: ~/.m2/repository 16 | key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} 17 | restore-keys: | 18 | ${{ runner.os }}-maven- 19 | - name: Set up Java for publishing to GitHub Packages 20 | uses: actions/setup-java@v4 21 | with: 22 | java-version: 17 23 | distribution: zulu 24 | server-id: github 25 | server-username: GITHUB_ACTOR 26 | server-password: GITHUB_TOKEN 27 | - name: Deploy pre-release version to GitHub Packages 28 | run: | 29 | pre_release_version=${{ github.event.release.tag_name }} 30 | echo Pre-release version $pre_release_version 31 | mvn versions:set -DnewVersion=$pre_release_version -DgenerateBackupPoms=false 32 | mvn versions:commit 33 | mvn clean deploy -Pdeploy2Github -B -V 34 | env: 35 | GITHUB_TOKEN: ${{ secrets.ORGANIZATION_TOKEN }} 36 | - name: Set up Java for publishing to Maven Central Repository 37 | uses: actions/setup-java@v4 38 | with: 39 | java-version: 17 40 | distribution: zulu 41 | server-id: ossrh 42 | server-username: MAVEN_USERNAME 43 | server-password: MAVEN_PASSWORD 44 | gpg-private-key: ${{ secrets.MAVEN_GPG_PRIVATE_KEY }} 45 | gpg-passphrase: MAVEN_GPG_PASSPHRASE 46 | - name: Deploy pre-release version to the Maven Central Repository 47 | run: | 48 | pre_release_version=${{ github.event.release.tag_name }} 49 | echo Pre-release version $pre_release_version 50 | mvn versions:set -DnewVersion=$pre_release_version -DgenerateBackupPoms=false 51 | mvn versions:commit 52 | mvn deploy -Pdeploy2Maven -DskipTests -B -V 53 | env: 54 | MAVEN_USERNAME: ${{ secrets.OSSRH_USERNAME }} 55 | MAVEN_PASSWORD: ${{ secrets.OSSRH_TOKEN }} 56 | MAVEN_GPG_PASSPHRASE: ${{ secrets.MAVEN_GPG_PASSPHRASE }} 57 | - name: Rollback pre-release (remove tag) 58 | if: failure() 59 | run: git push origin :refs/tags/${{ github.event.release.tag_name }} 60 | -------------------------------------------------------------------------------- /.github/workflows/release-ci.yml: -------------------------------------------------------------------------------- 1 | name: Release CI 2 | 3 | on: 4 | release: 5 | types: [released] 6 | 7 | jobs: 8 | build: 9 | name: Release CI 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v4 13 | with: 14 | fetch-depth: 0 15 | - run: git checkout ${{ github.event.release.target_commitish }} 16 | - uses: actions/cache@v3 17 | with: 18 | path: ~/.m2/repository 19 | key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} 20 | restore-keys: | 21 | ${{ runner.os }}-maven- 22 | - name: Set up Java for publishing to GitHub Packages 23 | uses: actions/setup-java@v4 24 | with: 25 | java-version: 17 26 | distribution: zulu 27 | server-id: github 28 | server-username: GITHUB_ACTOR 29 | server-password: GITHUB_TOKEN 30 | - name: Maven Build 31 | run: mvn clean install -DskipTests=true -B -V 32 | env: 33 | GITHUB_TOKEN: ${{ secrets.ORGANIZATION_TOKEN }} 34 | - name: Maven Verify 35 | run: mvn verify -B 36 | - name: Configure git 37 | run: | 38 | git config --global user.email "${GITHUB_ACTOR}@users.noreply.github.com" 39 | git config --global user.name "${GITHUB_ACTOR}" 40 | - name: Prepare release 41 | id: prepare_release 42 | run: | 43 | mvn -B build-helper:parse-version release:prepare \ 44 | -DreleaseVersion=\${parsedVersion.majorVersion}.\${parsedVersion.minorVersion}.\${parsedVersion.incrementalVersion} \ 45 | -Darguments="-DskipTests=true" 46 | echo release_tag=$(git describe --tags --abbrev=0) >> $GITHUB_OUTPUT 47 | - name: Perform release 48 | run: mvn -B release:perform -Pdeploy2Github -Darguments="-DskipTests=true -Pdeploy2Github" 49 | env: 50 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 51 | GITHUB_REPOSITORY: ${{ secrets.GITHUB_REPOSITORY }} 52 | - name: Set up Java for publishing to Maven Central Repository 53 | uses: actions/setup-java@v4 54 | with: 55 | java-version: 17 56 | distribution: zulu 57 | server-id: ossrh 58 | server-username: MAVEN_USERNAME 59 | server-password: MAVEN_PASSWORD 60 | gpg-private-key: ${{ secrets.MAVEN_GPG_PRIVATE_KEY }} 61 | gpg-passphrase: MAVEN_GPG_PASSPHRASE 62 | - name: Deploy release version to the Maven Central Repository 63 | run: | 64 | release_version=$(echo ${{ steps.prepare_release.outputs.release_tag }} | sed "s/release-//") 65 | echo release version $release_version 66 | mvn versions:set -DnewVersion=$release_version -DgenerateBackupPoms=false 67 | mvn versions:commit 68 | mvn deploy -Pdeploy2Maven -DskipTests -B -V 69 | env: 70 | MAVEN_USERNAME: ${{ secrets.OSSRH_USERNAME }} 71 | MAVEN_PASSWORD: ${{ secrets.OSSRH_TOKEN }} 72 | MAVEN_GPG_PASSPHRASE: ${{ secrets.MAVEN_GPG_PASSPHRASE }} 73 | - name: Rollback release 74 | if: failure() 75 | run: | 76 | mvn release:rollback || echo "nothing to rollback" 77 | git push origin :refs/tags/${{ github.event.release.tag_name }} 78 | if [ ! -z "${{ steps.prepare_release.outputs.release_tag }}" ] 79 | then 80 | git tag -d ${{ steps.prepare_release.outputs.release_tag }} 81 | git push origin :refs/tags/${{ steps.prepare_release.outputs.release_tag }} 82 | fi 83 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .* 2 | !.gitignore 3 | !.gitattributes 4 | !.github 5 | !.editorconfig 6 | !.*.yml 7 | !.env.example 8 | **/target/ 9 | *.iml 10 | **/logs/*.log 11 | **/logs/*.log.* 12 | *.db 13 | *.csv 14 | *.log 15 | -------------------------------------------------------------------------------- /.yamllint.yml: -------------------------------------------------------------------------------- 1 | extends: default 2 | rules: 3 | document-start: 4 | present: false 5 | truthy: disable 6 | comments: 7 | min-spaces-from-content: 1 8 | line-length: 9 | max: 150 10 | braces: 11 | min-spaces-inside: 0 12 | max-spaces-inside: 0 13 | brackets: 14 | min-spaces-inside: 0 15 | max-spaces-inside: 0 16 | indentation: 17 | indent-sequences: consistent 18 | -------------------------------------------------------------------------------- /CHANGES.md: -------------------------------------------------------------------------------- 1 | # Changes 2 | 3 | ## 0.3.1 / 2017-09-21 4 | 5 | * Enhancements of object properties support 6 | 7 | ## 0.3.0 / 2017-09-11 8 | 9 | * Support generic object property type (property groups) 10 | 11 | ## 0.2.2 / 2017-09-07 12 | 13 | * Fixed issue with PostConstruct calls twice init method with Spring 14 | * Fixed issue for getting address for localhost at Docker container 15 | * Enhanced duration parser to support more convenient Duration format 16 | * Added new API method `valueOrThrow` 17 | 18 | ## 0.2.1 / 2017-08-29 19 | 20 | * Improve internal implementation of callbacks mechanism 21 | * Use Jetty HTTP server instead of Netty-based 22 | * DirectoryConfigSource do not throw exceptions if can't find base directory 23 | 24 | ## 0.2.0 / 2017-08-21 25 | 26 | * Enhance ConfigProperty API 27 | * Support Duration config type 28 | * Support typed lists config types 29 | * Support custom executors for property callbacks 30 | 31 | ## 0.1.1 / 2017-08-17 32 | 33 | * Change HTTP resource base path to `_config` 34 | * Add shortcut methods to get property values from `ConfigRegistry` 35 | * Add `addFisrtSource` and `addBeforeSource` methods to `ConfigRegistrySettings` 36 | 37 | ## 0.1.0 / 2017-08-15 38 | 39 | * Initial release 40 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ScaleCube Config 2 | 3 | [![Maven Central](https://maven-badges.herokuapp.com/maven-central/io.scalecube/config/badge.svg)](https://maven-badges.herokuapp.com/maven-central/io.scalecube/config) 4 | 5 | ScaleCube Config is a configuration management library for JVM based distributed applications. 6 | 7 | It provides the following functionality: 8 | * Dynamic typed properties 9 | * Register callbacks on property changes 10 | * Object binding for grouping properties 11 | * Extensible range of supported property sources (environment variables, program arguments, classpath, property files, mongodb, git repository, zookeeper etc.) 12 | * Support centralized hierarchical property sources 13 | * Control over order of applying different property sources 14 | * Audit log of property changes 15 | * Expose properties and settings via JMX and/or HTTP 16 | 17 | ## Usage 18 | 19 | Configure and create configuration registry instance: 20 | 21 | ``` java 22 | Predicate predicate = path -> path.toString().endsWith(".props"); // match by .props extension 23 | ConfigRegistrySettings settings = ConfigRegistrySettings.builder() 24 | .addLastSource("classpath", new ClassPathConfigSource(predicate)) 25 | .addLastSource("configDirectory", new DirectoryConfigSource("conf" /* base path */, predicate)) 26 | .addListener(new Slf4JConfigEventListener()) // print all property changes to log 27 | .build(); 28 | ConfigRegistry configRegistry = ConfigRegistry.create(settings); 29 | ``` 30 | 31 | Get dynamic typed configuration property: 32 | 33 | ``` java 34 | LongConfigProperty timeoutProperty = configRegistry.longProperty("http.request-timeout"); 35 | long timeout = timeoutProperty.get(30 /* default value */); 36 | ``` 37 | 38 | Register callbacks on property modifications: 39 | 40 | ``` java 41 | timeoutProperty.addCallback((oldValue, newValue) -> 42 | System.out.println("Timeout value changed to " + newValue)); 43 | ``` 44 | 45 | Register validators on property values (only values which passed all validators will be applied): 46 | ``` java 47 | timeoutProperty.addValidator(val -> val >= 0); // timeout can't be negative 48 | ``` 49 | 50 | Utilize object binding: 51 | 52 | ``` java 53 | // Define configuration class 54 | public interface MyConfig { 55 | private boolean meaning; 56 | private int answer; 57 | private double realAnswer; 58 | ... 59 | } 60 | 61 | // myapp.config.meaning=true 62 | // myapp.config.answer=42 63 | // myapp.config.realAnswer=42.000000000000001 64 | ObjectConfigProperty config = configRegistry.objectProperty("myapp.config", MyConfig.class); 65 | 66 | // Get current config values 67 | MyConfig currentConfig = config.value(MyConfig.defaultValue() /* or default */); 68 | 69 | // Register callback (called only once per config reload even when several properties changed) 70 | config.addCallback((oldConfig, newConfig) -> 71 | System.out.println("MyConfig updated from " + oldConfig + " to " + newConfig)); 72 | 73 | // Register validator 74 | // If meaning is present, an answer should be at least as big as the answer 75 | config.addValidator(val -> val.meaning && val.answer >= 42); 76 | ``` 77 | 78 | Start embedded HTTP server which exposes configuration endpoints: 79 | 80 | ``` java 81 | ConfigRegistryHttpServer.create(configRegistry, 5050); // starts http server on port 5050 82 | ``` 83 | 84 | After HTTP server is started explore configuration registry by browsing following endpoints: 85 | * [http://localhost:5050/_config/properties](http://localhost:5050/_config/properties) 86 | * [http://localhost:5050/_config/sources](http://localhost:5050/_config/sources) 87 | * [http://localhost:5050/_config/events](http://localhost:5050/_config/events) 88 | * [http://localhost:5050/_config/settings](http://localhost:5050/_config/settings) 89 | 90 | See more examples at [config-examples](https://github.com/scalecube/scalecube-config/tree/master/config-examples/src/main/java/io/scalecube/config/examples) module. 91 | 92 | ## Maven 93 | 94 | Binaries and dependency information for Maven can be found at 95 | [http://search.maven.org](http://search.maven.org/#search%7Cga%7C1%7Cio.scalecube.config). 96 | 97 | Change history and [version numbers](http://semver.org/) can be found at [CHANGES.md](https://github.com/scalecube/scalecube-config/blob/master/CHANGES.md). 98 | 99 | Maven dependency: 100 | 101 | ``` xml 102 | 103 | io.scalecube 104 | config 105 | x.y.z 106 | 107 | 108 | 109 | 110 | io.scalecube 111 | config-http-server 112 | x.y.z 113 | 114 | 115 | 116 | 117 | io.scalecube 118 | config-mongo 119 | x.y.z 120 | 121 | 122 | ``` 123 | 124 | ## Bugs and Feedback 125 | 126 | For bugs, questions and discussions please use the [GitHub Issues](https://github.com/scalecube/scalecube-config/issues). 127 | 128 | ## License 129 | 130 | [Apache License, Version 2.0](https://github.com/scalecube/scalecube-config/blob/master/LICENSE.txt) 131 | -------------------------------------------------------------------------------- /checkstyle-suppressions.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /config-examples/config/config.props: -------------------------------------------------------------------------------- 1 | prop1=from_filesystem 2 | prop2=from_filesystem_config_only 3 | propertyDuration=PT0.2S 4 | propertyEnhancedDuration=200ms 5 | propertyList1=a,b,c 6 | propertyList2=1.0,2.0,3.0 7 | -------------------------------------------------------------------------------- /config-examples/config/example.props: -------------------------------------------------------------------------------- 1 | host=www.from-directory1 -------------------------------------------------------------------------------- /config-examples/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | 6 | io.scalecube 7 | scalecube-config-parent 8 | 0.5.2-SNAPSHOT 9 | 10 | 11 | scalecube-config-examples 12 | 13 | 14 | 15 | io.scalecube 16 | scalecube-config 17 | ${project.version} 18 | 19 | 20 | org.slf4j 21 | slf4j-api 22 | 23 | 24 | org.apache.logging.log4j 25 | log4j-slf4j-impl 26 | 27 | 28 | org.apache.logging.log4j 29 | log4j-core 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /config-examples/src/main/java/io/scalecube/config/examples/ConfigRegistryExample.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.config.examples; 2 | 3 | import io.scalecube.config.ConfigRegistry; 4 | import io.scalecube.config.ConfigRegistrySettings; 5 | import io.scalecube.config.StringConfigProperty; 6 | import io.scalecube.config.audit.LoggingConfigEventListener; 7 | import io.scalecube.config.source.ClassPathConfigSource; 8 | import io.scalecube.config.source.FileDirectoryConfigSource; 9 | import java.nio.file.Path; 10 | import java.util.function.Predicate; 11 | 12 | @SuppressWarnings("OptionalGetWithoutIsPresent") 13 | public class ConfigRegistryExample { 14 | 15 | /** 16 | * Main method of example of using {@link ConfigRegistry}. 17 | * 18 | * @param args program arguments 19 | */ 20 | public static void main(String[] args) { 21 | Predicate propsPredicate = path -> path.toString().endsWith(".props"); 22 | 23 | String basePath = "config-examples/config"; 24 | 25 | ConfigRegistry configRegistry = 26 | ConfigRegistry.create( 27 | ConfigRegistrySettings.builder() 28 | .addLastSource("classpath", new ClassPathConfigSource(propsPredicate)) 29 | .addLastSource( 30 | "configDirectory", new FileDirectoryConfigSource(basePath, propsPredicate)) 31 | .addListener(new LoggingConfigEventListener()) 32 | .jmxEnabled(true) 33 | .jmxMBeanName("config.exporter:name=ConfigRegistry") 34 | .build()); 35 | 36 | StringConfigProperty orderedProp1 = configRegistry.stringProperty("orderedProp1"); 37 | 38 | System.out.println("### Matched by first predicate orderedProp1=" + orderedProp1.value().get()); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /config-examples/src/main/java/io/scalecube/config/examples/DemoConfig.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.config.examples; 2 | 3 | import io.scalecube.config.ConfigRegistry; 4 | import io.scalecube.config.ConfigRegistrySettings; 5 | import io.scalecube.config.StringConfigProperty; 6 | import io.scalecube.config.audit.LoggingConfigEventListener; 7 | import io.scalecube.config.source.FileDirectoryConfigSource; 8 | import java.nio.file.Path; 9 | import java.util.function.Predicate; 10 | 11 | public class DemoConfig { 12 | 13 | /** 14 | * Main method of example. 15 | * 16 | * @param args program arguments 17 | */ 18 | public static void main(String[] args) { 19 | 20 | // Mongo property source init 21 | String databaseName = "MongoConfigExample"; 22 | String uri = "mongodb://localhost:27017/" + databaseName; 23 | String configSourceCollectionName = "MongoConfigRepository"; 24 | String auditLogCollectionName = "TestConfigurationAuditLog"; 25 | 26 | // Local resource cfg source init 27 | Predicate propsPredicate = path -> path.toString().endsWith(".props"); 28 | String basePath = "config-examples/config"; 29 | 30 | // Config registry init 31 | ConfigRegistry configRegistry = 32 | ConfigRegistry.create( 33 | ConfigRegistrySettings.builder() 34 | .addLastSource( 35 | "ConfigDirectory", new FileDirectoryConfigSource(basePath, propsPredicate)) 36 | .addListener(new LoggingConfigEventListener()) 37 | .keepRecentConfigEvents(10) 38 | .reloadIntervalSec(3) 39 | .jmxEnabled(true) 40 | .jmxMBeanName("config.exporter:name=ConfigRegistry") 41 | .build()); 42 | 43 | // Inject cfgReg into target component 44 | SomeComponent component = new SomeComponent(configRegistry); 45 | } 46 | 47 | static class SomeComponent { 48 | 49 | private StringConfigProperty host; 50 | 51 | SomeComponent(ConfigRegistry cfgReg) { 52 | host = cfgReg.stringProperty("host"); 53 | host.addCallback( 54 | (oldVal, newVal) -> System.out.println("###Property changed: " + oldVal + "->" + newVal)); 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /config-examples/src/main/java/io/scalecube/config/examples/LocalResourceConfigExample.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.config.examples; 2 | 3 | import io.scalecube.config.ConfigRegistry; 4 | import io.scalecube.config.ConfigRegistrySettings; 5 | import io.scalecube.config.StringConfigProperty; 6 | import io.scalecube.config.source.ClassPathConfigSource; 7 | import io.scalecube.config.source.FileDirectoryConfigSource; 8 | import java.nio.file.Path; 9 | import java.util.function.Predicate; 10 | 11 | @SuppressWarnings("OptionalGetWithoutIsPresent") 12 | public class LocalResourceConfigExample { 13 | 14 | /** 15 | * Main method of example of local resource config. 16 | * 17 | * @param args program arguments 18 | */ 19 | public static void main(String[] args) { 20 | Predicate propsPredicate = path -> path.toString().endsWith(".props"); 21 | 22 | String basePath = "config-examples/config"; 23 | 24 | ConfigRegistry configRegistry = 25 | ConfigRegistry.create( 26 | ConfigRegistrySettings.builder() 27 | .addLastSource("classpath", new ClassPathConfigSource(propsPredicate)) 28 | .addLastSource( 29 | "configDirectory", new FileDirectoryConfigSource(basePath, propsPredicate)) 30 | .jmxEnabled(false) 31 | .build()); 32 | 33 | StringConfigProperty prop1 = configRegistry.stringProperty("prop1"); 34 | StringConfigProperty prop2 = configRegistry.stringProperty("prop2"); 35 | 36 | System.out.println("### Classpath property: prop1=" + prop1.value().get()); 37 | System.out.println("### Property existing only in filesystem: prop2=" + prop2.value().get()); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /config-examples/src/main/java/io/scalecube/config/examples/PredicateOrderingConfigExample.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.config.examples; 2 | 3 | import io.scalecube.config.ConfigRegistry; 4 | import io.scalecube.config.ConfigRegistrySettings; 5 | import io.scalecube.config.StringConfigProperty; 6 | import io.scalecube.config.source.ClassPathConfigSource; 7 | import io.scalecube.config.source.SystemPropertiesConfigSource; 8 | import java.nio.file.Path; 9 | import java.util.function.Predicate; 10 | import java.util.stream.Collectors; 11 | import java.util.stream.Stream; 12 | 13 | @SuppressWarnings("OptionalGetWithoutIsPresent") 14 | public class PredicateOrderingConfigExample { 15 | 16 | /** 17 | * Main method for example of predicate ordering. 18 | * 19 | * @param args program arguments 20 | */ 21 | public static void main(String[] args) { 22 | Predicate propsPredicate = path -> path.toString().endsWith(".props"); 23 | Predicate rootPredicate = 24 | propsPredicate.and(path -> path.toString().contains("config.props")); 25 | Predicate firstPredicate = propsPredicate.and(path -> path.toString().contains("order1")); 26 | Predicate secondPredicate = 27 | propsPredicate.and(path -> path.toString().contains("order2")); 28 | Predicate customSysPredicate = 29 | propsPredicate.and(path -> path.toString().contains("customSys")); 30 | 31 | // Emulate scenario where sys.foo was also given from system properties 32 | // System.setProperty("sys.foo", "sys foo from java system properties"); 33 | 34 | ConfigRegistry configRegistry = 35 | ConfigRegistry.create( 36 | ConfigRegistrySettings.builder() 37 | .addLastSource("sysProps", new SystemPropertiesConfigSource()) 38 | .addLastSource( 39 | "customSysProps", 40 | new SystemPropertiesConfigSource(new ClassPathConfigSource(customSysPredicate))) 41 | .addLastSource( 42 | "classpath", 43 | new ClassPathConfigSource( 44 | Stream.of(firstPredicate, secondPredicate, rootPredicate) 45 | .collect(Collectors.toList()))) 46 | .build()); 47 | 48 | StringConfigProperty orderedProp1 = configRegistry.stringProperty("orderedProp1"); 49 | String foo = configRegistry.stringProperty("foo").valueOrThrow(); 50 | String bar = configRegistry.stringProperty("bar").valueOrThrow(); 51 | String sysFoo = configRegistry.stringProperty("sys.foo").valueOrThrow(); 52 | 53 | System.out.println( 54 | "### Matched by first predicate: orderedProp1=" + orderedProp1.value().get()); 55 | System.out.println("### Regardeless of predicates: foo=" + foo + ", bar=" + bar); 56 | System.out.println( 57 | "### Custom system property: sysFoo=" 58 | + sysFoo 59 | + ", System.getProperty(sysFoo)=" 60 | + System.getProperty("sys.foo")); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /config-examples/src/main/java/io/scalecube/config/examples/PredicateShortcutsConfigExample.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.config.examples; 2 | 3 | import io.scalecube.config.ConfigRegistry; 4 | import io.scalecube.config.ConfigRegistrySettings; 5 | import io.scalecube.config.StringConfigProperty; 6 | import io.scalecube.config.source.ClassPathConfigSource; 7 | import io.scalecube.config.source.SystemPropertiesConfigSource; 8 | import java.util.Arrays; 9 | 10 | @SuppressWarnings("OptionalGetWithoutIsPresent") 11 | public class PredicateShortcutsConfigExample { 12 | 13 | /** 14 | * Main method for example of predicate ordering. 15 | * 16 | * @param args program arguments 17 | */ 18 | public static void main(String[] args) { 19 | // Emulate scenario where sys.foo was also given from system properties 20 | // System.setProperty("sys.foo", "sys foo from java system properties"); 21 | 22 | String filename = "config.props"; 23 | 24 | ConfigRegistry configRegistry = 25 | ConfigRegistry.create( 26 | ConfigRegistrySettings.builder() 27 | .addLastSource("system", new SystemPropertiesConfigSource()) 28 | .addLastSource( 29 | "system.from.file", 30 | new SystemPropertiesConfigSource( 31 | ClassPathConfigSource.createWithPattern( 32 | filename, Arrays.asList("system.override", "system")))) 33 | .addLastSource( 34 | "classpath", 35 | ClassPathConfigSource.createWithPattern( 36 | filename, Arrays.asList("order.override", "order"))) 37 | .build()); 38 | 39 | StringConfigProperty orderedProp1 = configRegistry.stringProperty("orderedProp1"); 40 | String foo = configRegistry.stringProperty("foo").valueOrThrow(); 41 | String bar = configRegistry.stringProperty("bar").valueOrThrow(); 42 | String baz = configRegistry.stringProperty("baz").valueOrThrow(); 43 | String sysFoo = configRegistry.stringProperty("sys.foo").valueOrThrow(); 44 | 45 | System.out.println( 46 | "### Matched by first predicate: orderedProp1=" + orderedProp1.value().get()); 47 | System.out.println("### By predicates: foo=" + foo + ", bar=" + bar + ", baz=" + baz); 48 | System.out.println( 49 | "### Custom system property: sysFoo=" 50 | + sysFoo 51 | + ", System.getProperty(sysFoo)=" 52 | + System.getProperty("sys.foo")); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /config-examples/src/main/java/io/scalecube/config/examples/ReloadableLocalResourceConfigExample.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.config.examples; 2 | 3 | import io.scalecube.config.ConfigRegistry; 4 | import io.scalecube.config.ConfigRegistrySettings; 5 | import io.scalecube.config.DurationConfigProperty; 6 | import io.scalecube.config.ListConfigProperty; 7 | import io.scalecube.config.ObjectConfigProperty; 8 | import io.scalecube.config.StringConfigProperty; 9 | import io.scalecube.config.audit.LoggingConfigEventListener; 10 | import io.scalecube.config.source.FileDirectoryConfigSource; 11 | import java.io.BufferedWriter; 12 | import java.io.File; 13 | import java.io.FileWriter; 14 | import java.io.IOException; 15 | import java.nio.file.Path; 16 | import java.util.HashMap; 17 | import java.util.List; 18 | import java.util.Map; 19 | import java.util.concurrent.TimeUnit; 20 | import java.util.function.Predicate; 21 | import java.util.stream.Collectors; 22 | import java.util.stream.Stream; 23 | 24 | @SuppressWarnings("OptionalGetWithoutIsPresent") 25 | public class ReloadableLocalResourceConfigExample { 26 | 27 | /** 28 | * Main method of example of reloadable config. 29 | * 30 | * @param args program arguments 31 | * @throws Exception exception 32 | */ 33 | public static void main(String[] args) throws Exception { 34 | Predicate reloadablePropsPredicate = path -> path.toString().endsWith(".reloadableProps"); 35 | Predicate propsPredicate = path -> path.toString().endsWith(".props"); 36 | 37 | String basePath = "config-examples/config"; 38 | 39 | ConfigRegistry configRegistry = 40 | ConfigRegistry.create( 41 | ConfigRegistrySettings.builder() 42 | .addLastSource( 43 | "configDirectory", 44 | new FileDirectoryConfigSource( 45 | basePath, 46 | Stream.of(reloadablePropsPredicate, propsPredicate) 47 | .collect(Collectors.toList()))) 48 | .addListener(new LoggingConfigEventListener()) 49 | .reloadIntervalSec(1) 50 | .build()); 51 | 52 | StringConfigProperty prop1 = configRegistry.stringProperty("prop1"); 53 | System.out.println("### Initial filesystem config property: prop1=" + prop1.value().get()); 54 | prop1.addCallback( 55 | (s1, s2) -> 56 | System.out.println( 57 | "### Callback called for 'prop1' and value updated from='" 58 | + s1 59 | + "' to='" 60 | + s2 61 | + "'")); 62 | 63 | ObjectConfigProperty objectProperty = 64 | configRegistry.objectProperty( 65 | new HashMap() { 66 | { 67 | put("anInt", "reloadable.config.test.intProp"); 68 | put("theList", "reloadable.config.test.listProp"); 69 | } 70 | }, 71 | ObjectConfig.class); 72 | objectProperty.addCallback( 73 | (config1, config2) -> 74 | System.out.println( 75 | "### Callback called for objectProperty and value updated from='" 76 | + config1 77 | + "' to='" 78 | + config2 79 | + "'")); 80 | 81 | File file = createConfigFile(basePath); 82 | writeProperties( 83 | file, 84 | new HashMap() { 85 | { 86 | put("prop1", "42"); 87 | } 88 | }); 89 | TimeUnit.SECONDS.sleep(2); 90 | System.out.println("### Property reloaded: prop1=" + prop1.value().get()); 91 | 92 | writeProperties( 93 | file, 94 | new HashMap() { 95 | { 96 | put("prop1", ""); 97 | } 98 | }); 99 | TimeUnit.SECONDS.sleep(2); 100 | System.out.println("### Property reloaded again: prop1=" + prop1.value().get()); 101 | 102 | writeProperties( 103 | file, 104 | new HashMap() { 105 | { 106 | put("reloadable.config.test.intProp", "1"); 107 | put("reloadable.config.test.listProp", "a,b,c"); 108 | } 109 | }); 110 | TimeUnit.SECONDS.sleep(2); 111 | System.out.println("### Object property reloaded: " + objectProperty.value().get()); 112 | 113 | file.delete(); 114 | TimeUnit.SECONDS.sleep(2); 115 | System.out.println( 116 | "### Property reloaded again and back to its very intial value: prop1=" 117 | + prop1.value().get()); 118 | 119 | DurationConfigProperty propertyDuration = configRegistry.durationProperty("propertyDuration"); 120 | System.out.println( 121 | "### Property duration (showing in millis): " + propertyDuration.value().get().toMillis()); 122 | 123 | DurationConfigProperty propertyEnhancedDuration = 124 | configRegistry.durationProperty("propertyEnhancedDuration"); 125 | System.out.println( 126 | "### Property enhanced duration (showing in millis): " 127 | + propertyEnhancedDuration.value().get().toMillis()); 128 | 129 | ListConfigProperty propertyList1 = configRegistry.stringListProperty("propertyList1"); 130 | System.out.println("### Property type-list (string): " + propertyList1.value().get()); 131 | 132 | ListConfigProperty propertyList2 = configRegistry.doubleListProperty("propertyList2"); 133 | System.out.println("### Property type-list (double): " + propertyList2.value().get()); 134 | } 135 | 136 | private static void writeProperties(File file, Map props) throws IOException { 137 | try (BufferedWriter writer = new BufferedWriter(new FileWriter(file))) { 138 | props.forEach( 139 | (key, value) -> { 140 | try { 141 | writer.write(key + "=" + value + "\n"); 142 | } catch (IOException e) { 143 | throw new RuntimeException(e); 144 | } 145 | }); 146 | writer.flush(); 147 | } 148 | } 149 | 150 | private static File createConfigFile(String basePath) { 151 | File file = new File(basePath + "/config.reloadableProps"); 152 | file.deleteOnExit(); 153 | return file; 154 | } 155 | 156 | public static class ObjectConfig { 157 | private int anInt; 158 | private List theList; 159 | 160 | public int getAnInt() { 161 | return anInt; 162 | } 163 | 164 | public List getTheList() { 165 | return theList; 166 | } 167 | 168 | @Override 169 | public String toString() { 170 | return "ObjectConfig{" + "anInt=" + anInt + ", theList=" + theList + '}'; 171 | } 172 | } 173 | } 174 | -------------------------------------------------------------------------------- /config-examples/src/main/java/io/scalecube/config/examples/SourceOrderExample.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.config.examples; 2 | 3 | import io.scalecube.config.ConfigRegistry; 4 | import io.scalecube.config.ConfigRegistrySettings; 5 | import io.scalecube.config.source.ClassPathConfigSource; 6 | import java.nio.file.Path; 7 | import java.util.function.Predicate; 8 | 9 | /** 10 | * Source order example. 11 | * 12 | * @author Anton Kharenko 13 | */ 14 | public class SourceOrderExample { 15 | 16 | /** 17 | * Main method of example of source ordering. 18 | * 19 | * @param args program arguments 20 | */ 21 | public static void main(String[] args) { 22 | Predicate propsPredicate = path -> path.toString().endsWith(".props"); 23 | ConfigRegistry configRegistry = 24 | ConfigRegistry.create( 25 | ConfigRegistrySettings.builder() 26 | .addLastSource("classpath#1", new ClassPathConfigSource(propsPredicate)) 27 | .addFirstSource("classpath#0", new ClassPathConfigSource(propsPredicate)) 28 | .addLastSource("classpath#3", new ClassPathConfigSource(propsPredicate)) 29 | .addBeforeSource( 30 | "classpath#3", "classpath#2", new ClassPathConfigSource(propsPredicate)) 31 | .build()); 32 | 33 | System.out.println("### Sources: \n" + configRegistry.getConfigSources()); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /config-examples/src/main/resources/config.props: -------------------------------------------------------------------------------- 1 | foo=foo 2 | bar=bar 3 | baz=baz 4 | -------------------------------------------------------------------------------- /config-examples/src/main/resources/log4j2.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | %level{length=1} %date{MMdd-HHmm:ss,SSS} %logger{1.} %message [%thread]%n 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /config-examples/src/main/resources/order.config.props: -------------------------------------------------------------------------------- 1 | orderedProp1=this is orderedProp1 2 | -------------------------------------------------------------------------------- /config-examples/src/main/resources/order.override.config.props: -------------------------------------------------------------------------------- 1 | orderedProp1=this is orderedProp1 from override 2 | -------------------------------------------------------------------------------- /config-examples/src/main/resources/system.config.props: -------------------------------------------------------------------------------- 1 | sys.foo=this is sys.foo 2 | baz=this is baz 3 | -------------------------------------------------------------------------------- /config-examples/src/main/resources/system.override.config.props: -------------------------------------------------------------------------------- 1 | sys.foo=this is sys.foo from override 2 | baz=this is baz from override 3 | -------------------------------------------------------------------------------- /config-vault/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | 6 | io.scalecube 7 | scalecube-config-parent 8 | 0.5.2-SNAPSHOT 9 | 10 | 11 | scalecube-config-vault 12 | 13 | 14 | 15 | io.scalecube 16 | scalecube-config 17 | ${project.version} 18 | 19 | 20 | com.bettercloud 21 | vault-java-driver 22 | 23 | 24 | org.slf4j 25 | slf4j-api 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /config-vault/src/main/java/io/scalecube/config/vault/EnvironmentVaultTokenSupplier.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.config.vault; 2 | 3 | import com.bettercloud.vault.VaultConfig; 4 | import java.util.Objects; 5 | 6 | public class EnvironmentVaultTokenSupplier implements VaultTokenSupplier { 7 | 8 | public String getToken(VaultConfig config) { 9 | return Objects.requireNonNull(config.getToken(), "VaultConfig.token is missing"); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /config-vault/src/main/java/io/scalecube/config/vault/KubernetesVaultTokenSupplier.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.config.vault; 2 | 3 | import com.bettercloud.vault.EnvironmentLoader; 4 | import com.bettercloud.vault.Vault; 5 | import com.bettercloud.vault.VaultConfig; 6 | import io.scalecube.config.utils.ThrowableUtil; 7 | import java.nio.file.Files; 8 | import java.nio.file.Paths; 9 | import java.util.Objects; 10 | import java.util.Optional; 11 | import java.util.stream.Collectors; 12 | import java.util.stream.Stream; 13 | 14 | public class KubernetesVaultTokenSupplier implements VaultTokenSupplier { 15 | 16 | private static final EnvironmentLoader ENVIRONMENT_LOADER = new EnvironmentLoader(); 17 | 18 | private final String vaultRole; 19 | private final String vaultJwtProvider; 20 | private final String serviceAccountTokenPath; 21 | 22 | private KubernetesVaultTokenSupplier(Builder builder) { 23 | this.vaultRole = Objects.requireNonNull(builder.vaultRole, "vault role"); 24 | this.vaultJwtProvider = Objects.requireNonNull(builder.vaultJwtProvider, "jwt provider"); 25 | this.serviceAccountTokenPath = 26 | Objects.requireNonNull(builder.serviceAccountTokenPath, "k8s service account token path"); 27 | } 28 | 29 | public static Builder builder() { 30 | return new Builder(); 31 | } 32 | 33 | public static KubernetesVaultTokenSupplier newInstance() { 34 | return builder().build(); 35 | } 36 | 37 | @Override 38 | public String getToken(VaultConfig config) { 39 | try (Stream stream = Files.lines(Paths.get(serviceAccountTokenPath))) { 40 | String jwt = stream.collect(Collectors.joining()); 41 | return new Vault(config) 42 | .auth() 43 | .loginByJwt(vaultJwtProvider, vaultRole, jwt) 44 | .getAuthClientToken(); 45 | } catch (Exception e) { 46 | throw ThrowableUtil.propagate(e); 47 | } 48 | } 49 | 50 | public static class Builder { 51 | 52 | private String vaultRole = ENVIRONMENT_LOADER.loadVariable("VAULT_ROLE"); 53 | 54 | private String vaultJwtProvider = 55 | Optional.ofNullable( 56 | Optional.ofNullable(ENVIRONMENT_LOADER.loadVariable("VAULT_JWT_PROVIDER")) 57 | .orElse(ENVIRONMENT_LOADER.loadVariable("VAULT_MOUNT_POINT"))) 58 | .orElse("kubernetes"); 59 | 60 | private String serviceAccountTokenPath = 61 | Optional.ofNullable(ENVIRONMENT_LOADER.loadVariable("SERVICE_ACCOUNT_TOKEN_PATH")) 62 | .orElse("/var/run/secrets/kubernetes.io/serviceaccount/token"); 63 | 64 | private Builder() {} 65 | 66 | public Builder vaultRole(String vaultRole) { 67 | this.vaultRole = vaultRole; 68 | return this; 69 | } 70 | 71 | public Builder vaultJwtProvider(String vaultJwtProvider) { 72 | this.vaultJwtProvider = vaultJwtProvider; 73 | return this; 74 | } 75 | 76 | public Builder serviceAccountTokenPath(String serviceAccountTokenPath) { 77 | this.serviceAccountTokenPath = serviceAccountTokenPath; 78 | return this; 79 | } 80 | 81 | public KubernetesVaultTokenSupplier build() { 82 | return new KubernetesVaultTokenSupplier(this); 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /config-vault/src/main/java/io/scalecube/config/vault/VaultClientTokenSupplier.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.config.vault; 2 | 3 | import com.bettercloud.vault.VaultConfig; 4 | import com.bettercloud.vault.VaultException; 5 | import java.util.Objects; 6 | import java.util.concurrent.CompletableFuture; 7 | import org.slf4j.Logger; 8 | import org.slf4j.LoggerFactory; 9 | 10 | public class VaultClientTokenSupplier { 11 | 12 | private static final Logger LOGGER = LoggerFactory.getLogger(VaultClientTokenSupplier.class); 13 | 14 | private final String vaultAddress; 15 | private final String vaultToken; 16 | private final String vaultRole; 17 | 18 | /** 19 | * Constructor. 20 | * 21 | * @param vaultAddress vaultAddress 22 | * @param vaultToken vaultToken (must not set be together with {@code vaultRole}) 23 | * @param vaultRole vaultRole (must not set be together with {@code vaultToken}) 24 | */ 25 | public VaultClientTokenSupplier(String vaultAddress, String vaultToken, String vaultRole) { 26 | this.vaultAddress = vaultAddress; 27 | this.vaultToken = vaultToken; 28 | this.vaultRole = vaultRole; 29 | if (isNullOrNoneOrEmpty(vaultAddress)) { 30 | throw new IllegalArgumentException("Vault address is required"); 31 | } 32 | if (isNullOrNoneOrEmpty(vaultToken) && isNullOrNoneOrEmpty(vaultRole)) { 33 | throw new IllegalArgumentException( 34 | "Vault auth scheme is required (specify either vaultToken or vaultRole)"); 35 | } 36 | } 37 | 38 | /** 39 | * Returns new instance of {@link VaultClientTokenSupplier}. 40 | * 41 | * @param vaultAddress vaultAddress 42 | * @param vaultToken vaultToken 43 | * @return new instance of {@link VaultClientTokenSupplier} 44 | */ 45 | public static VaultClientTokenSupplier supplierByToken(String vaultAddress, String vaultToken) { 46 | return new VaultClientTokenSupplier(vaultAddress, vaultToken, null); 47 | } 48 | 49 | /** 50 | * Returns new instance of {@link VaultClientTokenSupplier}. 51 | * 52 | * @param vaultAddress vaultAddress 53 | * @param vaultRole vaultRole 54 | * @return new instance of {@link VaultClientTokenSupplier} 55 | */ 56 | public static VaultClientTokenSupplier supplierByRole(String vaultAddress, String vaultRole) { 57 | return new VaultClientTokenSupplier(vaultAddress, null, vaultRole); 58 | } 59 | 60 | /** 61 | * Obtains vault client token. 62 | * 63 | * @return future result 64 | */ 65 | public CompletableFuture getToken() { 66 | try { 67 | VaultTokenSupplier vaultTokenSupplier; 68 | VaultConfig vaultConfig; 69 | 70 | if (!isNullOrNoneOrEmpty(vaultRole)) { 71 | if (!isNullOrNoneOrEmpty(vaultToken)) { 72 | LOGGER.warn( 73 | "Taking KubernetesVaultTokenSupplier by precedence rule, " 74 | + "ignoring EnvironmentVaultTokenSupplier " 75 | + "(specify either vaultToken or vaultRole, not both)"); 76 | } 77 | vaultTokenSupplier = KubernetesVaultTokenSupplier.builder().vaultRole(vaultRole).build(); 78 | vaultConfig = new VaultConfig().address(vaultAddress).build(); 79 | } else { 80 | vaultTokenSupplier = new EnvironmentVaultTokenSupplier(); 81 | vaultConfig = new VaultConfig().address(vaultAddress).token(vaultToken).build(); 82 | } 83 | 84 | return CompletableFuture.supplyAsync(() -> vaultTokenSupplier.getToken(vaultConfig)); 85 | } catch (VaultException e) { 86 | throw new RuntimeException(e); 87 | } 88 | } 89 | 90 | private static boolean isNullOrNoneOrEmpty(String value) { 91 | return Objects.isNull(value) 92 | || "none".equalsIgnoreCase(value) 93 | || "null".equals(value) 94 | || value.isEmpty(); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /config-vault/src/main/java/io/scalecube/config/vault/VaultConfigSource.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.config.vault; 2 | 3 | import static io.scalecube.config.vault.VaultInvoker.STATUS_CODE_NOT_FOUND; 4 | 5 | import com.bettercloud.vault.EnvironmentLoader; 6 | import com.bettercloud.vault.VaultConfig; 7 | import com.bettercloud.vault.VaultException; 8 | import com.bettercloud.vault.response.LogicalResponse; 9 | import io.scalecube.config.ConfigProperty; 10 | import io.scalecube.config.ConfigSourceNotAvailableException; 11 | import io.scalecube.config.source.ConfigSource; 12 | import io.scalecube.config.source.LoadedConfigProperty; 13 | import java.util.ArrayList; 14 | import java.util.Arrays; 15 | import java.util.Collection; 16 | import java.util.HashMap; 17 | import java.util.HashSet; 18 | import java.util.Map; 19 | import java.util.Optional; 20 | import java.util.Set; 21 | import java.util.function.Function; 22 | import java.util.function.UnaryOperator; 23 | import java.util.stream.Collectors; 24 | import org.slf4j.Logger; 25 | import org.slf4j.LoggerFactory; 26 | 27 | /** 28 | * This class is an implementation of {@link ConfigSource} for Vault. 29 | * 30 | * @see Vault Project 31 | */ 32 | public class VaultConfigSource implements ConfigSource { 33 | 34 | private static final Logger LOGGER = LoggerFactory.getLogger(VaultConfigSource.class); 35 | 36 | private static final EnvironmentLoader ENVIRONMENT_LOADER = new EnvironmentLoader(); 37 | private static final String PATHS_SEPARATOR = ":"; 38 | 39 | private final VaultInvoker vault; 40 | private final Collection secretsPaths; 41 | 42 | private VaultConfigSource(VaultInvoker vault, Collection secretsPaths) { 43 | this.vault = vault; 44 | this.secretsPaths = new ArrayList<>(secretsPaths); 45 | } 46 | 47 | public static Builder builder() { 48 | return new Builder(); 49 | } 50 | 51 | @Override 52 | public Map loadConfig() { 53 | Map propertyMap = new HashMap<>(); 54 | for (String path : secretsPaths) { 55 | try { 56 | LogicalResponse response = vault.invoke(vault -> vault.logical().read(path)); 57 | final Map pathProps = 58 | response.getData().entrySet().stream() 59 | .map(LoadedConfigProperty::withNameAndValue) 60 | .map(LoadedConfigProperty.Builder::build) 61 | .collect(Collectors.toMap(LoadedConfigProperty::name, Function.identity())); 62 | propertyMap.putAll(pathProps); 63 | } catch (VaultException ex) { 64 | if (ex.getHttpStatusCode() == STATUS_CODE_NOT_FOUND) { 65 | LOGGER.error("Unable to load config properties from: {}", path); 66 | } else { 67 | throw new ConfigSourceNotAvailableException(ex); 68 | } 69 | } catch (Exception ex) { 70 | LOGGER.error("Unable to load config properties from: {}", path, ex); 71 | throw new ConfigSourceNotAvailableException(ex); 72 | } 73 | } 74 | return propertyMap; 75 | } 76 | 77 | public static final class Builder { 78 | 79 | private Function builderFunction = b -> b; 80 | 81 | private VaultInvoker invoker; 82 | 83 | private Set secretsPaths = 84 | Optional.ofNullable( 85 | Optional.ofNullable(ENVIRONMENT_LOADER.loadVariable("VAULT_SECRETS_PATH")) 86 | .orElse(ENVIRONMENT_LOADER.loadVariable("VAULT_SECRETS_PATHS"))) 87 | .map(s -> s.split(PATHS_SEPARATOR)) 88 | .map(Arrays::asList) 89 | .map(HashSet::new) 90 | .orElseGet(HashSet::new); 91 | 92 | private Builder() {} 93 | 94 | /** 95 | * Appends secrets paths (each path value may contain values separated by colons). 96 | * 97 | * @param secretsPath secretsPath 98 | * @return this 99 | */ 100 | public Builder addSecretsPath(String... secretsPath) { 101 | secretsPaths.addAll(toSecretsPaths(Arrays.asList(secretsPath))); 102 | return this; 103 | } 104 | 105 | /** 106 | * Setter for secrets paths (each path value may contain values separated by colons). 107 | * 108 | * @param secretsPaths secretsPaths 109 | * @return this 110 | */ 111 | public Builder secretsPaths(Collection secretsPaths) { 112 | this.secretsPaths = toSecretsPaths(secretsPaths); 113 | return this; 114 | } 115 | 116 | private static Set toSecretsPaths(Collection secretsPaths) { 117 | return secretsPaths.stream() 118 | .flatMap(s -> Arrays.stream(s.split(PATHS_SEPARATOR))) 119 | .collect(Collectors.toSet()); 120 | } 121 | 122 | /** 123 | * Setter for {@link VaultInvoker}. 124 | * 125 | * @param vaultInvoker vaultInvoker 126 | * @return this 127 | */ 128 | public Builder invoker(VaultInvoker vaultInvoker) { 129 | this.invoker = vaultInvoker; 130 | return this; 131 | } 132 | 133 | /** 134 | * Setter for {@link VaultInvoker.Builder} operator. 135 | * 136 | * @param operator operator for {@link VaultInvoker.Builder} 137 | * @return this 138 | */ 139 | public Builder vault(UnaryOperator operator) { 140 | this.builderFunction = this.builderFunction.andThen(operator); 141 | return this; 142 | } 143 | 144 | /** 145 | * Setter for {@link VaultConfig}. 146 | * 147 | * @param vaultConfig vaultConfig 148 | * @return this 149 | */ 150 | public Builder config(UnaryOperator vaultConfig) { 151 | this.builderFunction = this.builderFunction.andThen(b -> b.options(vaultConfig)); 152 | return this; 153 | } 154 | 155 | /** 156 | * Setter for {@link VaultTokenSupplier}. 157 | * 158 | * @param tokenSupplier tokenSupplier 159 | * @return this 160 | */ 161 | public Builder tokenSupplier(VaultTokenSupplier tokenSupplier) { 162 | this.builderFunction = this.builderFunction.andThen(b -> b.tokenSupplier(tokenSupplier)); 163 | return this; 164 | } 165 | 166 | public VaultConfigSource build() { 167 | return new VaultConfigSource( 168 | invoker != null ? invoker : builderFunction.apply(VaultInvoker.builder()).build(), 169 | secretsPaths); 170 | } 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /config-vault/src/main/java/io/scalecube/config/vault/VaultInvoker.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.config.vault; 2 | 3 | import com.bettercloud.vault.EnvironmentLoader; 4 | import com.bettercloud.vault.Vault; 5 | import com.bettercloud.vault.VaultConfig; 6 | import com.bettercloud.vault.VaultException; 7 | import com.bettercloud.vault.response.AuthResponse; 8 | import com.bettercloud.vault.response.LookupResponse; 9 | import com.bettercloud.vault.response.VaultResponse; 10 | import com.bettercloud.vault.rest.RestResponse; 11 | import io.scalecube.config.utils.ThrowableUtil; 12 | import java.nio.charset.StandardCharsets; 13 | import java.util.Objects; 14 | import java.util.Optional; 15 | import java.util.Timer; 16 | import java.util.TimerTask; 17 | import java.util.concurrent.TimeUnit; 18 | import java.util.function.Function; 19 | import java.util.function.UnaryOperator; 20 | import org.slf4j.Logger; 21 | import org.slf4j.LoggerFactory; 22 | 23 | public class VaultInvoker { 24 | 25 | private static final Logger LOGGER = LoggerFactory.getLogger(VaultInvoker.class); 26 | 27 | public static final int STATUS_CODE_FORBIDDEN = 403; 28 | public static final int STATUS_CODE_NOT_FOUND = 404; 29 | public static final int STATUS_CODE_HEALTH_OK = 200; 30 | public static final int STATUS_CODE_RESPONSE_OK = 200; 31 | public static final int STATUS_CODE_RESPONSE_NO_DATA = 204; 32 | 33 | private static final long MIN_REFRESH_MARGIN = TimeUnit.MINUTES.toSeconds(10); 34 | 35 | private final Builder builder; 36 | 37 | private Vault vault; 38 | private Timer timer; 39 | 40 | private VaultInvoker(Builder builder) { 41 | this.builder = builder; 42 | } 43 | 44 | public static Builder builder() { 45 | return new Builder(); 46 | } 47 | 48 | /** 49 | * Invokes a given call with vault. 50 | * 51 | * @param call call 52 | * @return vault response 53 | */ 54 | public T invoke(VaultCall call) throws VaultException { 55 | Vault vault = this.vault; 56 | try { 57 | if (vault == null) { 58 | vault = recreateVault(null); 59 | } 60 | T response = call.apply(vault); 61 | checkResponse(response.getRestResponse()); 62 | return response; 63 | } catch (VaultException e) { 64 | // try recreate Vault according to https://www.vaultproject.io/api/overview#http-status-codes 65 | if (e.getHttpStatusCode() == STATUS_CODE_FORBIDDEN) { 66 | LOGGER.warn( 67 | "Authentication failed (error message: {}), now trying to recreate vault", 68 | e.getMessage()); 69 | vault = recreateVault(vault); 70 | return call.apply(vault); 71 | } 72 | throw e; 73 | } 74 | } 75 | 76 | private synchronized Vault recreateVault(Vault prev) throws VaultException { 77 | try { 78 | if (!Objects.equals(prev, vault) && vault != null) { 79 | return vault; 80 | } 81 | if (timer != null) { 82 | timer.cancel(); 83 | timer = null; 84 | } 85 | vault = null; 86 | 87 | VaultConfig vaultConfig = 88 | builder 89 | .options 90 | .apply(new VaultConfig().environmentLoader(new EnvironmentLoader())) 91 | .build(); 92 | String token = builder.tokenSupplier.getToken(vaultConfig); 93 | Vault vault = new Vault(vaultConfig.token(token)); 94 | checkVault(vault); 95 | LookupResponse lookupSelf = vault.auth().lookupSelf(); 96 | if (lookupSelf.isRenewable()) { 97 | long ttl = lookupSelf.getTTL(); 98 | long delay = TimeUnit.SECONDS.toMillis(suggestedRefreshInterval(ttl)); 99 | timer = new Timer("VaultScheduler", true); 100 | timer.schedule(new RenewTokenTask(), delay); 101 | LOGGER.info("Renew token timer was set to {}s, (TTL = {}s)", delay, ttl); 102 | } else { 103 | LOGGER.warn("Vault token is not renewable"); 104 | } 105 | this.vault = vault; 106 | } catch (VaultException e) { 107 | LOGGER.error("Could not initialize and validate the vault", e); 108 | throw e; 109 | } 110 | return vault; 111 | } 112 | 113 | private void renewToken() throws VaultException { 114 | Vault vault = this.vault; 115 | if (vault == null) { 116 | return; 117 | } 118 | try { 119 | AuthResponse response = vault.auth().renewSelf(); 120 | long ttl = response.getAuthLeaseDuration(); 121 | LOGGER.debug("Token was successfully renewed (new TTL = {}s)", ttl); 122 | if (response.isAuthRenewable()) { 123 | if (ttl > 1) { 124 | long delay = TimeUnit.SECONDS.toMillis(suggestedRefreshInterval(ttl)); 125 | timer.schedule(new RenewTokenTask(), delay); 126 | } else { 127 | LOGGER.warn("Token TTL ({}s) is not enough for scheduling", ttl); 128 | vault = recreateVault(vault); 129 | } 130 | } else { 131 | LOGGER.warn("Vault token is not renewable now"); 132 | } 133 | } catch (VaultException e) { 134 | // try recreate Vault according to https://www.vaultproject.io/api/overview#http-status-codes 135 | if (e.getHttpStatusCode() == STATUS_CODE_FORBIDDEN) { 136 | LOGGER.warn("Could not renew the Vault token", e); 137 | //noinspection UnusedAssignment 138 | vault = recreateVault(vault); 139 | } 140 | } 141 | } 142 | 143 | /** 144 | * Checks vault is active. See 145 | * https://www.vaultproject.io/api/system/health.html#read-health-information. 146 | * 147 | * @param vault vault 148 | */ 149 | private void checkVault(Vault vault) throws VaultException { 150 | RestResponse restResponse = vault.debug().health(true, null, null, null).getRestResponse(); 151 | if (restResponse.getStatus() == STATUS_CODE_HEALTH_OK) { 152 | return; 153 | } 154 | throw new VaultException(bodyAsString(restResponse), restResponse.getStatus()); 155 | } 156 | 157 | /** 158 | * Checks rest response. See https://www.vaultproject.io/api/overview#http-status-codes. 159 | * 160 | * @param restResponse rest response 161 | */ 162 | private void checkResponse(RestResponse restResponse) throws VaultException { 163 | if (restResponse == null) { 164 | return; 165 | } 166 | int status = restResponse.getStatus(); 167 | switch (status) { 168 | case STATUS_CODE_RESPONSE_OK: 169 | case STATUS_CODE_RESPONSE_NO_DATA: 170 | return; 171 | default: 172 | LOGGER.warn("Vault responded with code: {}", status); 173 | throw new VaultException(bodyAsString(restResponse), status); 174 | } 175 | } 176 | 177 | /** 178 | * We should refresh tokens from Vault before they expire, so we add a MIN_REFRESH_MARGIN margin. 179 | * If the token is valid for less than MIN_REFRESH_MARGIN * 2, we use duration / 2 instead. 180 | */ 181 | private static long suggestedRefreshInterval(long duration) { 182 | return duration < MIN_REFRESH_MARGIN * 2 ? duration / 2 : duration - MIN_REFRESH_MARGIN; 183 | } 184 | 185 | private static String bodyAsString(RestResponse response) { 186 | return new String(response.getBody(), StandardCharsets.UTF_8); 187 | } 188 | 189 | @FunctionalInterface 190 | public interface VaultCall { 191 | 192 | T apply(Vault vault) throws VaultException; 193 | } 194 | 195 | private class RenewTokenTask extends TimerTask { 196 | 197 | @Override 198 | public void run() { 199 | try { 200 | renewToken(); 201 | } catch (Exception e) { 202 | throw ThrowableUtil.propagate(e); 203 | } 204 | } 205 | } 206 | 207 | public static class Builder { 208 | 209 | private static final int OPEN_TIMEOUT_SEC = 210 | Optional.ofNullable(System.getenv("VAULT_OPEN_TIMEOUT")).map(Integer::parseInt).orElse(10); 211 | 212 | private static final int READ_TIMEOUT_SEC = 213 | Optional.ofNullable(System.getenv("VAULT_READ_TIMEOUT")).map(Integer::parseInt).orElse(10); 214 | 215 | private Function options = 216 | config -> config.openTimeout(OPEN_TIMEOUT_SEC).readTimeout(READ_TIMEOUT_SEC); 217 | 218 | private VaultTokenSupplier tokenSupplier = new EnvironmentVaultTokenSupplier(); 219 | 220 | private Builder() {} 221 | 222 | /** 223 | * Setter for {@link VaultConfig} operator. 224 | * 225 | * @param operator operator for {@link VaultConfig} 226 | * @return this 227 | */ 228 | public Builder options(UnaryOperator operator) { 229 | options = options.andThen(operator); 230 | return this; 231 | } 232 | 233 | /** 234 | * Setter for {@link VaultTokenSupplier}. 235 | * 236 | * @param supplier vault token supplier 237 | * @return this 238 | */ 239 | public Builder tokenSupplier(VaultTokenSupplier supplier) { 240 | tokenSupplier = supplier; 241 | return this; 242 | } 243 | 244 | /** 245 | * Builds vault invoker. 246 | * 247 | * @return instance of {@link VaultInvoker} 248 | */ 249 | public VaultInvoker build() { 250 | Builder builder = new Builder(); 251 | builder.options = options; 252 | builder.tokenSupplier = tokenSupplier; 253 | return new VaultInvoker(builder); 254 | } 255 | } 256 | } 257 | -------------------------------------------------------------------------------- /config-vault/src/main/java/io/scalecube/config/vault/VaultInvokers.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.config.vault; 2 | 3 | import java.util.Map; 4 | import java.util.Objects; 5 | import org.slf4j.Logger; 6 | import org.slf4j.LoggerFactory; 7 | 8 | public class VaultInvokers { 9 | 10 | public static final Logger LOGGER = LoggerFactory.getLogger(VaultInvokers.class); 11 | 12 | public static final String VAULT_MOUNT_POINT_ENV = "VAULT_MOUNT_POINT"; 13 | public static final String VAULT_ADDR_ENV = "VAULT_ADDR"; 14 | public static final String VAULT_TOKEN_ENV = "VAULT_TOKEN"; 15 | public static final String VAULT_ROLE_ENV = "VAULT_ROLE"; 16 | public static final String VAULT_JWT_PROVIDER_ENV = "VAULT_JWT_PROVIDER"; 17 | public static final String VAULT_ENGINE_VERSION_ENV = "VAULT_ENGINE_VERSION"; 18 | 19 | public static final String DEFAULT_VAULT_ENGINE_VERSION = "1"; 20 | 21 | private VaultInvokers() { 22 | // Do not instantiate 23 | } 24 | 25 | /** 26 | * Creates {@link VaultInvoker}, or throws error if {@link VaultInvoker} instance cannot be 27 | * created. 28 | * 29 | * @return new {@code VaultInvoker} instance, or throws error 30 | */ 31 | public static VaultInvoker newVaultInvokerOrThrow() { 32 | final VaultInvoker vaultInvoker = newVaultInvoker(); 33 | if (vaultInvoker == null) { 34 | throw new IllegalStateException("Cannot create vaultInvoker"); 35 | } 36 | return vaultInvoker; 37 | } 38 | 39 | /** 40 | * Creates and returns new {@link VaultInvoker} instance. 41 | * 42 | * @return new {@code VaultInvoker} instance 43 | */ 44 | public static VaultInvoker newVaultInvoker() { 45 | Map env = System.getenv(); 46 | 47 | final String vaultAddr = env.get(VAULT_ADDR_ENV); 48 | final int vaultEngineVersion = 49 | Integer.parseInt(env.getOrDefault(VAULT_ENGINE_VERSION_ENV, DEFAULT_VAULT_ENGINE_VERSION)); 50 | 51 | if (isNullOrNone(vaultAddr)) { 52 | return null; 53 | } 54 | 55 | String vaultToken = env.get(VAULT_TOKEN_ENV); 56 | String vaultRole = env.get(VAULT_ROLE_ENV); 57 | 58 | if (isNullOrNone(vaultToken) && isNullOrNone(vaultRole)) { 59 | throw new IllegalArgumentException( 60 | "Vault auth scheme is required (specify either VAULT_ROLE or VAULT_TOKEN)"); 61 | } 62 | 63 | final VaultInvoker.Builder builder = 64 | VaultInvoker.builder() 65 | .options(config -> config.address(vaultAddr).engineVersion(vaultEngineVersion)); 66 | 67 | if (!isNullOrNone(vaultRole)) { 68 | if (!isNullOrNone(vaultToken)) { 69 | LOGGER.warn( 70 | "Taking KubernetesVaultTokenSupplier by precedence rule, " 71 | + "ignoring EnvironmentVaultTokenSupplier " 72 | + "(specify either VAULT_ROLE or VAULT_TOKEN, not both)"); 73 | } 74 | builder.tokenSupplier(KubernetesVaultTokenSupplier.newInstance()); 75 | } else { 76 | builder.tokenSupplier(new EnvironmentVaultTokenSupplier()); 77 | } 78 | 79 | return builder.build(); 80 | } 81 | 82 | private static boolean isNullOrNone(String value) { 83 | return Objects.isNull(value) || "none".equalsIgnoreCase(value); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /config-vault/src/main/java/io/scalecube/config/vault/VaultTokenSupplier.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.config.vault; 2 | 3 | import com.bettercloud.vault.VaultConfig; 4 | 5 | @FunctionalInterface 6 | public interface VaultTokenSupplier { 7 | 8 | String getToken(VaultConfig config); 9 | } 10 | -------------------------------------------------------------------------------- /config-vault/src/test/java/io/scalecube/config/vault/VaultContainerExtension.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.config.vault; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | import java.util.function.UnaryOperator; 6 | import org.junit.jupiter.api.extension.AfterAllCallback; 7 | import org.junit.jupiter.api.extension.AfterEachCallback; 8 | import org.junit.jupiter.api.extension.BeforeAllCallback; 9 | import org.junit.jupiter.api.extension.ExtensionContext; 10 | import org.testcontainers.vault.VaultContainer; 11 | 12 | public class VaultContainerExtension 13 | implements AfterAllCallback, BeforeAllCallback, AfterEachCallback { 14 | 15 | private VaultInstance vaultInstance; 16 | private List vaultInstances = new ArrayList<>(); 17 | 18 | @Override 19 | public void beforeAll(ExtensionContext context) { 20 | vaultInstance = startNewVaultInstance(); 21 | vaultInstances.clear(); 22 | } 23 | 24 | @Override 25 | public void afterAll(ExtensionContext context) { 26 | if (vaultInstance != null && vaultInstance.container().isRunning()) { 27 | vaultInstance.container().stop(); 28 | } 29 | } 30 | 31 | @Override 32 | public void afterEach(ExtensionContext context) { 33 | vaultInstances.forEach(VaultInstance::close); 34 | vaultInstances.clear(); 35 | } 36 | 37 | VaultInstance vaultInstance() { 38 | return vaultInstance; 39 | } 40 | 41 | VaultInstance startNewVaultInstance() { 42 | return startNewVaultInstance(UnaryOperator.identity()); 43 | } 44 | 45 | VaultInstance startNewVaultInstance(UnaryOperator function) { 46 | VaultInstance vaultInstance = VaultInstance.start(function); 47 | vaultInstances.add(vaultInstance); 48 | return vaultInstance; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /config-vault/src/test/java/io/scalecube/config/vault/VaultInstance.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.config.vault; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertEquals; 4 | 5 | import com.bettercloud.vault.SslConfig; 6 | import com.bettercloud.vault.Vault; 7 | import com.bettercloud.vault.VaultConfig; 8 | import com.bettercloud.vault.response.AuthResponse; 9 | import com.bettercloud.vault.rest.RestResponse; 10 | import io.scalecube.config.utils.ThrowableUtil; 11 | import java.nio.charset.StandardCharsets; 12 | import java.util.Objects; 13 | import java.util.UUID; 14 | import java.util.concurrent.Callable; 15 | import java.util.concurrent.TimeUnit; 16 | import java.util.concurrent.TimeoutException; 17 | import java.util.concurrent.atomic.AtomicInteger; 18 | import java.util.function.Predicate; 19 | import java.util.function.UnaryOperator; 20 | import org.testcontainers.DockerClientFactory; 21 | import org.testcontainers.containers.Container.ExecResult; 22 | import org.testcontainers.containers.ContainerLaunchException; 23 | import org.testcontainers.containers.output.OutputFrame; 24 | import org.testcontainers.containers.output.WaitingConsumer; 25 | import org.testcontainers.containers.wait.strategy.AbstractWaitStrategy; 26 | import org.testcontainers.utility.LogUtils; 27 | import org.testcontainers.vault.VaultContainer; 28 | 29 | public class VaultInstance implements AutoCloseable { 30 | 31 | private static final String VAULT_IMAGE_NAME = "vault:1.6.1"; 32 | private static final int VAULT_PORT = 8200; 33 | private static final AtomicInteger PORT_COUNTER = new AtomicInteger(VAULT_PORT); 34 | private static final String UNSEAL_KEY_LOG = "Unseal Key: "; 35 | private static final String ROOT_TOKEN_LOG = "Root Token: "; 36 | 37 | private final VaultContainer container; 38 | private final String unsealKey; 39 | private final String rootToken; 40 | 41 | private VaultInstance(VaultContainer container, String unsealKey, String rootToken) { 42 | this.container = container; 43 | this.unsealKey = Objects.requireNonNull(unsealKey, "unseal key"); 44 | this.rootToken = Objects.requireNonNull(rootToken, "root token"); 45 | } 46 | 47 | static VaultInstance start(UnaryOperator function) { 48 | VaultInstanceWaitStrategy waitStrategy = new VaultInstanceWaitStrategy(); 49 | VaultContainer container = 50 | function.apply( 51 | new VaultContainer<>(VAULT_IMAGE_NAME) 52 | .withVaultToken(UUID.randomUUID().toString()) 53 | .withVaultPort(PORT_COUNTER.incrementAndGet()) 54 | .waitingFor(waitStrategy)); 55 | container.start(); 56 | return new VaultInstance(container, waitStrategy.unsealKey, waitStrategy.rootToken); 57 | } 58 | 59 | public VaultContainer container() { 60 | return container; 61 | } 62 | 63 | public Vault vault() { 64 | return invoke( 65 | () -> { 66 | String vaultToken = container.getEnvMap().get("VAULT_TOKEN").toString(); 67 | VaultConfig config = 68 | new VaultConfig() 69 | .address(address()) 70 | .token(vaultToken) 71 | .openTimeout(5) 72 | .readTimeout(30) 73 | .sslConfig(new SslConfig().build()) 74 | .build(); 75 | return new Vault(config).withRetries(5, 1000); 76 | }); 77 | } 78 | 79 | public void putSecrets(String path, String firstSecret, String... remainingSecrets) { 80 | StringBuilder command = 81 | new StringBuilder() 82 | .append("vault kv put ") 83 | .append(path) 84 | .append(" ") 85 | .append(firstSecret) 86 | .append(" "); 87 | for (String secret : remainingSecrets) { 88 | command.append(secret).append(" "); 89 | } 90 | ExecResult execResult = 91 | invoke(() -> container.execInContainer("/bin/sh", "-c", command.toString())); 92 | assertEquals(0, execResult.getExitCode(), execResult.toString()); 93 | } 94 | 95 | /** 96 | * Creates a new token with given options. See 97 | * https://www.vaultproject.io/docs/commands/token/create.html. 98 | * 99 | * @param options command options, 100 | * https://www.vaultproject.io/docs/commands/token/create.html#command-options 101 | * @return key-value result outcome 102 | */ 103 | public AuthResponse createToken(String... options) { 104 | StringBuilder command = new StringBuilder().append("vault token create -format=json "); 105 | for (String secret : options) { 106 | command.append(secret).append(" "); 107 | } 108 | String stdout = execInContainer(command.toString()).replaceAll("\\r?\\n", ""); 109 | return new AuthResponse( 110 | new RestResponse(200, "application/json", stdout.getBytes(StandardCharsets.UTF_8)), 0); 111 | } 112 | 113 | public String execInContainer(String command) { 114 | ExecResult execResult = invoke(() -> container.execInContainer("/bin/sh", "-c", command)); 115 | assertEquals(0, execResult.getExitCode(), execResult.toString()); 116 | return execResult.getStdout(); 117 | } 118 | 119 | public String address() { 120 | return String.format( 121 | "http://%s:%d", container.getContainerIpAddress(), container.getMappedPort(VAULT_PORT)); 122 | } 123 | 124 | public String rootToken() { 125 | return rootToken; 126 | } 127 | 128 | public String unsealKey() { 129 | return unsealKey; 130 | } 131 | 132 | public void close() { 133 | container.close(); 134 | } 135 | 136 | private T invoke(Callable action) { 137 | try { 138 | return action.call(); 139 | } catch (Exception e) { 140 | throw ThrowableUtil.propagate(e); 141 | } 142 | } 143 | 144 | private static class VaultInstanceWaitStrategy extends AbstractWaitStrategy { 145 | 146 | private static final String VAULT_STARTED_LOG_MESSAGE = 147 | "==> Vault server started! Log data will stream in below:"; 148 | 149 | private boolean isVaultStarted; 150 | private String unsealKey; 151 | private String rootToken; 152 | 153 | @Override 154 | protected void waitUntilReady() { 155 | WaitingConsumer waitingConsumer = new WaitingConsumer(); 156 | LogUtils.followOutput( 157 | DockerClientFactory.instance().client(), 158 | waitStrategyTarget.getContainerId(), 159 | waitingConsumer); 160 | 161 | Predicate waitPredicate = 162 | outputFrame -> { 163 | String log = outputFrame.getUtf8String(); 164 | if (log.contains(UNSEAL_KEY_LOG)) { 165 | unsealKey = log.substring(UNSEAL_KEY_LOG.length()).replaceAll("\\r?\\n", ""); 166 | } 167 | if (log.contains(ROOT_TOKEN_LOG)) { 168 | rootToken = log.substring(ROOT_TOKEN_LOG.length()).replaceAll("\\r?\\n", ""); 169 | } 170 | if (log.contains(VAULT_STARTED_LOG_MESSAGE)) { 171 | isVaultStarted = true; 172 | } 173 | return isVaultStarted && unsealKey != null && rootToken != null; 174 | }; 175 | 176 | try { 177 | waitingConsumer.waitUntil(waitPredicate, startupTimeout.getSeconds(), TimeUnit.SECONDS, 1); 178 | } catch (TimeoutException e) { 179 | throw new ContainerLaunchException( 180 | "Timed out waiting for log output matching '" + VAULT_STARTED_LOG_MESSAGE + "'"); 181 | } 182 | } 183 | } 184 | } 185 | -------------------------------------------------------------------------------- /config-vault/src/test/resources/log4j2-test.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | %level{length=1} %d{ISO8601} %c{1.} %m [%t]%n 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /config/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | 6 | io.scalecube 7 | scalecube-config-parent 8 | 0.5.2-SNAPSHOT 9 | 10 | 11 | scalecube-config 12 | 13 | 14 | 15 | org.slf4j 16 | slf4j-api 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /config/src/main/java/io/scalecube/config/AbstractConfigProperty.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.config; 2 | 3 | import io.scalecube.config.source.LoadedConfigProperty; 4 | import java.util.Collection; 5 | import java.util.List; 6 | import java.util.Map; 7 | import java.util.Optional; 8 | import java.util.concurrent.CopyOnWriteArraySet; 9 | import java.util.concurrent.Executor; 10 | import java.util.function.BiConsumer; 11 | import java.util.function.Function; 12 | import java.util.function.Predicate; 13 | import java.util.stream.Collectors; 14 | import org.slf4j.Logger; 15 | import org.slf4j.LoggerFactory; 16 | 17 | /** 18 | * Abstract parent class for config property classes. Holds mutable state fields: {@link #value} the 19 | * parsed config property field, {@link #inputList} which is kind of a 'source' for parsed value. 20 | * Reload process may change volatile values of those fields, of course, if property value actually 21 | * changed and validation passed. Collections of validators and callbacks (of type {@link T}) are 22 | * defined here and only operations around them are shared to subclasses. 23 | * 24 | * @param type of the property value 25 | */ 26 | abstract class AbstractConfigProperty { 27 | 28 | private static final Logger LOGGER = LoggerFactory.getLogger(AbstractConfigProperty.class); 29 | 30 | private static final String ERROR_VALIDATION_FAILED = 31 | "Validation failed on config property: %s, failed value: %s"; 32 | 33 | final String name; 34 | final Class propertyClass; 35 | final Collection> validators = 36 | new CopyOnWriteArraySet<>(); // of type Set for a reason 37 | final Collection> callbacks = 38 | new CopyOnWriteArraySet<>(); // of type Set for a reason 39 | 40 | private PropertyCallback propertyCallback; // initialized from subclass 41 | private volatile T value; // initialized from subclass, reset in callback 42 | private volatile List 43 | inputList; // initialized from subclass, reset in callback 44 | 45 | AbstractConfigProperty(String name, Class propertyClass) { 46 | this.name = name; 47 | this.propertyClass = propertyClass; 48 | } 49 | 50 | public final String name() { 51 | return name; 52 | } 53 | 54 | public final Optional value() { 55 | return Optional.ofNullable(value); 56 | } 57 | 58 | public final void addValidator(Predicate validator) { 59 | if (!validator.test(value)) { 60 | throw new IllegalArgumentException(String.format(ERROR_VALIDATION_FAILED, name, value)); 61 | } 62 | validators.add(validator); 63 | } 64 | 65 | public final void addCallback(BiConsumer callback) { 66 | callbacks.add((t1, t2) -> invokeCallback(callback, t1, t2)); 67 | } 68 | 69 | public final void addCallback(Executor executor, BiConsumer callback) { 70 | callbacks.add((t1, t2) -> executor.execute(() -> invokeCallback(callback, t1, t2))); 71 | } 72 | 73 | /** 74 | * Binds this config property instance to given {@link PropertyCallback}. The latter is shared 75 | * among config property instances of the same type. 76 | * 77 | * @param propertyCallback propertyCallback of certain type. 78 | */ 79 | final void setPropertyCallback(PropertyCallback propertyCallback) { 80 | (this.propertyCallback = propertyCallback).addConfigProperty(this); 81 | } 82 | 83 | /** 84 | * This method is being called from subclasses to assign a {@link #value} with initial value. 85 | * 86 | * @see PropertyCallback#computeValue(List, AbstractConfigProperty) 87 | */ 88 | final void computeValue(List inputList) { 89 | propertyCallback.computeValue(inputList, this); 90 | } 91 | 92 | /** 93 | * Takes new value, validates it, resets {@code this.value} field and optionally calling 94 | * callbacks. 95 | * 96 | * @param value1 new value to set; may be null. 97 | * @param inputList1 valueParser input list; contains additional info such as source, origin and 98 | * string value representation which in fact had built up given {@code value1} param. 99 | * @param invokeCallbacks flag indicating whether it's needed to notify callbacks about changes. 100 | * @throws IllegalArgumentException in case new value fails against existing validators. 101 | */ 102 | final void acceptValue(T value1, List inputList1, boolean invokeCallbacks) { 103 | if ((value == null && value1 == null) || isInputsEqual(inputList1)) { 104 | return; 105 | } 106 | 107 | if (!validators.stream().allMatch(input -> input.test(value1))) { 108 | throw new IllegalArgumentException(String.format(ERROR_VALIDATION_FAILED, name, value)); 109 | } 110 | 111 | T t1 = value; 112 | T t2 = value = value1; 113 | 114 | inputList = inputList1; 115 | 116 | if (invokeCallbacks) { 117 | for (BiConsumer callback : callbacks) { 118 | callback.accept(t1, t2); 119 | } 120 | } 121 | } 122 | 123 | /** 124 | * Helper method which applies given {@code mapper} lambda to the {@link #inputList} (if any). For 125 | * example if one needs to retrieve more than just a {@link #value} info from this config 126 | * property, like source, origin and etc. 127 | */ 128 | final Optional mapToString(Function, String> mapper) { 129 | return Optional.ofNullable(inputList).map(mapper); 130 | } 131 | 132 | private void invokeCallback(BiConsumer callback, T t1, T t2) { 133 | try { 134 | callback.accept(t1, t2); 135 | } catch (Exception e) { 136 | LOGGER.error( 137 | "Exception occurred on property-change callback: " 138 | + "{}, property name: {}, oldValue: {}, newValue: {}", 139 | callback, 140 | name, 141 | t1, 142 | t2, 143 | e); 144 | } 145 | } 146 | 147 | protected boolean isMyProperty(LoadedConfigProperty property) { 148 | return this.name.equals(property.name()); 149 | } 150 | 151 | private boolean isInputsEqual(List inputList1) { 152 | if ((inputList == null && inputList1 != null) || (inputList != null && inputList1 == null)) { 153 | return false; 154 | } 155 | 156 | Map> inputMap = 157 | inputList.stream() 158 | .collect( 159 | Collectors.toMap(LoadedConfigProperty::name, LoadedConfigProperty::valueAsString)); 160 | 161 | Map> inputMap1 = 162 | inputList1.stream() 163 | .collect( 164 | Collectors.toMap(LoadedConfigProperty::name, LoadedConfigProperty::valueAsString)); 165 | 166 | return inputMap.equals(inputMap1); 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /config/src/main/java/io/scalecube/config/AbstractSimpleConfigProperty.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.config; 2 | 3 | import io.scalecube.config.source.LoadedConfigProperty; 4 | import java.util.Collections; 5 | import java.util.Map; 6 | import java.util.NoSuchElementException; 7 | import java.util.Optional; 8 | import java.util.concurrent.ConcurrentHashMap; 9 | import java.util.function.Function; 10 | 11 | /** 12 | * Parent class for 'simple' property types, such as: double, int, string, list and etc. 13 | * 14 | * @param type of the property value 15 | */ 16 | class AbstractSimpleConfigProperty extends AbstractConfigProperty implements ConfigProperty { 17 | 18 | /** Constructor for non-object config property. */ 19 | AbstractSimpleConfigProperty( 20 | String name, 21 | Class propertyClass, 22 | Map propertyMap, 23 | Map> propertyCallbackMap, 24 | Function valueParser) { 25 | 26 | super(name, propertyClass); 27 | 28 | // noinspection unchecked 29 | setPropertyCallback(computePropertyCallback(valueParser, propertyCallbackMap)); 30 | 31 | LoadedConfigProperty configProperty = propertyMap.get(name); 32 | computeValue(configProperty != null ? Collections.singletonList(configProperty) : null); 33 | } 34 | 35 | @Override 36 | public final Optional source() { 37 | return mapToString(list -> list.get(0).source().orElse(null)); 38 | } 39 | 40 | @Override 41 | public final Optional origin() { 42 | return mapToString(list -> list.get(0).origin().orElse(null)); 43 | } 44 | 45 | @Override 46 | public final Optional valueAsString() { 47 | return mapToString(list -> list.get(0).valueAsString().orElse(null)); 48 | } 49 | 50 | @Override 51 | public final String valueAsString(String defaultValue) { 52 | return valueAsString().orElse(defaultValue); 53 | } 54 | 55 | final NoSuchElementException newNoSuchElementException() { 56 | return new NoSuchElementException("Value is null for property '" + name + "'"); 57 | } 58 | 59 | private PropertyCallback computePropertyCallback( 60 | Function valueParser, 61 | Map> propertyCallbackMap) { 62 | 63 | PropertyCallback propertyCallback = 64 | new PropertyCallback<>(list -> list.get(0).valueAsString().map(valueParser).orElse(null)); 65 | 66 | propertyCallbackMap.putIfAbsent(name, new ConcurrentHashMap<>()); 67 | Map callbackMap = propertyCallbackMap.get(name); 68 | callbackMap.putIfAbsent(propertyClass, propertyCallback); 69 | return callbackMap.get(propertyClass); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /config/src/main/java/io/scalecube/config/BooleanConfigProperty.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.config; 2 | 3 | import java.util.NoSuchElementException; 4 | import java.util.Optional; 5 | import java.util.concurrent.Executor; 6 | import java.util.function.BiConsumer; 7 | import java.util.function.Predicate; 8 | 9 | public interface BooleanConfigProperty extends ConfigProperty { 10 | 11 | /** 12 | * Returns value. 13 | * 14 | * @return optional boolean value 15 | */ 16 | Optional value(); 17 | 18 | /** 19 | * Shortcut on {@code value().orElse(defaultValue)}. 20 | * 21 | * @return existing value or default 22 | */ 23 | boolean value(boolean defaultValue); 24 | 25 | /** 26 | * Returns existing value or throws {@link NoSuchElementException} if value is null. 27 | * 28 | * @return existing value or exception 29 | * @throws NoSuchElementException if value is null 30 | */ 31 | boolean valueOrThrow(); 32 | 33 | /** 34 | * Adds reload callback to the list. Callbacks will be invoked in the order they were added, and 35 | * only after validation have been passed. 36 | * 37 | * @param callback reload callback, 1st argument is old value 2nd one is new value, both are 38 | * nullable; though callback may throw exception, this wouldn't stop other callbacks from 39 | * execution 40 | */ 41 | void addCallback(BiConsumer callback); 42 | 43 | /** 44 | * Adds reload callback to the list. Callbacks will be invoked in the order they were added, and 45 | * only after validation have been passed. 46 | * 47 | * @param executor executor where reload callback will be executed. 48 | * @param callback reload callback, 1st argument is old value 2nd one is new value, both are 49 | * nullable; though callback may throw exception, this wouldn't stop other callbacks from 50 | * execution 51 | */ 52 | void addCallback(Executor executor, BiConsumer callback); 53 | 54 | /** 55 | * Adds validator to the list of validators. Validators will be invoked in the order they were 56 | * added. An argument to predicate is nullable. 57 | * 58 | * @throws IllegalArgumentException in case existing value fails against passed {@code validator} 59 | */ 60 | void addValidator(Predicate validator); 61 | } 62 | -------------------------------------------------------------------------------- /config/src/main/java/io/scalecube/config/BooleanConfigPropertyImpl.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.config; 2 | 3 | import io.scalecube.config.source.LoadedConfigProperty; 4 | import java.util.Map; 5 | 6 | class BooleanConfigPropertyImpl extends AbstractSimpleConfigProperty 7 | implements BooleanConfigProperty { 8 | 9 | BooleanConfigPropertyImpl( 10 | String name, 11 | Map propertyMap, 12 | Map> propertyCallbackMap) { 13 | super(name, Boolean.class, propertyMap, propertyCallbackMap, ConfigRegistryImpl.BOOLEAN_PARSER); 14 | } 15 | 16 | @Override 17 | public boolean value(boolean defaultValue) { 18 | return value().orElse(defaultValue); 19 | } 20 | 21 | @Override 22 | public boolean valueOrThrow() { 23 | return value().orElseThrow(this::newNoSuchElementException); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /config/src/main/java/io/scalecube/config/ConfigProperty.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.config; 2 | 3 | import java.util.Optional; 4 | 5 | /** 6 | * Single valued config property. Subclasses support value types: string, int, double, long, 7 | * duration, boolean and comma separated list. This type of property is suitable for configs having 8 | * single independent value with independent validation and independent callback function: 9 | * 10 | *
11 |  * IntConfigProperty prop = ...
12 |  * prop.addValidator(i -> i >= 42);
13 |  * prop.addCallback((i0, i) -> doSometing(i));
14 |  * 
15 | * 16 | *

In example above validator and callback dont need other config values, they are fine with 17 | * single value {@code i} (though, for callback, there's also and old {@code i}). 18 | */ 19 | public interface ConfigProperty { 20 | 21 | /** 22 | * Returns value. 23 | * 24 | * @return property name, never null. 25 | */ 26 | String name(); 27 | 28 | /** 29 | * Info from what config source this property had been loaded: classpath, file directory, mongodb 30 | * and etc. 31 | * 32 | * @return optional config source name. 33 | */ 34 | Optional source(); 35 | 36 | /** 37 | * Info from what concrete place this property had been loaded: path to file, mongo db table and 38 | * etc. 39 | * 40 | * @return optional origin string. 41 | */ 42 | Optional origin(); 43 | 44 | /** 45 | * Returns optional string value. 46 | * 47 | * @return optional raw string value. 48 | */ 49 | Optional valueAsString(); 50 | 51 | /** 52 | * Shortcut on {@code valueAsString().orElse(defaultValue)}. 53 | * 54 | * @return existing value or default 55 | */ 56 | String valueAsString(String defaultValue); 57 | } 58 | -------------------------------------------------------------------------------- /config/src/main/java/io/scalecube/config/ConfigPropertyInfo.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.config; 2 | 3 | public class ConfigPropertyInfo { 4 | private String name; 5 | private String value; 6 | private String source; 7 | private String origin; 8 | private String host; 9 | 10 | public String getName() { 11 | return name; 12 | } 13 | 14 | public void setName(String name) { 15 | this.name = name; 16 | } 17 | 18 | public String getValue() { 19 | return value; 20 | } 21 | 22 | public void setValue(String value) { 23 | this.value = value; 24 | } 25 | 26 | public String getSource() { 27 | return source; 28 | } 29 | 30 | public void setSource(String source) { 31 | this.source = source; 32 | } 33 | 34 | public String getOrigin() { 35 | return origin; 36 | } 37 | 38 | public void setOrigin(String origin) { 39 | this.origin = origin; 40 | } 41 | 42 | public String getHost() { 43 | return host; 44 | } 45 | 46 | public void setHost(String host) { 47 | this.host = host; 48 | } 49 | 50 | @Override 51 | public String toString() { 52 | return "{\"name\":\" " 53 | + name 54 | + "\",\"value\":\"" 55 | + value 56 | + "\",\"source\":\"" 57 | + source 58 | + "\",\"origin\":\"" 59 | + source 60 | + "\",\"host\":\"" 61 | + host 62 | + "\"}"; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /config/src/main/java/io/scalecube/config/ConfigRegistryException.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.config; 2 | 3 | public abstract class ConfigRegistryException extends RuntimeException { 4 | 5 | public ConfigRegistryException() {} 6 | 7 | public ConfigRegistryException(String message) { 8 | super(message); 9 | } 10 | 11 | public ConfigRegistryException(String message, Throwable cause) { 12 | super(message, cause); 13 | } 14 | 15 | public ConfigRegistryException(Throwable cause) { 16 | super(cause); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /config/src/main/java/io/scalecube/config/ConfigRegistrySettings.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.config; 2 | 3 | import io.scalecube.config.audit.ConfigEventListener; 4 | import io.scalecube.config.source.ConfigSource; 5 | import java.net.InetAddress; 6 | import java.util.Collections; 7 | import java.util.HashMap; 8 | import java.util.LinkedHashMap; 9 | import java.util.LinkedList; 10 | import java.util.Map; 11 | import java.util.StringJoiner; 12 | 13 | /** 14 | * Represents settings of config registry. 15 | * 16 | * @author Anton Kharenko 17 | */ 18 | public final class ConfigRegistrySettings { 19 | 20 | public static final int DEFAULT_RELOAD_PERIOD_SEC = 15; 21 | public static final int DEFAULT_RECENT_EVENTS_NUM = 30; 22 | public static final boolean DEFAULT_JMX_ENABLED = true; 23 | public static final String DEFAULT_JMX_MBEAN_NAME = "io.scalecube.config:name=ConfigRegistry"; 24 | 25 | private final Map sources; 26 | private final String host; 27 | private final int reloadIntervalSec; 28 | private final int recentConfigEventsNum; 29 | private final Map listeners; 30 | private final boolean jmxEnabled; 31 | private final String jmxMBeanName; 32 | 33 | private ConfigRegistrySettings(Builder builder) { 34 | Map sourcesTmp = new LinkedHashMap<>(builder.sources.size()); 35 | for (String name : builder.sourceOrder) { 36 | sourcesTmp.put(name, builder.sources.get(name)); 37 | } 38 | this.sources = Collections.unmodifiableMap(sourcesTmp); 39 | this.host = builder.host != null ? builder.host : resolveLocalHost(); 40 | this.reloadIntervalSec = builder.reloadIntervalSec; 41 | this.recentConfigEventsNum = builder.recentConfigEventsNum; 42 | this.listeners = Collections.unmodifiableMap(new HashMap<>(builder.listeners)); 43 | this.jmxEnabled = builder.jmxEnabled; 44 | this.jmxMBeanName = builder.jmxMBeanName; 45 | } 46 | 47 | private static String resolveLocalHost() { 48 | try { 49 | return InetAddress.getLocalHost().getHostAddress(); 50 | } catch (Exception e) { 51 | return "unresolved"; 52 | } 53 | } 54 | 55 | public static Builder builder() { 56 | return new Builder(); 57 | } 58 | 59 | public int getReloadIntervalSec() { 60 | return reloadIntervalSec; 61 | } 62 | 63 | public boolean isReloadEnabled() { 64 | return reloadIntervalSec != Integer.MAX_VALUE; 65 | } 66 | 67 | public int getRecentConfigEventsNum() { 68 | return recentConfigEventsNum; 69 | } 70 | 71 | public Map getListeners() { 72 | return listeners; 73 | } 74 | 75 | public Map getSources() { 76 | return sources; 77 | } 78 | 79 | public String getHost() { 80 | return host; 81 | } 82 | 83 | public boolean isJmxEnabled() { 84 | return jmxEnabled; 85 | } 86 | 87 | public String getJmxMBeanName() { 88 | return jmxMBeanName; 89 | } 90 | 91 | @Override 92 | public String toString() { 93 | return new StringJoiner(", ", ConfigRegistrySettings.class.getSimpleName() + "[", "]") 94 | .add("sources=" + sources) 95 | .add("host='" + host + "'") 96 | .add("reloadIntervalSec=" + reloadIntervalSec) 97 | .add("recentConfigEventsNum=" + recentConfigEventsNum) 98 | .add("listeners=" + listeners) 99 | .add("jmxEnabled=" + jmxEnabled) 100 | .add("jmxMBeanName='" + jmxMBeanName + "'") 101 | .toString(); 102 | } 103 | 104 | public static class Builder { 105 | private final LinkedList sourceOrder = new LinkedList<>(); 106 | private final Map sources = new HashMap<>(); 107 | private final String host = null; 108 | private int reloadIntervalSec = DEFAULT_RELOAD_PERIOD_SEC; 109 | private int recentConfigEventsNum = DEFAULT_RECENT_EVENTS_NUM; 110 | private final Map listeners = new HashMap<>(); 111 | private boolean jmxEnabled = DEFAULT_JMX_ENABLED; 112 | private String jmxMBeanName = DEFAULT_JMX_MBEAN_NAME; 113 | 114 | private Builder() {} 115 | 116 | public Builder noReload() { 117 | this.reloadIntervalSec = Integer.MAX_VALUE; 118 | return this; 119 | } 120 | 121 | public Builder reloadIntervalSec(int reloadPeriodSec) { 122 | this.reloadIntervalSec = reloadPeriodSec; 123 | return this; 124 | } 125 | 126 | public Builder keepRecentConfigEvents(int recentConfigEventsNum) { 127 | this.recentConfigEventsNum = recentConfigEventsNum; 128 | return this; 129 | } 130 | 131 | public Builder addListener(ConfigEventListener configEventListener) { 132 | this.listeners.put(configEventListener.getClass().getSimpleName(), configEventListener); 133 | return this; 134 | } 135 | 136 | /** 137 | * Add config source as the last one to be processed. 138 | * 139 | * @param name source alias name 140 | * @param configSource config source instance 141 | * @return builder instance 142 | */ 143 | public Builder addLastSource(String name, ConfigSource configSource) { 144 | sourceOrder.addLast(name); 145 | sources.put(name, configSource); 146 | return this; 147 | } 148 | 149 | /** 150 | * Add config source as the first one to be processed. 151 | * 152 | * @param name source alias name 153 | * @param configSource config source instance 154 | * @return builder instance 155 | */ 156 | public Builder addFirstSource(String name, ConfigSource configSource) { 157 | sourceOrder.addFirst(name); 158 | sources.put(name, configSource); 159 | return this; 160 | } 161 | 162 | /** 163 | * Add config source to be processed before another specified by beforeName source. 164 | * 165 | * @param beforeName source alias name before which newly added source will be processed 166 | * @param name source alias name 167 | * @param configSource config source instance 168 | * @return builder instance 169 | */ 170 | public Builder addBeforeSource(String beforeName, String name, ConfigSource configSource) { 171 | int ind = sourceOrder.indexOf(beforeName); 172 | sourceOrder.add(ind, name); 173 | sources.put(name, configSource); 174 | return this; 175 | } 176 | 177 | public Builder jmxEnabled(boolean jmxEnabled) { 178 | this.jmxEnabled = jmxEnabled; 179 | return this; 180 | } 181 | 182 | public Builder jmxMBeanName(String jmxMBeanName) { 183 | this.jmxMBeanName = jmxMBeanName; 184 | return this; 185 | } 186 | 187 | public ConfigRegistrySettings build() { 188 | return new ConfigRegistrySettings(this); 189 | } 190 | } 191 | } 192 | -------------------------------------------------------------------------------- /config/src/main/java/io/scalecube/config/ConfigSourceNotAvailableException.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.config; 2 | 3 | public class ConfigSourceNotAvailableException extends ConfigRegistryException { 4 | 5 | public ConfigSourceNotAvailableException() {} 6 | 7 | public ConfigSourceNotAvailableException(String message) { 8 | super(message); 9 | } 10 | 11 | public ConfigSourceNotAvailableException(String message, Throwable cause) { 12 | super(message, cause); 13 | } 14 | 15 | public ConfigSourceNotAvailableException(Throwable cause) { 16 | super(cause); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /config/src/main/java/io/scalecube/config/DoubleConfigProperty.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.config; 2 | 3 | import java.util.NoSuchElementException; 4 | import java.util.Optional; 5 | import java.util.concurrent.Executor; 6 | import java.util.function.BiConsumer; 7 | import java.util.function.Predicate; 8 | 9 | public interface DoubleConfigProperty extends ConfigProperty { 10 | 11 | /** 12 | * Returns value. 13 | * 14 | * @return optional double value 15 | */ 16 | Optional value(); 17 | 18 | /** 19 | * Shortcut on {@code value().orElse(defaultValue)}. 20 | * 21 | * @return existing value or default 22 | */ 23 | double value(double defaultValue); 24 | 25 | /** 26 | * Returns existing value or throws {@link NoSuchElementException} if value is null. 27 | * 28 | * @return existing value or exception 29 | * @throws NoSuchElementException if value is null 30 | */ 31 | double valueOrThrow(); 32 | 33 | /** 34 | * Adds reload callback to the list. Callbacks will be invoked in the order they were added, and 35 | * only after validation have been passed. 36 | * 37 | * @param callback reload callback, 1st argument is old value 2nd one is new value, both are 38 | * nullable; though callback may throw exception, this wouldn't stop other callbacks from 39 | * execution 40 | */ 41 | void addCallback(BiConsumer callback); 42 | 43 | /** 44 | * Adds reload callback to the list. Callbacks will be invoked in the order they were added, and 45 | * only after validation have been passed. 46 | * 47 | * @param executor executor where reload callback will be executed. 48 | * @param callback reload callback, 1st argument is old value 2nd one is new value, both are 49 | * nullable; though callback may throw exception, this wouldn't stop other callbacks from 50 | * execution 51 | */ 52 | void addCallback(Executor executor, BiConsumer callback); 53 | 54 | /** 55 | * Adds validator to the list of validators. Validators will be invoked in the order they were 56 | * added. An argument to predicate is nullable. 57 | * 58 | * @throws IllegalArgumentException in case existing value fails against passed {@code validator} 59 | */ 60 | void addValidator(Predicate validator); 61 | } 62 | -------------------------------------------------------------------------------- /config/src/main/java/io/scalecube/config/DoubleConfigPropertyImpl.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.config; 2 | 3 | import io.scalecube.config.source.LoadedConfigProperty; 4 | import java.util.Map; 5 | 6 | class DoubleConfigPropertyImpl extends AbstractSimpleConfigProperty 7 | implements DoubleConfigProperty { 8 | 9 | DoubleConfigPropertyImpl( 10 | String name, 11 | Map propertyMap, 12 | Map> propertyCallbackMap) { 13 | super(name, Double.class, propertyMap, propertyCallbackMap, ConfigRegistryImpl.DOUBLE_PARSER); 14 | } 15 | 16 | @Override 17 | public double value(double defaultValue) { 18 | return value().orElse(defaultValue); 19 | } 20 | 21 | @Override 22 | public double valueOrThrow() { 23 | return value().orElseThrow(this::newNoSuchElementException); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /config/src/main/java/io/scalecube/config/DurationConfigProperty.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.config; 2 | 3 | import java.time.Duration; 4 | import java.util.NoSuchElementException; 5 | import java.util.Optional; 6 | import java.util.concurrent.Executor; 7 | import java.util.function.BiConsumer; 8 | import java.util.function.Predicate; 9 | 10 | public interface DurationConfigProperty extends ConfigProperty { 11 | 12 | /** 13 | * Returns value. 14 | * 15 | * @return optional duration value 16 | */ 17 | Optional value(); 18 | 19 | /** 20 | * Shortcut on {@code value().orElse(defaultValue)}. 21 | * 22 | * @return existing value or default 23 | */ 24 | Duration value(Duration defaultValue); 25 | 26 | /** 27 | * Returns existing value or throws {@link NoSuchElementException} if value is null. 28 | * 29 | * @return existing value or exception 30 | * @throws NoSuchElementException if value is null 31 | */ 32 | Duration valueOrThrow(); 33 | 34 | /** 35 | * Adds reload callback to the list. Callbacks will be invoked in the order they were added, and 36 | * only after validation have been passed. 37 | * 38 | * @param callback reload callback, 1st argument is old value 2nd one is new value, both are 39 | * nullable; though callback may throw exception, this wouldn't stop other callbacks from 40 | * execution 41 | */ 42 | void addCallback(BiConsumer callback); 43 | 44 | /** 45 | * Adds reload callback to the list. Callbacks will be invoked in the order they were added, and 46 | * only after validation have been passed. 47 | * 48 | * @param executor executor where reload callback will be executed. 49 | * @param callback reload callback, 1st argument is old value 2nd one is new value, both are 50 | * nullable; though callback may throw exception, this wouldn't stop other callbacks from 51 | * execution 52 | */ 53 | void addCallback(Executor executor, BiConsumer callback); 54 | 55 | /** 56 | * Adds validator to the list of validators. Validators will be invoked in the order they were 57 | * added. An argument to predicate is nullable. 58 | * 59 | * @throws IllegalArgumentException in case existing value fails against passed {@code validator} 60 | */ 61 | void addValidator(Predicate validator); 62 | } 63 | -------------------------------------------------------------------------------- /config/src/main/java/io/scalecube/config/DurationConfigPropertyImpl.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.config; 2 | 3 | import io.scalecube.config.source.LoadedConfigProperty; 4 | import java.time.Duration; 5 | import java.util.Map; 6 | 7 | class DurationConfigPropertyImpl extends AbstractSimpleConfigProperty 8 | implements DurationConfigProperty { 9 | 10 | DurationConfigPropertyImpl( 11 | String name, 12 | Map propertyMap, 13 | Map> propertyCallbackMap) { 14 | super( 15 | name, Duration.class, propertyMap, propertyCallbackMap, ConfigRegistryImpl.DURATION_PARSER); 16 | } 17 | 18 | @Override 19 | public Duration value(Duration defaultValue) { 20 | return value().orElse(defaultValue); 21 | } 22 | 23 | @Override 24 | public Duration valueOrThrow() { 25 | return value().orElseThrow(this::newNoSuchElementException); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /config/src/main/java/io/scalecube/config/DurationParser.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.config; 2 | 3 | import java.time.Duration; 4 | import java.time.temporal.ChronoUnit; 5 | 6 | /** 7 | * {@link java.time.Duration} parser. Recognizes string format from official java8 documentation and more convenient string form with integer 10 | * value followed by time_unit string (which is one of: ns - nanos, us - micros, 11 | * ms - millis, s - seconds, m - minutes, h - hours, d - days). 12 | */ 13 | class DurationParser { 14 | 15 | private DurationParser() { 16 | // Do not instantiate 17 | } 18 | 19 | // adapted from 20 | // https://github.com/typesafehub/config/blob/v1.3.0/config/src/main/java/com/typesafe/config/impl/SimpleConfig.java#L551-L624 21 | static Duration parseDuration(String input) { 22 | if (input.startsWith("P") || input.startsWith("-P") || input.startsWith("+P")) { 23 | return Duration.parse(input); 24 | } 25 | 26 | String[] parts = splitNumericAndChar(input); 27 | String numberString = parts[0]; 28 | String originalUnitString = parts[1]; 29 | String unitString = originalUnitString; 30 | 31 | if (numberString.length() == 0) { 32 | throw new IllegalArgumentException(String.format("No number in duration value '%s'", input)); 33 | } 34 | 35 | if (unitString.length() > 2 && !unitString.endsWith("s")) { 36 | unitString = unitString + "s"; 37 | } 38 | 39 | ChronoUnit units; 40 | switch (unitString) { 41 | case "ns": 42 | units = ChronoUnit.NANOS; 43 | break; 44 | case "us": 45 | units = ChronoUnit.MICROS; 46 | break; 47 | case "": 48 | case "ms": 49 | units = ChronoUnit.MILLIS; 50 | break; 51 | case "s": 52 | units = ChronoUnit.SECONDS; 53 | break; 54 | case "m": 55 | units = ChronoUnit.MINUTES; 56 | break; 57 | case "h": 58 | units = ChronoUnit.HOURS; 59 | break; 60 | case "d": 61 | units = ChronoUnit.DAYS; 62 | break; 63 | default: 64 | throw new IllegalArgumentException( 65 | String.format( 66 | "Could not parse time unit '%s' (try ns, us, ms, s, m, h, d)", originalUnitString)); 67 | } 68 | 69 | return Duration.of(Long.parseLong(numberString), units); 70 | } 71 | 72 | // adapted from 73 | // https://github.com/typesafehub/config/blob/v1.3.0/config/src/main/java/com/typesafe/config/impl/ConfigImplUtil.java#L118-L164 74 | private static String[] splitNumericAndChar(String input) { 75 | int i = input.length() - 1; 76 | while (i >= 0) { 77 | char c = input.charAt(i); 78 | if (!Character.isLetter(c)) { 79 | break; 80 | } 81 | i -= 1; 82 | } 83 | return new String[] {input.substring(0, i + 1).trim(), input.substring(i + 1).trim()}; 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /config/src/main/java/io/scalecube/config/IntConfigProperty.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.config; 2 | 3 | import java.util.NoSuchElementException; 4 | import java.util.Optional; 5 | import java.util.concurrent.Executor; 6 | import java.util.function.BiConsumer; 7 | import java.util.function.Predicate; 8 | 9 | public interface IntConfigProperty extends ConfigProperty { 10 | 11 | /** 12 | * Returns value. 13 | * 14 | * @return optional int value 15 | */ 16 | Optional value(); 17 | 18 | /** 19 | * Shortcut on {@code value().orElse(defaultValue)}. 20 | * 21 | * @return existing value or default 22 | */ 23 | int value(int defaultValue); 24 | 25 | /** 26 | * Returns existing value or throws {@link NoSuchElementException} if value is null. 27 | * 28 | * @return existing value or exception 29 | * @throws NoSuchElementException if value is null 30 | */ 31 | int valueOrThrow(); 32 | 33 | /** 34 | * Adds reload callback to the list. Callbacks will be invoked in the order they were added, and 35 | * only after validation have been passed. 36 | * 37 | * @param callback reload callback, 1st argument is old value 2nd one is new value, both are 38 | * nullable; though callback may throw exception, this wouldn't stop other callbacks from 39 | * execution 40 | */ 41 | void addCallback(BiConsumer callback); 42 | 43 | /** 44 | * Adds reload callback to the list. Callbacks will be invoked in the order they were added, and 45 | * only after validation have been passed. 46 | * 47 | * @param executor executor where reload callback will be executed. 48 | * @param callback reload callback, 1st argument is old value 2nd one is new value, both are 49 | * nullable; though callback may throw exception, this wouldn't stop other callbacks from 50 | * execution 51 | */ 52 | void addCallback(Executor executor, BiConsumer callback); 53 | 54 | /** 55 | * Adds validator to the list of validators. Validators will be invoked in the order they were 56 | * added. The argument to predicate is nullable. 57 | * 58 | * @throws IllegalArgumentException in case existing value fails against passed {@code validator} 59 | */ 60 | void addValidator(Predicate validator); 61 | } 62 | -------------------------------------------------------------------------------- /config/src/main/java/io/scalecube/config/IntConfigPropertyImpl.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.config; 2 | 3 | import io.scalecube.config.source.LoadedConfigProperty; 4 | import java.util.Map; 5 | 6 | class IntConfigPropertyImpl extends AbstractSimpleConfigProperty 7 | implements IntConfigProperty { 8 | 9 | IntConfigPropertyImpl( 10 | String name, 11 | Map propertyMap, 12 | Map> propertyCallbackMap) { 13 | super(name, Integer.class, propertyMap, propertyCallbackMap, ConfigRegistryImpl.INT_PARSER); 14 | } 15 | 16 | @Override 17 | public int value(int defaultValue) { 18 | return value().orElse(defaultValue); 19 | } 20 | 21 | @Override 22 | public int valueOrThrow() { 23 | return value().orElseThrow(this::newNoSuchElementException); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /config/src/main/java/io/scalecube/config/ListConfigProperty.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.config; 2 | 3 | import java.util.List; 4 | import java.util.NoSuchElementException; 5 | import java.util.Optional; 6 | import java.util.concurrent.Executor; 7 | import java.util.function.BiConsumer; 8 | import java.util.function.Predicate; 9 | 10 | /** 11 | * List config property for comma separated values. Parsable types supported: string, int, double, 12 | * long, duration. 13 | * 14 | * @param type of list element 15 | */ 16 | public interface ListConfigProperty extends ConfigProperty { 17 | 18 | /** 19 | * Returns value. 20 | * 21 | * @return optional list value 22 | */ 23 | Optional> value(); 24 | 25 | /** 26 | * Shortcut on {@code value().orElse(defaultValue)}. 27 | * 28 | * @return existing value or default 29 | */ 30 | List value(List defaultValue); 31 | 32 | /** 33 | * Returns existing value or throws {@link NoSuchElementException} if value is null. 34 | * 35 | * @return existing value or exception 36 | * @throws NoSuchElementException if value is null 37 | */ 38 | List valueOrThrow(); 39 | 40 | /** 41 | * Adds reload callback to the list. Callbacks will be invoked in the order they were added, and 42 | * only after validation have been passed. 43 | * 44 | * @param callback reload callback, 1st argument is old value 2nd one is new value, both are 45 | * nullable; though callback may throw exception, this wouldn't stop other callbacks from 46 | * execution 47 | */ 48 | void addCallback(BiConsumer, List> callback); 49 | 50 | /** 51 | * Adds reload callback to the list. Callbacks will be invoked in the order they were added, and 52 | * only after validation have been passed. 53 | * 54 | * @param executor executor where reload callback will be executed. 55 | * @param callback reload callback, 1st argument is old value 2nd one is new value, both are 56 | * nullable; though callback may throw exception, this wouldn't stop other callbacks from 57 | * execution 58 | */ 59 | void addCallback(Executor executor, BiConsumer, List> callback); 60 | 61 | /** 62 | * Adds validator to the list of validators. Validators will be invoked in the order they were 63 | * added. An argument to predicate is nullable. 64 | * 65 | * @throws IllegalArgumentException in case existing value fails against passed {@code validator} 66 | */ 67 | void addValidator(Predicate> validator); 68 | } 69 | -------------------------------------------------------------------------------- /config/src/main/java/io/scalecube/config/ListConfigPropertyImpl.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.config; 2 | 3 | import io.scalecube.config.source.LoadedConfigProperty; 4 | import java.util.Arrays; 5 | import java.util.List; 6 | import java.util.Map; 7 | import java.util.function.Function; 8 | import java.util.stream.Collectors; 9 | 10 | class ListConfigPropertyImpl extends AbstractSimpleConfigProperty> 11 | implements ListConfigProperty { 12 | 13 | static Function> toListPropertyParser(Function valueParser) { 14 | return str -> Arrays.stream(str.split(",")).map(valueParser).collect(Collectors.toList()); 15 | } 16 | 17 | ListConfigPropertyImpl( 18 | String name, 19 | Map propertyMap, 20 | Map> propertyCallbackMap, 21 | Function valueParser) { 22 | super( 23 | name, 24 | getListPropertyClass(valueParser), 25 | propertyMap, 26 | propertyCallbackMap, 27 | toListPropertyParser(valueParser)); 28 | } 29 | 30 | @Override 31 | public List value(List defaultValue) { 32 | return value().orElse(defaultValue); 33 | } 34 | 35 | @Override 36 | public List valueOrThrow() { 37 | return value().orElseThrow(this::newNoSuchElementException); 38 | } 39 | 40 | private static Class getListPropertyClass(Function valueParser) { 41 | Class result = null; 42 | if (ConfigRegistryImpl.STRING_PARSER == valueParser) { 43 | result = StringList.class; 44 | } else if (ConfigRegistryImpl.DOUBLE_PARSER == valueParser) { 45 | result = DoubleList.class; 46 | } else if (ConfigRegistryImpl.LONG_PARSER == valueParser) { 47 | result = LongList.class; 48 | } else if (ConfigRegistryImpl.INT_PARSER == valueParser) { 49 | result = IntList.class; 50 | } else if (ConfigRegistryImpl.DURATION_PARSER == valueParser) { 51 | result = DurationList.class; 52 | } 53 | if (result == null) { 54 | throw new IllegalArgumentException( 55 | "ListConfigPropertyImpl: unsupported list valueParser " + valueParser); 56 | } 57 | return result; 58 | } 59 | 60 | private static class StringList {} 61 | 62 | private static class DoubleList {} 63 | 64 | private static class LongList {} 65 | 66 | private static class IntList {} 67 | 68 | private static class DurationList {} 69 | } 70 | -------------------------------------------------------------------------------- /config/src/main/java/io/scalecube/config/LongConfigProperty.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.config; 2 | 3 | import java.util.NoSuchElementException; 4 | import java.util.Optional; 5 | import java.util.concurrent.Executor; 6 | import java.util.function.BiConsumer; 7 | import java.util.function.Predicate; 8 | 9 | public interface LongConfigProperty extends ConfigProperty { 10 | 11 | /** 12 | * Returns value. 13 | * 14 | * @return optional long value 15 | */ 16 | Optional value(); 17 | 18 | /** 19 | * Shortcut on {@code value().orElse(defaultValue)}. 20 | * 21 | * @return existing value or default 22 | */ 23 | long value(long defaultValue); 24 | 25 | /** 26 | * Returns existing value or throws {@link NoSuchElementException} if value is null. 27 | * 28 | * @return existing value or exception 29 | * @throws NoSuchElementException if value is null 30 | */ 31 | long valueOrThrow(); 32 | 33 | /** 34 | * Adds reload callback to the list. Callbacks will be invoked in the order they were added, and 35 | * only after validation have been passed. 36 | * 37 | * @param callback reload callback, 1st argument is old value 2nd one is new value, both are 38 | * nullable; though callback may throw exception, this wouldn't stop other callbacks from 39 | * execution 40 | */ 41 | void addCallback(BiConsumer callback); 42 | 43 | /** 44 | * Adds reload callback to the list. Callbacks will be invoked in the order they were added, and 45 | * only after validation have been passed. 46 | * 47 | * @param executor executor where reload callback will be executed. 48 | * @param callback reload callback, 1st argument is old value 2nd one is new value, both are 49 | * nullable; though callback may throw exception, this wouldn't stop other callbacks from 50 | * execution 51 | */ 52 | void addCallback(Executor executor, BiConsumer callback); 53 | 54 | /** 55 | * Adds validator to the list of validators. Validators will be invoked in the order they were 56 | * added. An argument to predicate is nullable. 57 | * 58 | * @throws IllegalArgumentException in case existing value fails against passed {@code validator} 59 | */ 60 | void addValidator(Predicate validator); 61 | } 62 | -------------------------------------------------------------------------------- /config/src/main/java/io/scalecube/config/LongConfigPropertyImpl.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.config; 2 | 3 | import io.scalecube.config.source.LoadedConfigProperty; 4 | import java.util.Map; 5 | 6 | class LongConfigPropertyImpl extends AbstractSimpleConfigProperty 7 | implements LongConfigProperty { 8 | 9 | LongConfigPropertyImpl( 10 | String name, 11 | Map propertyMap, 12 | Map> propertyCallbackMap) { 13 | super(name, Long.class, propertyMap, propertyCallbackMap, ConfigRegistryImpl.LONG_PARSER); 14 | } 15 | 16 | @Override 17 | public long value(long defaultValue) { 18 | return value().orElse(defaultValue); 19 | } 20 | 21 | @Override 22 | public long valueOrThrow() { 23 | return value().orElseThrow(this::newNoSuchElementException); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /config/src/main/java/io/scalecube/config/MappedObjectConfigProperty.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.config; 2 | 3 | import java.util.Optional; 4 | import java.util.concurrent.Executor; 5 | import java.util.function.BiConsumer; 6 | import java.util.function.Function; 7 | import java.util.function.Predicate; 8 | 9 | class MappedObjectConfigProperty implements ObjectConfigProperty { 10 | 11 | private final StringConfigProperty configProperty; 12 | private final Function mapper; 13 | 14 | MappedObjectConfigProperty(StringConfigProperty configProperty, Function mapper) { 15 | this.configProperty = configProperty; 16 | this.mapper = mapper; 17 | } 18 | 19 | @Override 20 | public String name() { 21 | return configProperty.name(); 22 | } 23 | 24 | @Override 25 | public Optional value() { 26 | return configProperty.value().map(mapper); 27 | } 28 | 29 | @Override 30 | public T value(T defaultValue) { 31 | return value().orElse(defaultValue); 32 | } 33 | 34 | @Override 35 | public void addCallback(BiConsumer callback) { 36 | configProperty.addCallback( 37 | (v0, v1) -> { 38 | T t0 = Optional.ofNullable(v0).map(mapper).orElse(null); 39 | T t1 = Optional.ofNullable(v1).map(mapper).orElse(null); 40 | callback.accept(t0, t1); 41 | }); 42 | } 43 | 44 | @Override 45 | public void addCallback(Executor executor, BiConsumer callback) { 46 | configProperty.addCallback( 47 | executor, 48 | (v0, v1) -> { 49 | T t0 = Optional.ofNullable(v0).map(mapper).orElse(null); 50 | T t1 = Optional.ofNullable(v1).map(mapper).orElse(null); 51 | callback.accept(t0, t1); 52 | }); 53 | } 54 | 55 | @Override 56 | public void addValidator(Predicate validator) { 57 | configProperty.addValidator( 58 | value -> validator.test(Optional.ofNullable(value).map(mapper).orElse(null))); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /config/src/main/java/io/scalecube/config/MultimapConfigProperty.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.config; 2 | 3 | import java.util.List; 4 | import java.util.Map; 5 | import java.util.NoSuchElementException; 6 | import java.util.Optional; 7 | import java.util.concurrent.Executor; 8 | import java.util.function.BiConsumer; 9 | import java.util.function.Predicate; 10 | 11 | /** 12 | * Multimap config property. Parsable value types supported: string, int, double, long, duration. 13 | */ 14 | public interface MultimapConfigProperty extends ConfigProperty { 15 | /** 16 | * Returns value. 17 | * 18 | * @return optional multimap value 19 | */ 20 | Optional>> value(); 21 | 22 | /** 23 | * Shortcut on {@code value().orElse(defaultValue)}. 24 | * 25 | * @return existing value or default 26 | */ 27 | Map> value(Map> defaultValue); 28 | 29 | /** 30 | * Returns existing value or throws {@link NoSuchElementException} if value is null. 31 | * 32 | * @return existing value or exception 33 | * @throws NoSuchElementException if value is null 34 | */ 35 | Map> valueOrThrow(); 36 | 37 | /** 38 | * Adds reload callback to the list. Callbacks will be invoked in the order they were added, and 39 | * only after validation have been passed. 40 | * 41 | * @param callback reload callback, 1st argument is old value 2nd one is new value, both are 42 | * nullable; though callback may throw exception, this wouldn't stop other callbacks from 43 | * execution 44 | */ 45 | void addCallback(BiConsumer>, Map>> callback); 46 | 47 | /** 48 | * Adds reload callback to the list. Callbacks will be invoked in the order they were added, and 49 | * only after validation have been passed. 50 | * 51 | * @param executor executor where reload callback will be executed. 52 | * @param callback reload callback, 1st argument is old value 2nd one is new value, both are 53 | * nullable; though callback may throw exception, this wouldn't stop other callbacks from 54 | * execution 55 | */ 56 | void addCallback( 57 | Executor executor, BiConsumer>, Map>> callback); 58 | 59 | /** 60 | * Adds validator to the list of validators. Validators will be invoked in the order they were 61 | * added. An argument to predicate is nullable. 62 | * 63 | * @throws IllegalArgumentException in case existing value fails against passed {@code validator} 64 | */ 65 | void addValidator(Predicate>> validator); 66 | } 67 | -------------------------------------------------------------------------------- /config/src/main/java/io/scalecube/config/MultimapConfigPropertyImpl.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.config; 2 | 3 | import io.scalecube.config.source.LoadedConfigProperty; 4 | import java.util.ArrayList; 5 | import java.util.HashMap; 6 | import java.util.List; 7 | import java.util.Map; 8 | import java.util.function.Function; 9 | 10 | class MultimapConfigPropertyImpl extends AbstractSimpleConfigProperty>> 11 | implements MultimapConfigProperty { 12 | 13 | static Function>> toMultimapPropertyParser( 14 | Function valueParser) { 15 | return str -> { 16 | Map> result = new HashMap<>(); 17 | String[] tokens = str.split(","); 18 | String key = null; 19 | for (String token : tokens) { 20 | String[] entry = token.split("=", 2); 21 | String value; 22 | if (entry.length > 1) { // entry ["key", "value"] 23 | key = entry[0]; 24 | value = entry[1]; 25 | } else { // only value ["value"] 26 | value = entry[0]; 27 | } 28 | if (key != null) { 29 | result.computeIfAbsent(key, k -> new ArrayList<>()).add(valueParser.apply(value)); 30 | } 31 | } 32 | return result; 33 | }; 34 | } 35 | 36 | MultimapConfigPropertyImpl( 37 | String name, 38 | Map propertyMap, 39 | Map> propertyCallbackMap, 40 | Function valueParser) { 41 | super( 42 | name, 43 | getMapPropertyClass(valueParser), 44 | propertyMap, 45 | propertyCallbackMap, 46 | toMultimapPropertyParser(valueParser)); 47 | } 48 | 49 | @Override 50 | public Map> value(Map> defaultValue) { 51 | return value().orElse(defaultValue); 52 | } 53 | 54 | @Override 55 | public Map> valueOrThrow() { 56 | return value().orElseThrow(this::newNoSuchElementException); 57 | } 58 | 59 | private static Class getMapPropertyClass(Function valueParser) { 60 | Class result = null; 61 | if (ConfigRegistryImpl.STRING_PARSER == valueParser) { 62 | result = StringMultimap.class; 63 | } else if (ConfigRegistryImpl.DOUBLE_PARSER == valueParser) { 64 | result = DoubleMultimap.class; 65 | } else if (ConfigRegistryImpl.LONG_PARSER == valueParser) { 66 | result = LongMultimap.class; 67 | } else if (ConfigRegistryImpl.INT_PARSER == valueParser) { 68 | result = IntMultimap.class; 69 | } else if (ConfigRegistryImpl.DURATION_PARSER == valueParser) { 70 | result = DurationMultimap.class; 71 | } 72 | if (result == null) { 73 | throw new IllegalArgumentException( 74 | "MultimapConfigPropertyImpl: unsupported multimap valueParser " + valueParser); 75 | } 76 | return result; 77 | } 78 | 79 | private static class StringMultimap {} 80 | 81 | private static class DoubleMultimap {} 82 | 83 | private static class LongMultimap {} 84 | 85 | private static class IntMultimap {} 86 | 87 | private static class DurationMultimap {} 88 | } 89 | -------------------------------------------------------------------------------- /config/src/main/java/io/scalecube/config/ObjectConfigProperty.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.config; 2 | 3 | import java.util.Optional; 4 | import java.util.concurrent.Executor; 5 | import java.util.function.BiConsumer; 6 | import java.util.function.Predicate; 7 | 8 | /** 9 | * Multi valued typed config property. Purpose is to support logical groups of config values related 10 | * to certain configurable component, plus provide their atomic value retrieval, atomic validation, 11 | * and atomic reload. For example: 12 | * 13 | *

14 |  * class ConnectorSettings {
15 |  *   String user; // -> 'com.acme.connector.user'
16 |  *   String password; // -> 'com.acme.connector.password'
17 |  * }
18 |  *
19 |  * ObjectConfigProperty<ConnectorSettings> prop = ...
20 |  * prop.addValidator(settings -> checkConnectorCredentialsValid(settings.user, settings.password);
21 |  * prop.addCallback((settings0, settings) -> newConnector(settings.user, settings.password));
22 |  * 
23 | * 24 | * @param object config type (must have default constructor) 25 | */ 26 | public interface ObjectConfigProperty { 27 | 28 | /** 29 | * Returns config class name. 30 | * 31 | * @return config class name, never null 32 | */ 33 | String name(); 34 | 35 | /** 36 | * Returns value. 37 | * 38 | * @return optional object value 39 | */ 40 | Optional value(); 41 | 42 | /** 43 | * Shortcut on {@code value().orElse(defaultValue)}. 44 | * 45 | * @return existing value or default 46 | */ 47 | T value(T defaultValue); 48 | 49 | /** 50 | * Adds reload callback to the list. Callbacks will be invoked in the order they were added, and 51 | * only after validation have been passed. 52 | * 53 | * @param callback reload callback, 1st argument is old value 2nd one is new value, both are 54 | * nullable; though callback may throw exception, this wouldn't stop other callbacks from 55 | * execution 56 | */ 57 | void addCallback(BiConsumer callback); 58 | 59 | /** 60 | * Adds reload callback to the list. Callbacks will be invoked in the order they were added, and 61 | * only after validation have been passed. 62 | * 63 | * @param executor executor where reload callback will be executed. 64 | * @param callback reload callback, 1st argument is old value 2nd one is new value, both are 65 | * nullable; though callback may throw exception, this wouldn't stop other callbacks from 66 | * execution 67 | */ 68 | void addCallback(Executor executor, BiConsumer callback); 69 | 70 | /** 71 | * Adds validator to the list of validators. Validators will be invoked in the order they were 72 | * added. An argument to predicate is nullable. 73 | * 74 | * @throws IllegalArgumentException in case existing value fails against passed {@code validator} 75 | */ 76 | void addValidator(Predicate validator); 77 | } 78 | -------------------------------------------------------------------------------- /config/src/main/java/io/scalecube/config/ObjectConfigPropertyImpl.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.config; 2 | 3 | import io.scalecube.config.source.LoadedConfigProperty; 4 | import io.scalecube.config.utils.ThrowableUtil; 5 | import java.lang.reflect.Field; 6 | import java.lang.reflect.Modifier; 7 | import java.util.ArrayList; 8 | import java.util.List; 9 | import java.util.Map; 10 | import java.util.concurrent.ConcurrentHashMap; 11 | import java.util.stream.Collectors; 12 | 13 | /** 14 | * Implementation of {@link ObjectConfigProperty}. 15 | * 16 | * @param type of the property value 17 | */ 18 | class ObjectConfigPropertyImpl extends AbstractConfigProperty 19 | implements ObjectConfigProperty { 20 | 21 | @SuppressWarnings("rawtypes") 22 | ObjectConfigPropertyImpl( 23 | Map bindingMap, 24 | Class cfgClass, 25 | Map propertyMap, 26 | Map> propertyCallbackMap) { 27 | 28 | super(cfgClass.getName(), cfgClass); 29 | 30 | List propertyFields = toPropertyFields(bindingMap, cfgClass); 31 | setPropertyCallback(computePropertyCallback(cfgClass, propertyFields, propertyCallbackMap)); 32 | 33 | computeValue( 34 | propertyFields.stream() 35 | .map(ObjectPropertyField::getPropertyName) 36 | .filter(propertyMap::containsKey) 37 | .map(propertyMap::get) 38 | .collect(Collectors.toList())); 39 | } 40 | 41 | @Override 42 | public T value(T defaultValue) { 43 | return value().orElse(defaultValue); 44 | } 45 | 46 | private List toPropertyFields( 47 | Map bindingMap, Class cfgClass) { 48 | List propertyFields = new ArrayList<>(bindingMap.size()); 49 | for (String fieldName : bindingMap.keySet()) { 50 | Field field; 51 | try { 52 | field = cfgClass.getDeclaredField(fieldName); 53 | } catch (NoSuchFieldException e) { 54 | throw ThrowableUtil.propagate(e); 55 | } 56 | int modifiers = field.getModifiers(); 57 | if (!Modifier.isStatic(modifiers) && !Modifier.isFinal(modifiers)) { 58 | propertyFields.add(new ObjectPropertyField(field, bindingMap.get(fieldName))); 59 | } 60 | } 61 | return propertyFields; 62 | } 63 | 64 | private PropertyCallback computePropertyCallback( 65 | Class cfgClass, 66 | List propertyFields, 67 | Map> propertyCallbackMap) { 68 | 69 | PropertyCallback propertyCallback = 70 | new PropertyCallback<>( 71 | list -> ObjectPropertyParser.parseObject(list, propertyFields, cfgClass)); 72 | 73 | List propertyNames = 74 | propertyFields.stream() 75 | .map(ObjectPropertyField::getPropertyName) 76 | .collect(Collectors.toList()); 77 | 78 | // ensure that only one propertyCallback instance will be shared among instances of the same 79 | // type 80 | synchronized (propertyCallbackMap) { 81 | propertyNames.forEach( 82 | propName -> { 83 | propertyCallbackMap.putIfAbsent(propName, new ConcurrentHashMap<>()); 84 | Map callbackMap = propertyCallbackMap.get(propName); 85 | callbackMap.putIfAbsent(propertyClass, propertyCallback); 86 | }); 87 | } 88 | 89 | // noinspection unchecked 90 | return propertyCallbackMap.entrySet().stream() 91 | .filter(e -> propertyNames.contains(e.getKey())) 92 | .map(Map.Entry::getValue) 93 | .filter(callbackMap -> callbackMap.containsKey(propertyClass)) 94 | .map(callbackMap -> callbackMap.get(propertyClass)) 95 | .collect(Collectors.toSet()) 96 | .iterator() 97 | .next(); 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /config/src/main/java/io/scalecube/config/ObjectPropertyField.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.config; 2 | 3 | import io.scalecube.config.utils.ThrowableUtil; 4 | import java.lang.reflect.Field; 5 | import java.lang.reflect.Modifier; 6 | import java.lang.reflect.ParameterizedType; 7 | import java.lang.reflect.Type; 8 | import java.time.Duration; 9 | import java.util.List; 10 | import java.util.Map; 11 | import java.util.function.Function; 12 | 13 | /** 14 | * Helper holder class. Contains parsed field of the corresponding object class, associated property 15 | * name and computed {@link #valueParser} function. 16 | */ 17 | class ObjectPropertyField { 18 | private final Field field; 19 | private final String propertyName; 20 | private final Function valueParser; 21 | 22 | ObjectPropertyField(Field field, String propertyName) { 23 | int modifiers = field.getModifiers(); 24 | if (Modifier.isStatic(modifiers) || Modifier.isFinal(modifiers)) { 25 | throw new IllegalArgumentException( 26 | "ObjectPropertyField: 'static' or 'final' declaration is not supported (field: " 27 | + field 28 | + ")"); 29 | } 30 | 31 | this.field = field; 32 | field.setAccessible(true); // set accessible to overcome 'private' declaration 33 | this.propertyName = propertyName; 34 | 35 | if (field.getGenericType() instanceof ParameterizedType) { 36 | ParameterizedType paramType = (ParameterizedType) field.getGenericType(); 37 | if (isList(paramType)) { 38 | Type type = paramType.getActualTypeArguments()[0]; 39 | this.valueParser = ListConfigPropertyImpl.toListPropertyParser(getValueParser(type)); 40 | } else if (isMultimap(paramType)) { 41 | Type[] typeArguments = paramType.getActualTypeArguments(); 42 | ParameterizedType valueType = ((ParameterizedType) typeArguments[1]); 43 | Type type = valueType.getActualTypeArguments()[0]; 44 | this.valueParser = 45 | MultimapConfigPropertyImpl.toMultimapPropertyParser(getValueParser(type)); 46 | } else { 47 | throw new IllegalArgumentException( 48 | "ObjectPropertyField: unsupported type on field: " + field); 49 | } 50 | } else { 51 | this.valueParser = getValueParser(field.getType()); 52 | } 53 | } 54 | 55 | private boolean isList(ParameterizedType paramType) { 56 | return paramType.getRawType() == List.class; 57 | } 58 | 59 | private boolean isMultimap(ParameterizedType paramType) { 60 | boolean result = false; 61 | if (paramType.getRawType() == Map.class) { 62 | Type[] typeArguments = paramType.getActualTypeArguments(); 63 | Type keyType = typeArguments[0]; 64 | Type valueType = typeArguments[1]; 65 | if (keyType == String.class && valueType instanceof ParameterizedType) { 66 | ParameterizedType parameterizedValueType = ((ParameterizedType) valueType); 67 | if (parameterizedValueType.getRawType() == List.class) { 68 | result = true; 69 | } 70 | } 71 | } 72 | return result; 73 | } 74 | 75 | private Function getValueParser(Type type) { 76 | if (type == String.class) { 77 | return str -> str; 78 | } else if (type == Duration.class) { 79 | return DurationParser::parseDuration; 80 | } else if (type == Integer.TYPE || type == Integer.class) { 81 | return Integer::parseInt; 82 | } else if (type == Double.TYPE || type == Double.class) { 83 | return Double::parseDouble; 84 | } else if (type == Boolean.TYPE || type == Boolean.class) { 85 | return Boolean::parseBoolean; 86 | } else if (type == Long.TYPE || type == Long.class) { 87 | return Long::parseLong; 88 | } else { 89 | return Function.identity(); 90 | } 91 | } 92 | 93 | String getPropertyName() { 94 | return propertyName; 95 | } 96 | 97 | void applyValueParser(Object instance, String value) { 98 | try { 99 | field.set(instance, valueParser.apply(value)); 100 | } catch (IllegalAccessException e) { 101 | throw ThrowableUtil.propagate(e); 102 | } 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /config/src/main/java/io/scalecube/config/ObjectPropertyParser.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.config; 2 | 3 | import io.scalecube.config.source.LoadedConfigProperty; 4 | import io.scalecube.config.utils.ThrowableUtil; 5 | import java.util.List; 6 | import java.util.Map; 7 | import java.util.Optional; 8 | import java.util.stream.Collectors; 9 | 10 | /** 11 | * Parser for {@link ObjectConfigProperty}. Returns object instance of the given class by the list 12 | * of {@link LoadedConfigProperty} objects and list of {@link ObjectPropertyField} . The class must 13 | * contain default constructor. 14 | */ 15 | class ObjectPropertyParser { 16 | 17 | private ObjectPropertyParser() { 18 | // Do not instantiate 19 | } 20 | 21 | static T parseObject( 22 | List inputList, 23 | List propertyFields, 24 | Class cfgClass) { 25 | 26 | T instance; 27 | try { 28 | instance = cfgClass.newInstance(); 29 | } catch (Exception e) { 30 | throw ThrowableUtil.propagate(e); 31 | } 32 | 33 | Map> inputMap = 34 | inputList.stream() 35 | .collect( 36 | Collectors.toMap(LoadedConfigProperty::name, LoadedConfigProperty::valueAsString)); 37 | 38 | for (ObjectPropertyField propertyField : propertyFields) { 39 | Optional valueOptional = inputMap.get(propertyField.getPropertyName()); 40 | if (valueOptional != null && valueOptional.isPresent()) { 41 | propertyField.applyValueParser(instance, valueOptional.get()); 42 | } 43 | } 44 | return instance; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /config/src/main/java/io/scalecube/config/PropertyCallback.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.config; 2 | 3 | import io.scalecube.config.audit.ConfigEvent; 4 | import io.scalecube.config.source.LoadedConfigProperty; 5 | import java.util.Collection; 6 | import java.util.List; 7 | import java.util.concurrent.CopyOnWriteArraySet; 8 | import java.util.function.Function; 9 | import java.util.stream.Collectors; 10 | import org.slf4j.Logger; 11 | import org.slf4j.LoggerFactory; 12 | 13 | /** 14 | * Property controller class for config property instances of type {@link T}. Config property 15 | * instances of the same type are managed by exactly one property controller. 16 | * 17 | * @param type of the property value 18 | */ 19 | class PropertyCallback { 20 | 21 | private static final Logger LOGGER = LoggerFactory.getLogger(PropertyCallback.class); 22 | 23 | /** 24 | * Value parser function. Converts list of string name-value pairs (input list can be null or 25 | * empty, in this case function would simply return null) to concrete object, i.e. a config 26 | * property value. 27 | */ 28 | private final Function, T> valueParser; 29 | 30 | /** 31 | * Collection of ConfigProperty objects of the same type assigned to this {@link 32 | * PropertyCallback}. 33 | */ 34 | private final Collection> configProperties = 35 | new CopyOnWriteArraySet<>(); 36 | 37 | /** 38 | * Creates property callback. 39 | * 40 | * @param valueParser value parser for config property object of certain type. 41 | */ 42 | PropertyCallback(Function, T> valueParser) { 43 | this.valueParser = list -> list == null || list.isEmpty() ? null : valueParser.apply(list); 44 | } 45 | 46 | /** Just adds config property instance to internal collection. */ 47 | void addConfigProperty(AbstractConfigProperty configProperty) { 48 | configProperties.add(configProperty); 49 | } 50 | 51 | /** 52 | * Computes new value for config property instances (of type {@link T}) from the list of {@link 53 | * ConfigEvent} objects. This method is being called from config registry reload process. 54 | * 55 | * @param events config events computed during config registry reload. 56 | */ 57 | void computeValue(List events) { 58 | List inputList = 59 | events.stream() 60 | .filter( 61 | event -> 62 | event.getType() 63 | != ConfigEvent.Type.REMOVED) // we only interested in ADDED or UPDATED 64 | .map( 65 | event -> 66 | LoadedConfigProperty.withNameAndValue(event.getName(), event.getNewValue()) 67 | .source(event.getNewSource()) 68 | .origin(event.getNewOrigin()) 69 | .build()) 70 | .collect(Collectors.toList()); 71 | 72 | T value; 73 | try { 74 | value = applyValueParser(inputList); 75 | } catch (Exception e) { 76 | LOGGER.error("Exception occurred", e); 77 | return; // return right away if parser failed 78 | } 79 | 80 | T newValue = value; // new value 81 | configProperties.forEach( 82 | configProperty -> { 83 | try { 84 | configProperty.acceptValue(newValue, inputList, true /* invokeCallbacks */); 85 | } catch (Exception e) { 86 | LOGGER.error( 87 | "Exception occurred at acceptValue on input: {}, on value: {}", 88 | inputList, 89 | newValue, 90 | e); 91 | } 92 | }); 93 | } 94 | 95 | /** 96 | * Computes new value for config property instance (passed as second parameter) from the list of 97 | * {@link LoadedConfigProperty} objects. 98 | * 99 | * @param inputList an input for {@link #valueParser}. 100 | * @param configProperty an instance where attempt to set a new value has to be made. 101 | * @throws IllegalArgumentException in case value can't be parsed from the inputList or validation 102 | * doesn't pass. 103 | */ 104 | void computeValue( 105 | List inputList, AbstractConfigProperty configProperty) { 106 | T value = applyValueParser(inputList); 107 | try { 108 | configProperty.acceptValue(value, inputList, false /* invokeCallbacks */); 109 | } catch (Exception e) { 110 | throw new IllegalArgumentException( 111 | "Exception occurred at acceptValue on input: " + inputList, e); 112 | } 113 | } 114 | 115 | private T applyValueParser(List inputList) { 116 | try { 117 | return valueParser.apply(inputList); 118 | } catch (Exception e) { 119 | throw new IllegalArgumentException( 120 | "Exception occurred at valueParser on input: " + inputList, e); 121 | } 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /config/src/main/java/io/scalecube/config/StringConfigProperty.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.config; 2 | 3 | import java.util.NoSuchElementException; 4 | import java.util.Optional; 5 | import java.util.concurrent.Executor; 6 | import java.util.function.BiConsumer; 7 | import java.util.function.Predicate; 8 | 9 | public interface StringConfigProperty extends ConfigProperty { 10 | 11 | /** 12 | * Returns value. 13 | * 14 | * @return optional string value 15 | */ 16 | Optional value(); 17 | 18 | /** 19 | * Shortcut on {@code value().orElse(defaultValue)}. 20 | * 21 | * @return existing value or default 22 | */ 23 | String value(String defaultValue); 24 | 25 | /** 26 | * Returns existing value or throws {@link NoSuchElementException} if value is null. 27 | * 28 | * @return existing value or exception 29 | * @throws NoSuchElementException if value is null 30 | */ 31 | String valueOrThrow(); 32 | 33 | /** 34 | * Adds reload callback to the list. Callbacks will be invoked in the order they were added, and 35 | * only after validation have been passed. 36 | * 37 | * @param callback reload callback, 1st argument is old value 2nd one is new value, both are 38 | * nullable; though callback may throw exception, this wouldn't stop other callbacks from 39 | * execution 40 | */ 41 | void addCallback(BiConsumer callback); 42 | 43 | /** 44 | * Adds reload callback to the list. Callbacks will be invoked in the order they were added, and 45 | * only after validation have been passed. 46 | * 47 | * @param executor executor where reload callback will be executed. 48 | * @param callback reload callback, 1st argument is old value 2nd one is new value, both are 49 | * nullable; though callback may throw exception, this wouldn't stop other callbacks from 50 | * execution 51 | */ 52 | void addCallback(Executor executor, BiConsumer callback); 53 | 54 | /** 55 | * Adds validator to the list of validators. Validators will be invoked in the order they were 56 | * added. An argument to predicate is nullable. 57 | * 58 | * @throws IllegalArgumentException in case existing value fails against passed {@code validator} 59 | */ 60 | void addValidator(Predicate validator); 61 | } 62 | -------------------------------------------------------------------------------- /config/src/main/java/io/scalecube/config/StringConfigPropertyImpl.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.config; 2 | 3 | import io.scalecube.config.source.LoadedConfigProperty; 4 | import java.util.Map; 5 | 6 | class StringConfigPropertyImpl extends AbstractSimpleConfigProperty 7 | implements StringConfigProperty { 8 | 9 | StringConfigPropertyImpl( 10 | String name, 11 | Map propertyMap, 12 | Map> propertyCallbackMap) { 13 | super(name, String.class, propertyMap, propertyCallbackMap, ConfigRegistryImpl.STRING_PARSER); 14 | } 15 | 16 | @Override 17 | public String value(String defaultValue) { 18 | return super.valueAsString(defaultValue); 19 | } 20 | 21 | @Override 22 | public String valueOrThrow() { 23 | return value().orElseThrow(this::newNoSuchElementException); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /config/src/main/java/io/scalecube/config/audit/ConfigEvent.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.config.audit; 2 | 3 | import io.scalecube.config.ConfigProperty; 4 | import java.util.Date; 5 | import java.util.Objects; 6 | 7 | public final class ConfigEvent { 8 | 9 | public enum Type { 10 | ADDED, 11 | REMOVED, 12 | UPDATED 13 | } 14 | 15 | private final String name; 16 | private final Date timestamp; 17 | private final Type type; 18 | private final String host; 19 | 20 | private final String oldValue; 21 | private final String oldSource; 22 | private final String oldOrigin; 23 | 24 | private final String newValue; 25 | private final String newSource; 26 | private final String newOrigin; 27 | 28 | private ConfigEvent( 29 | String name, Type type, String host, ConfigProperty oldProp, ConfigProperty newProp) { 30 | this.name = Objects.requireNonNull(name, "ConfigEvent: propName is required"); 31 | this.timestamp = new Date(); 32 | this.type = type; 33 | this.host = host; 34 | 35 | this.oldValue = oldProp != null ? oldProp.valueAsString().orElse(null) : null; 36 | this.oldSource = oldProp != null ? oldProp.source().orElse(null) : null; 37 | this.oldOrigin = oldProp != null ? oldProp.origin().orElse(null) : null; 38 | 39 | this.newValue = newProp != null ? newProp.valueAsString().orElse(null) : null; 40 | this.newSource = newProp != null ? newProp.source().orElse(null) : null; 41 | this.newOrigin = newProp != null ? newProp.origin().orElse(null) : null; 42 | } 43 | 44 | /** 45 | * Creates {@link Type#ADDED} event for particular property. 46 | * 47 | * @param propName property name 48 | * @param host host 49 | * @param newProp added property 50 | * @return config event 51 | */ 52 | public static ConfigEvent createAdded(String propName, String host, ConfigProperty newProp) { 53 | Objects.requireNonNull(newProp, "ConfigEvent: newProp is required"); 54 | return new ConfigEvent(propName, Type.ADDED, host, null, newProp); 55 | } 56 | 57 | /** 58 | * Creates {@link Type#REMOVED} event for particular property. 59 | * 60 | * @param propName property name 61 | * @param host host 62 | * @param oldProp removed property 63 | * @return config event 64 | */ 65 | public static ConfigEvent createRemoved(String propName, String host, ConfigProperty oldProp) { 66 | Objects.requireNonNull(oldProp, "ConfigEvent: oldProp is required"); 67 | return new ConfigEvent(propName, Type.REMOVED, host, oldProp, null); 68 | } 69 | 70 | /** 71 | * Creates {@link Type#UPDATED} event for particular property. 72 | * 73 | * @param propName property name 74 | * @param host host 75 | * @param oldProp old property 76 | * @param newProp new property 77 | * @return config event 78 | */ 79 | public static ConfigEvent createUpdated( 80 | String propName, String host, ConfigProperty oldProp, ConfigProperty newProp) { 81 | Objects.requireNonNull(newProp, "ConfigEvent: newProp is required"); 82 | Objects.requireNonNull(oldProp, "ConfigEvent: oldProp is required"); 83 | return new ConfigEvent(propName, Type.UPDATED, host, oldProp, newProp); 84 | } 85 | 86 | public String getName() { 87 | return name; 88 | } 89 | 90 | public Date getTimestamp() { 91 | return timestamp; 92 | } 93 | 94 | public Type getType() { 95 | return type; 96 | } 97 | 98 | public String getHost() { 99 | return host; 100 | } 101 | 102 | public String getOldValue() { 103 | return oldValue; 104 | } 105 | 106 | public String getOldSource() { 107 | return oldSource; 108 | } 109 | 110 | public String getOldOrigin() { 111 | return oldOrigin; 112 | } 113 | 114 | public String getNewValue() { 115 | return newValue; 116 | } 117 | 118 | public String getNewSource() { 119 | return newSource; 120 | } 121 | 122 | public String getNewOrigin() { 123 | return newOrigin; 124 | } 125 | 126 | /** 127 | * Checks if property is changed. 128 | * 129 | * @return true if property is changed 130 | */ 131 | public boolean isChanged() { 132 | if (type == Type.ADDED || type == Type.REMOVED) { 133 | return true; 134 | } 135 | return !Objects.equals(this.oldSource, this.newSource) 136 | || !Objects.equals(this.oldOrigin, this.newOrigin) 137 | || !Objects.equals(this.oldValue, this.newValue); 138 | } 139 | 140 | @Override 141 | public String toString() { 142 | return "{\"name\":\" " 143 | + name 144 | + "\",\"timestamp\":\"" 145 | + timestamp 146 | + "\",\"type\":\"" 147 | + type 148 | + "\",\"host\":\"" 149 | + host 150 | + "\",\"oldValue\":\"" 151 | + oldValue 152 | + "\",\"oldSource\":\"" 153 | + oldSource 154 | + "\",\"oldOrigin\":\"" 155 | + oldOrigin 156 | + "\",\"newValue\":\"" 157 | + newValue 158 | + "\",\"newSource\":\"" 159 | + newSource 160 | + "\",\"newOrigin\":\"" 161 | + newOrigin 162 | + "\"}"; 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /config/src/main/java/io/scalecube/config/audit/ConfigEventListener.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.config.audit; 2 | 3 | import java.util.Collection; 4 | 5 | /** Listener of configuration changes events. */ 6 | public interface ConfigEventListener { 7 | 8 | /** Process configuration change event. */ 9 | void onEvents(Collection event); 10 | } 11 | -------------------------------------------------------------------------------- /config/src/main/java/io/scalecube/config/audit/LoggingConfigEventListener.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.config.audit; 2 | 3 | import java.util.Collection; 4 | import java.util.Comparator; 5 | import java.util.Objects; 6 | import org.slf4j.Logger; 7 | import org.slf4j.LoggerFactory; 8 | 9 | public class LoggingConfigEventListener implements ConfigEventListener { 10 | 11 | private static final Logger LOGGER = LoggerFactory.getLogger(LoggingConfigEventListener.class); 12 | 13 | @Override 14 | public void onEvents(Collection events) { 15 | if (!events.isEmpty()) { 16 | StringBuilder sb = new StringBuilder(); 17 | sb.append("["); 18 | events.stream() 19 | .sorted(Comparator.comparing(ConfigEvent::getName)) 20 | .forEach( 21 | event -> { 22 | sb.append("\n"); 23 | sb.append(event.getName()).append("=["); 24 | sb.append(propValueAsString(event)); 25 | sb.append("], "); 26 | sb.append("source="); 27 | sb.append(sourceAsString(event)); 28 | sb.append(", "); 29 | sb.append("origin="); 30 | sb.append(originAsString(event)); 31 | }); 32 | sb.append("\n").append("]"); 33 | LOGGER.info(sb.toString()); 34 | } 35 | } 36 | 37 | private static String originAsString(ConfigEvent event) { 38 | final String oldValue = event.getOldOrigin(); 39 | final String newValue = event.getNewOrigin(); 40 | 41 | if (Objects.equals(oldValue, newValue)) { 42 | return newValue; 43 | } 44 | return (oldValue == null || oldValue.isEmpty()) ? newValue : oldValue + "->" + newValue; 45 | } 46 | 47 | private static String sourceAsString(ConfigEvent event) { 48 | final String oldValue = event.getOldSource(); 49 | final String newValue = event.getNewSource(); 50 | 51 | if (Objects.equals(oldValue, newValue)) { 52 | return newValue; 53 | } 54 | return (oldValue == null || oldValue.isEmpty()) ? newValue : oldValue + "->" + newValue; 55 | } 56 | 57 | private static String propValueAsString(ConfigEvent event) { 58 | final String oldValue = event.getOldValue(); 59 | final String newValue = event.getNewValue(); 60 | 61 | if (Objects.equals(oldValue, newValue)) { 62 | return newValue; 63 | } 64 | return (oldValue == null || oldValue.isEmpty()) 65 | ? mask(newValue) 66 | : mask(oldValue) + "->" + mask(newValue); 67 | } 68 | 69 | private static String mask(String value) { 70 | if (value == null || value.isEmpty()) { 71 | return "null"; 72 | } 73 | if (value.length() < 5) { 74 | return "***"; 75 | } 76 | return value.replace(value.substring(2, value.length() - 2), "***"); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /config/src/main/java/io/scalecube/config/jmx/JmxConfigRegistry.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.config.jmx; 2 | 3 | import io.scalecube.config.ConfigPropertyInfo; 4 | import io.scalecube.config.ConfigRegistry; 5 | import io.scalecube.config.audit.ConfigEvent; 6 | import io.scalecube.config.source.ConfigSourceInfo; 7 | import java.util.Collection; 8 | import java.util.Collections; 9 | import java.util.stream.Collectors; 10 | 11 | public class JmxConfigRegistry implements JmxConfigRegistryMBean { 12 | 13 | private final ConfigRegistry configRegistry; 14 | 15 | public JmxConfigRegistry(ConfigRegistry configRegistry) { 16 | this.configRegistry = configRegistry; 17 | } 18 | 19 | @Override 20 | public Collection getProperties() { 21 | return configRegistry 22 | .getConfigProperties() 23 | .stream() 24 | .map(ConfigPropertyInfo::toString) 25 | .collect(Collectors.toList()); 26 | } 27 | 28 | @Override 29 | public Collection getSources() { 30 | return configRegistry 31 | .getConfigSources() 32 | .stream() 33 | .map(ConfigSourceInfo::toString) 34 | .collect(Collectors.toList()); 35 | } 36 | 37 | @Override 38 | public Collection getEvents() { 39 | return configRegistry 40 | .getRecentConfigEvents() 41 | .stream() 42 | .map(ConfigEvent::toString) 43 | .collect(Collectors.toList()); 44 | } 45 | 46 | @Override 47 | public Collection getSettings() { 48 | return Collections.singletonList(configRegistry.getSettings().toString()); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /config/src/main/java/io/scalecube/config/jmx/JmxConfigRegistryMBean.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.config.jmx; 2 | 3 | import java.util.Collection; 4 | 5 | public interface JmxConfigRegistryMBean { 6 | 7 | Collection getProperties(); 8 | 9 | Collection getSources(); 10 | 11 | Collection getEvents(); 12 | 13 | Collection getSettings(); 14 | } 15 | -------------------------------------------------------------------------------- /config/src/main/java/io/scalecube/config/keyvalue/KeyValueConfigEntity.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.config.keyvalue; 2 | 3 | import java.util.Objects; 4 | 5 | /** Generic entity class for key-value config data source. */ 6 | public final class KeyValueConfigEntity { 7 | /** 8 | * A config name. Non-persistent field. Being set in method {@link 9 | * #setConfigName(KeyValueConfigName)}. 10 | */ 11 | private KeyValueConfigName configName; 12 | 13 | /** Property name. Persistent not-nullable field. */ 14 | private String propName; 15 | 16 | /** Property value. Persistent not-nullable field. */ 17 | private String propValue; 18 | 19 | /** 20 | * Persistent indicator flag denoting intent to have actually the property key-value pair in data 21 | * source but have it in disabled state. 22 | */ 23 | private boolean disabled; 24 | 25 | public KeyValueConfigEntity() {} 26 | 27 | /** NOTE: this constructor exposed for test purpose only. */ 28 | KeyValueConfigEntity(String propName, String propValue, KeyValueConfigName configName) { 29 | this.configName = configName; 30 | this.propName = propName; 31 | this.propValue = propValue; 32 | } 33 | 34 | /** 35 | * Enhances this entity object with non-persistent configName, returns a copy. 36 | * 37 | * @param configName config name from where this entity object was loaded. 38 | * @return copy of this object with configName. 39 | */ 40 | public KeyValueConfigEntity setConfigName(KeyValueConfigName configName) { 41 | Objects.requireNonNull(configName); 42 | KeyValueConfigEntity entity = new KeyValueConfigEntity(); 43 | entity.configName = configName; 44 | entity.propName = this.propName; 45 | entity.propValue = this.propValue; 46 | entity.disabled = this.disabled; 47 | return entity; 48 | } 49 | 50 | public KeyValueConfigName getConfigName() { 51 | return configName; 52 | } 53 | 54 | public String getPropName() { 55 | return propName; 56 | } 57 | 58 | public void setPropName(String propName) { 59 | this.propName = propName; 60 | } 61 | 62 | public String getPropValue() { 63 | return propValue; 64 | } 65 | 66 | public void setPropValue(String propValue) { 67 | this.propValue = propValue; 68 | } 69 | 70 | public boolean getDisabled() { 71 | return disabled; 72 | } 73 | 74 | public void setDisabled(boolean disabled) { 75 | this.disabled = disabled; 76 | } 77 | 78 | @Override 79 | public String toString() { 80 | final StringBuilder sb = new StringBuilder("KeyValueConfigEntity{"); 81 | sb.append("configName=").append(configName); 82 | sb.append(", propName='").append(propName).append('\''); 83 | sb.append(", propValue='").append(propValue).append('\''); 84 | sb.append(", disabled=").append(disabled); 85 | sb.append('}'); 86 | return sb.toString(); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /config/src/main/java/io/scalecube/config/keyvalue/KeyValueConfigName.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.config.keyvalue; 2 | 3 | import java.util.Objects; 4 | import java.util.Optional; 5 | 6 | /** Generic key-value config name. Comes in two parts: group name and config collection name. */ 7 | public final class KeyValueConfigName { 8 | /** A group name. Nullable field. */ 9 | private final String groupName; 10 | 11 | /** A config collection name. Not null. */ 12 | private final String collectionName; 13 | 14 | public KeyValueConfigName(String groupName, String collectionName) { 15 | this.groupName = groupName; 16 | this.collectionName = Objects.requireNonNull(collectionName); 17 | } 18 | 19 | public String getQualifiedName() { 20 | return groupName != null ? groupName + '.' + collectionName : collectionName; 21 | } 22 | 23 | public Optional getGroupName() { 24 | return Optional.ofNullable(groupName); 25 | } 26 | 27 | public String getCollectionName() { 28 | return collectionName; 29 | } 30 | 31 | @Override 32 | public boolean equals(Object o) { 33 | if (this == o) { 34 | return true; 35 | } 36 | if (o == null || getClass() != o.getClass()) { 37 | return false; 38 | } 39 | KeyValueConfigName that = (KeyValueConfigName) o; 40 | return Objects.equals(groupName, that.groupName) 41 | && Objects.equals(collectionName, that.collectionName); 42 | } 43 | 44 | @Override 45 | public int hashCode() { 46 | return Objects.hash(groupName, collectionName); 47 | } 48 | 49 | @Override 50 | public String toString() { 51 | final StringBuilder sb = new StringBuilder("KeyValueConfigName{"); 52 | sb.append("groupName='").append(groupName).append('\''); 53 | sb.append(", collectionName='").append(collectionName).append('\''); 54 | sb.append('}'); 55 | return sb.toString(); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /config/src/main/java/io/scalecube/config/keyvalue/KeyValueConfigRepository.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.config.keyvalue; 2 | 3 | import java.util.List; 4 | 5 | /** Generic key-value config data access interface. */ 6 | public interface KeyValueConfigRepository { 7 | 8 | /** 9 | * Retrieves all key-value pairs under given config name. 10 | * 11 | * @param configName a config name. 12 | * @return list of key-value entries. 13 | * @throws Exception in case of any issue happened when accessing config data source. 14 | */ 15 | List findAll(KeyValueConfigName configName) throws Exception; 16 | } 17 | -------------------------------------------------------------------------------- /config/src/main/java/io/scalecube/config/keyvalue/KeyValueConfigSource.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.config.keyvalue; 2 | 3 | import io.scalecube.config.ConfigProperty; 4 | import io.scalecube.config.ConfigSourceNotAvailableException; 5 | import io.scalecube.config.source.ConfigSource; 6 | import io.scalecube.config.source.LoadedConfigProperty; 7 | import io.scalecube.config.utils.ThrowableUtil; 8 | import java.time.Duration; 9 | import java.util.ArrayList; 10 | import java.util.Arrays; 11 | import java.util.Collection; 12 | import java.util.Collections; 13 | import java.util.List; 14 | import java.util.Map; 15 | import java.util.Objects; 16 | import java.util.TreeMap; 17 | import java.util.concurrent.CompletableFuture; 18 | import java.util.concurrent.ExecutionException; 19 | import java.util.concurrent.Executor; 20 | import java.util.concurrent.Executors; 21 | import java.util.concurrent.ThreadFactory; 22 | import java.util.concurrent.TimeUnit; 23 | import java.util.concurrent.TimeoutException; 24 | import java.util.function.Supplier; 25 | import java.util.stream.Collector; 26 | import java.util.stream.Collectors; 27 | import org.slf4j.Logger; 28 | import org.slf4j.LoggerFactory; 29 | 30 | /** 31 | * Generic key-value config source. Communicates with concrete config data source (mongodb, redis, 32 | * zookeeper) using injectable {@link #repository}. 33 | */ 34 | public class KeyValueConfigSource implements ConfigSource { 35 | 36 | private static final Logger LOGGER = LoggerFactory.getLogger(KeyValueConfigSource.class); 37 | 38 | private static final ThreadFactory threadFactory; 39 | 40 | static { 41 | threadFactory = 42 | r -> { 43 | Thread thread = new Thread(r); 44 | thread.setDaemon(true); 45 | thread.setName("keyvalue-config-executor"); 46 | thread.setUncaughtExceptionHandler((t, e) -> LOGGER.error("Exception occurred", e)); 47 | return thread; 48 | }; 49 | } 50 | 51 | private static final Executor executor = Executors.newCachedThreadPool(threadFactory); 52 | 53 | private final KeyValueConfigRepository repository; 54 | private final Duration repositoryTimeout; 55 | private final List configNames; // calculated field 56 | 57 | private KeyValueConfigSource(Builder builder) { 58 | this.repository = builder.repository; 59 | this.repositoryTimeout = builder.repositoryTimeout; 60 | this.configNames = configureConfigNames(builder.groupList, builder.collectionName); 61 | } 62 | 63 | private static List configureConfigNames( 64 | List groupList, String collectionName) { 65 | List result = new ArrayList<>(); 66 | result.addAll(groupList); 67 | result.add(null); // by default 'root' group is always added 68 | return result.stream() 69 | .map(input -> new KeyValueConfigName(input, collectionName)) 70 | .collect(Collectors.toList()); 71 | } 72 | 73 | public static Builder withRepository(KeyValueConfigRepository repository) { 74 | return new Builder(repository); 75 | } 76 | 77 | public static Builder withRepository(KeyValueConfigRepository repository, String collectionName) { 78 | return new Builder(repository, collectionName); 79 | } 80 | 81 | @Override 82 | public Map loadConfig() { 83 | List>> futureList = 84 | configNames.stream().map(this::loadConfig).collect(Collectors.toList()); 85 | 86 | CompletableFuture allResults = 87 | CompletableFuture.allOf(futureList.toArray(new CompletableFuture[futureList.size()])); 88 | 89 | CompletableFuture>> joinedFuture = 90 | allResults.thenApply( 91 | input -> futureList.stream().map(CompletableFuture::join).collect(Collectors.toList())); 92 | 93 | List> resultList; 94 | try { 95 | resultList = joinedFuture.get(repositoryTimeout.toMillis(), TimeUnit.MILLISECONDS); 96 | } catch (ExecutionException e) { 97 | throw ThrowableUtil.propagate(e.getCause()); 98 | } catch (TimeoutException e) { 99 | String message = 100 | String.format("TimeoutException after '%s' millis", repositoryTimeout.toMillis()); 101 | throw new ConfigSourceNotAvailableException(message, e); 102 | } catch (InterruptedException e) { 103 | Thread.interrupted(); 104 | throw ThrowableUtil.propagate(e); 105 | } 106 | 107 | return resultList.stream() 108 | .flatMap(Collection::stream) 109 | .filter(i -> !i.getDisabled()) 110 | .collect( 111 | Collector.of( 112 | (Supplier>) TreeMap::new, 113 | (map, i) -> { 114 | String origin = i.getConfigName().getQualifiedName(); 115 | String name = i.getPropName(); 116 | String value = i.getPropValue(); 117 | map.putIfAbsent( 118 | name, 119 | LoadedConfigProperty.withNameAndValue(name, value).origin(origin).build()); 120 | }, 121 | (map1, map2) -> map1)); 122 | } 123 | 124 | private CompletableFuture> loadConfig(KeyValueConfigName configName) { 125 | return CompletableFuture.supplyAsync( 126 | () -> { 127 | List result; 128 | try { 129 | result = repository.findAll(configName); 130 | } catch (Exception e) { 131 | LOGGER.warn("[loadConfig] Exception occurred, configName: {}", configName, e); 132 | result = Collections.emptyList(); 133 | } 134 | return result; 135 | }, 136 | executor); 137 | } 138 | 139 | public static class Builder { 140 | private static final Duration DEFAULT_REPOSITORY_TIMEOUT = Duration.ofSeconds(3); 141 | private static final String DEFAULT_COLLECTION_NAME = "KeyValueConfigSource"; 142 | 143 | private final KeyValueConfigRepository repository; 144 | private final String collectionName; 145 | private List groupList = new ArrayList<>(); 146 | private Duration repositoryTimeout = DEFAULT_REPOSITORY_TIMEOUT; 147 | 148 | private Builder(KeyValueConfigRepository repository) { 149 | this(repository, DEFAULT_COLLECTION_NAME); 150 | } 151 | 152 | private Builder(KeyValueConfigRepository repository, String collectionName) { 153 | this.repository = Objects.requireNonNull(repository); 154 | this.collectionName = Objects.requireNonNull(collectionName); 155 | } 156 | 157 | public Builder groups(String... groups) { 158 | this.groupList = Arrays.asList(groups); 159 | return this; 160 | } 161 | 162 | public Builder groupList(List groupList) { 163 | this.groupList = groupList; 164 | return this; 165 | } 166 | 167 | public Builder repositoryTimeout(Duration repositoryTimeout) { 168 | this.repositoryTimeout = repositoryTimeout; 169 | return this; 170 | } 171 | 172 | public KeyValueConfigSource build() { 173 | return new KeyValueConfigSource(this); 174 | } 175 | } 176 | } 177 | -------------------------------------------------------------------------------- /config/src/main/java/io/scalecube/config/source/ConfigSource.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.config.source; 2 | 3 | import io.scalecube.config.ConfigProperty; 4 | import java.util.Map; 5 | 6 | /** Config source interface which represents specific provider of configuration properties. */ 7 | public interface ConfigSource { 8 | 9 | /** Loads all properties from the source. */ 10 | Map loadConfig(); 11 | } 12 | -------------------------------------------------------------------------------- /config/src/main/java/io/scalecube/config/source/ConfigSourceInfo.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.config.source; 2 | 3 | public final class ConfigSourceInfo { 4 | private String sourceName; 5 | private int priorityOrder; 6 | private String configSourceString; 7 | private String healthString; 8 | private String host; 9 | 10 | public String getSourceName() { 11 | return sourceName; 12 | } 13 | 14 | public void setSourceName(String sourceName) { 15 | this.sourceName = sourceName; 16 | } 17 | 18 | public int getPriorityOrder() { 19 | return priorityOrder; 20 | } 21 | 22 | public void setPriorityOrder(int priorityOrder) { 23 | this.priorityOrder = priorityOrder; 24 | } 25 | 26 | public String getConfigSourceString() { 27 | return configSourceString; 28 | } 29 | 30 | public void setConfigSourceString(String configSourceString) { 31 | this.configSourceString = configSourceString; 32 | } 33 | 34 | public String getHealthString() { 35 | return healthString; 36 | } 37 | 38 | public void setHealthString(String healthString) { 39 | this.healthString = healthString; 40 | } 41 | 42 | public String getHost() { 43 | return host; 44 | } 45 | 46 | public void setHost(String host) { 47 | this.host = host; 48 | } 49 | 50 | @Override 51 | public String toString() { 52 | return "{\"sourceName\":\" " 53 | + sourceName 54 | + "\",\"priorityOrder\":\"" 55 | + priorityOrder 56 | + "\",\"configSourceString\":\"" 57 | + configSourceString 58 | + "\",\"healthString\":\"" 59 | + healthString 60 | + "\",\"host\":\"" 61 | + host 62 | + "\"}"; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /config/src/main/java/io/scalecube/config/source/FileDirectoryConfigSource.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.config.source; 2 | 3 | import io.scalecube.config.ConfigProperty; 4 | import io.scalecube.config.ConfigSourceNotAvailableException; 5 | import java.io.File; 6 | import java.nio.file.LinkOption; 7 | import java.nio.file.Path; 8 | import java.nio.file.Paths; 9 | import java.util.Arrays; 10 | import java.util.Collections; 11 | import java.util.List; 12 | import java.util.Map; 13 | import java.util.Objects; 14 | import java.util.Optional; 15 | import java.util.StringJoiner; 16 | import java.util.TreeMap; 17 | import java.util.function.Predicate; 18 | import java.util.stream.Collectors; 19 | 20 | public final class FileDirectoryConfigSource extends FilteredPathConfigSource { 21 | private final Path directory; 22 | 23 | /** 24 | * Constructor. 25 | * 26 | * @param directory directory with configuration files 27 | * @param predicate predicate to match confgitratyion files 28 | */ 29 | public FileDirectoryConfigSource(String directory, Predicate predicate) { 30 | this(directory, Collections.singletonList(predicate)); 31 | } 32 | 33 | /** 34 | * Constructor. 35 | * 36 | * @param directory directory with configuration files 37 | * @param predicates list of predicates to match configuration files 38 | */ 39 | public FileDirectoryConfigSource(String directory, List> predicates) { 40 | super(predicates); 41 | Objects.requireNonNull(directory, "FileDirectoryConfigSource: directory is required"); 42 | this.directory = Paths.get(directory); 43 | } 44 | 45 | /** 46 | * Factory method to create {@code FileDirectoryConfigSource} instance using filename plus its 47 | * prefixPatterns. 48 | * 49 | * @param directory directory with configuration files 50 | * @param filename filename for template of configuration property file 51 | * @param prefixPattern pattern of prefix 52 | * @return new {@code FileDirectoryConfigSource} instance 53 | */ 54 | public static FileDirectoryConfigSource createWithPattern( 55 | String directory, String filename, String prefixPattern) { 56 | return createWithPattern(directory, filename, Collections.singletonList(prefixPattern)); 57 | } 58 | 59 | /** 60 | * Factory method to create {@code FileDirectoryConfigSource} instance using filename plus its 61 | * prefixPatterns. 62 | * 63 | * @param directory directory with configuration files 64 | * @param filename filename for template of configuration property file 65 | * @param prefixPatterns list of prefixPatterns (comma separated list of strings) 66 | * @return new {@code FileDirectoryConfigSource} instance 67 | */ 68 | public static FileDirectoryConfigSource createWithPattern( 69 | String directory, String filename, List prefixPatterns) { 70 | Objects.requireNonNull(directory, "FileDirectoryConfigSource: directory is required"); 71 | Objects.requireNonNull(filename, "FileDirectoryConfigSource: filename is required"); 72 | Objects.requireNonNull(prefixPatterns, "FileDirectoryConfigSource: prefixPatterns is required"); 73 | return new FileDirectoryConfigSource( 74 | directory, preparePatternPredicates(filename, prefixPatterns)); 75 | } 76 | 77 | @Override 78 | public Map loadConfig() { 79 | Path realDirectory; 80 | try { 81 | realDirectory = directory.toRealPath(LinkOption.NOFOLLOW_LINKS); 82 | } catch (Exception e) { 83 | String message = 84 | String.format( 85 | "Exception at FileDirectoryConfigSource (directory='%s'), cause: %s", directory, e); 86 | throw new ConfigSourceNotAvailableException(message, e); 87 | } 88 | 89 | File[] files = Optional.ofNullable(realDirectory.toFile().listFiles()).orElse(new File[0]); 90 | List pathCollection = Arrays.stream(files).map(File::toPath).collect(Collectors.toList()); 91 | 92 | Map result = new TreeMap<>(); 93 | filterAndCollectInOrder( 94 | predicates.iterator(), 95 | loadConfigMap(pathCollection), 96 | (path, map) -> 97 | map.entrySet() 98 | .forEach( 99 | entry -> 100 | result.putIfAbsent( 101 | entry.getKey(), 102 | LoadedConfigProperty.withNameAndValue(entry) 103 | .origin(path.toString()) 104 | .build()))); 105 | return result; 106 | } 107 | 108 | @Override 109 | public String toString() { 110 | return new StringJoiner(", ", FileDirectoryConfigSource.class.getSimpleName() + "[", "]") 111 | .add("directory=" + directory) 112 | .toString(); 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /config/src/main/java/io/scalecube/config/source/FilteredPathConfigSource.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.config.source; 2 | 3 | import io.scalecube.config.utils.ThrowableUtil; 4 | import java.io.InputStream; 5 | import java.nio.file.Path; 6 | import java.util.Collection; 7 | import java.util.Collections; 8 | import java.util.Enumeration; 9 | import java.util.HashMap; 10 | import java.util.Iterator; 11 | import java.util.List; 12 | import java.util.Map; 13 | import java.util.Objects; 14 | import java.util.Properties; 15 | import java.util.function.BiConsumer; 16 | import java.util.function.Predicate; 17 | import java.util.stream.Collectors; 18 | import java.util.stream.Stream; 19 | 20 | public abstract class FilteredPathConfigSource implements ConfigSource { 21 | 22 | private static final String FILENAME_PATTERN = "(?^%s.*)\\.(?%s$)"; 23 | 24 | protected final List> predicates; 25 | 26 | protected FilteredPathConfigSource(List> predicates) { 27 | Objects.requireNonNull(predicates, "FilteredPathConfigSource: predicates are required"); 28 | this.predicates = Collections.unmodifiableList(predicates); 29 | } 30 | 31 | protected final Map> loadConfigMap(Collection pathCollection) { 32 | return pathCollection.stream() 33 | .filter(path -> predicates.stream().anyMatch(predicate -> predicate.test(path))) 34 | .collect(Collectors.toMap(path -> path, FilteredPathConfigSource::loadProperties)); 35 | } 36 | 37 | static List> preparePatternPredicates( 38 | String filename, List prefixPatterns) { 39 | 40 | return Stream.concat( 41 | prefixPatterns.stream() 42 | .>map( 43 | prefixPattern -> 44 | path -> preparePatternPredicate(path, prefixPattern, filename)), 45 | // exact filename (without prefix pattern) equality goes latest 46 | Stream.of(path -> preparePatternPredicate(path, filename))) 47 | .collect(Collectors.toList()); 48 | } 49 | 50 | static void filterAndCollectInOrder( 51 | Iterator> predicateIterator, 52 | Map> configMap, 53 | BiConsumer> configCollector) { 54 | 55 | if (!predicateIterator.hasNext()) { 56 | return; 57 | } 58 | 59 | Predicate groupPredicate = predicateIterator.next(); 60 | List groups = 61 | configMap.keySet().stream().filter(groupPredicate).collect(Collectors.toList()); 62 | for (Path group : groups) { 63 | Map map = configMap.get(group); 64 | if (!map.isEmpty()) { 65 | configCollector.accept(group, map); 66 | } 67 | } 68 | 69 | filterAndCollectInOrder(predicateIterator, configMap, configCollector); 70 | } 71 | 72 | private static Map loadProperties(Path input) { 73 | try (InputStream is = input.toUri().toURL().openStream()) { 74 | Properties properties = new Properties(); 75 | properties.load(is); 76 | return fromProperties(properties); 77 | } catch (Exception e) { 78 | throw ThrowableUtil.propagate(e); 79 | } 80 | } 81 | 82 | private static Map fromProperties(Properties properties) { 83 | Map map = new HashMap<>(); 84 | for (Enumeration e = properties.propertyNames(); e.hasMoreElements(); ) { 85 | String key = (String) e.nextElement(); 86 | map.put(key, properties.getProperty(key)); 87 | } 88 | return map; 89 | } 90 | 91 | private static boolean preparePatternPredicate(Path path, String filename) { 92 | return path.getFileName().toString().equals(filename); 93 | } 94 | 95 | private static boolean preparePatternPredicate(Path path, String prefixPattern, String filename) { 96 | return path.getFileName() 97 | .toString() 98 | .matches(String.format(FILENAME_PATTERN, prefixPattern, filename)); 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /config/src/main/java/io/scalecube/config/source/LoadedConfigProperty.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.config.source; 2 | 3 | import io.scalecube.config.ConfigProperty; 4 | import java.util.Map; 5 | import java.util.Optional; 6 | 7 | // Helper class 8 | public final class LoadedConfigProperty implements ConfigProperty { 9 | private final String name; // not null 10 | private final String source; // nullable 11 | private final String origin; // nullable 12 | private final String value; // nullable 13 | 14 | private LoadedConfigProperty(Builder builder) { 15 | this.name = builder.name; 16 | this.source = builder.source; 17 | this.origin = builder.origin; 18 | this.value = builder.value; 19 | } 20 | 21 | public static LoadedConfigProperty forNameAndValue(String name, String value) { 22 | return withNameAndValue(name, value).build(); 23 | } 24 | 25 | public static Builder withNameAndValue(Map.Entry entry) { 26 | return withNameAndValue(entry.getKey(), entry.getValue()); 27 | } 28 | 29 | public static Builder withNameAndValue(String name, String value) { 30 | return new Builder(name, value); 31 | } 32 | 33 | /** 34 | * Creates builder from given config property. 35 | * 36 | * @param property config property 37 | * @return builder instance 38 | */ 39 | public static Builder withCopyFrom(ConfigProperty property) { 40 | Builder builder = new Builder(property.name(), property.valueAsString(null)); 41 | builder.source = property.source().orElse(null); 42 | builder.origin = property.origin().orElse(null); 43 | return builder; 44 | } 45 | 46 | @Override 47 | public String name() { 48 | return name; 49 | } 50 | 51 | @Override 52 | public Optional source() { 53 | return Optional.ofNullable(source); 54 | } 55 | 56 | @Override 57 | public Optional origin() { 58 | return Optional.ofNullable(origin); 59 | } 60 | 61 | @Override 62 | public Optional valueAsString() { 63 | return Optional.ofNullable(value); 64 | } 65 | 66 | @Override 67 | public String valueAsString(String defaultValue) { 68 | return valueAsString().orElse(defaultValue); 69 | } 70 | 71 | @Override 72 | public String toString() { 73 | final StringBuilder sb = new StringBuilder("LoadedConfigProperty{"); 74 | sb.append("name='").append(name).append('\''); 75 | sb.append(", source='").append(source).append('\''); 76 | sb.append(", origin='").append(origin).append('\''); 77 | sb.append(", value='").append(value).append('\''); 78 | sb.append('}'); 79 | return sb.toString(); 80 | } 81 | 82 | public static class Builder { 83 | private final String name; 84 | private final String value; 85 | private String source; 86 | private String origin; 87 | 88 | public Builder(String name, String value) { 89 | this.name = name; 90 | this.value = value; 91 | } 92 | 93 | public Builder source(String source) { 94 | this.source = source; 95 | return this; 96 | } 97 | 98 | public Builder origin(String origin) { 99 | this.origin = origin; 100 | return this; 101 | } 102 | 103 | public LoadedConfigProperty build() { 104 | return new LoadedConfigProperty(this); 105 | } 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /config/src/main/java/io/scalecube/config/source/SystemEnvironmentConfigSource.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.config.source; 2 | 3 | import io.scalecube.config.ConfigProperty; 4 | import java.util.Map; 5 | import java.util.TreeMap; 6 | 7 | public final class SystemEnvironmentConfigSource implements ConfigSource { 8 | 9 | private Map loadedConfig; 10 | 11 | @Override 12 | public Map loadConfig() { 13 | if (loadedConfig != null) { 14 | return loadedConfig; 15 | } 16 | 17 | Map env = System.getenv(); 18 | Map result = new TreeMap<>(); 19 | 20 | for (Map.Entry entry : env.entrySet()) { 21 | String propName = entry.getKey(); 22 | result.put(propName, LoadedConfigProperty.forNameAndValue(propName, entry.getValue())); 23 | } 24 | 25 | return loadedConfig = result; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /config/src/main/java/io/scalecube/config/source/SystemEnvironmentSingleVariableConfigSource.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.config.source; 2 | 3 | import io.scalecube.config.ConfigProperty; 4 | import java.util.Map; 5 | import java.util.TreeMap; 6 | 7 | public final class SystemEnvironmentSingleVariableConfigSource implements ConfigSource { 8 | 9 | private static final String VAR_NAME = "SETTINGS"; 10 | 11 | private static final String DELIMITER = ";"; 12 | private static final String SEPARATOR = "="; 13 | 14 | private final String varName; 15 | private final String delimiter; 16 | private final String separator; 17 | 18 | private Map loadedConfig; 19 | 20 | public SystemEnvironmentSingleVariableConfigSource() { 21 | this(VAR_NAME, DELIMITER, SEPARATOR); 22 | } 23 | 24 | /** 25 | * Constructor. 26 | * 27 | * @param varName varName 28 | * @param delimiter delimiter 29 | * @param separator separator 30 | */ 31 | public SystemEnvironmentSingleVariableConfigSource( 32 | String varName, String delimiter, String separator) { 33 | this.varName = varName; 34 | this.delimiter = delimiter; 35 | this.separator = separator; 36 | } 37 | 38 | @Override 39 | public Map loadConfig() { 40 | if (loadedConfig != null) { 41 | return loadedConfig; 42 | } 43 | 44 | Map result = new TreeMap<>(); 45 | 46 | String settings = System.getenv(varName); 47 | 48 | if (settings != null && !settings.isEmpty()) { 49 | for (String str : settings.split(delimiter)) { 50 | String[] split = str.split(separator); 51 | if (split.length != 2) { 52 | break; 53 | } 54 | 55 | String propName = split[0]; 56 | String propValue = split[1]; 57 | 58 | // store value as system property under mapped name 59 | System.setProperty(propName, propValue); 60 | result.put(propName, LoadedConfigProperty.forNameAndValue(propName, propValue)); 61 | } 62 | } 63 | 64 | return loadedConfig = result; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /config/src/main/java/io/scalecube/config/source/SystemEnvironmentVariablesConfigSource.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.config.source; 2 | 3 | import io.scalecube.config.ConfigProperty; 4 | import java.util.Map; 5 | import java.util.Objects; 6 | import java.util.TreeMap; 7 | import java.util.function.Consumer; 8 | 9 | public final class SystemEnvironmentVariablesConfigSource implements ConfigSource { 10 | 11 | private Map loadedConfig; 12 | 13 | // from-to environment variables mappings 14 | private final Map mappings = new TreeMap<>(); 15 | 16 | public SystemEnvironmentVariablesConfigSource(Consumer consumer) { 17 | consumer.accept(new MapperImpl()); 18 | } 19 | 20 | @Override 21 | public Map loadConfig() { 22 | if (loadedConfig != null) { 23 | return loadedConfig; 24 | } 25 | 26 | Map result = new TreeMap<>(); 27 | 28 | System.getenv() 29 | .forEach( 30 | (propName, propValue) -> { 31 | String dst = mappings.get(propName); 32 | if (dst != null) { 33 | // store value as system property under mapped name 34 | System.setProperty(dst, propValue); 35 | result.put(dst, LoadedConfigProperty.forNameAndValue(dst, propValue)); 36 | } else { 37 | result.put(propName, LoadedConfigProperty.forNameAndValue(propName, propValue)); 38 | } 39 | }); 40 | 41 | return loadedConfig = result; 42 | } 43 | 44 | // Stores mapping between environemnt variable name and corresponding system property name. 45 | public interface Mapper { 46 | 47 | Mapper map(String src, String dst); 48 | } 49 | 50 | // Inner implementation class 51 | private class MapperImpl implements Mapper { 52 | 53 | @Override 54 | public Mapper map(String src, String dst) { 55 | Objects.requireNonNull(src); 56 | Objects.requireNonNull(dst); 57 | mappings.put(src, dst); 58 | return this; 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /config/src/main/java/io/scalecube/config/source/SystemPropertiesConfigSource.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.config.source; 2 | 3 | import io.scalecube.config.ConfigProperty; 4 | import java.util.Enumeration; 5 | import java.util.Map; 6 | import java.util.Properties; 7 | import java.util.TreeMap; 8 | 9 | public final class SystemPropertiesConfigSource implements ConfigSource { 10 | private Map loadedConfig; 11 | 12 | private final ConfigSource configSource; 13 | 14 | public SystemPropertiesConfigSource() { 15 | this(null); 16 | } 17 | 18 | public SystemPropertiesConfigSource(ConfigSource configSource) { 19 | this.configSource = configSource; 20 | } 21 | 22 | @Override 23 | public Map loadConfig() { 24 | if (loadedConfig != null) { 25 | return loadedConfig; 26 | } 27 | 28 | Properties properties = System.getProperties(); 29 | if (configSource != null) { 30 | properties = mergeSystemProperties(configSource.loadConfig(), properties); 31 | } 32 | 33 | Map result = new TreeMap<>(); 34 | 35 | for (Enumeration e = properties.propertyNames(); e.hasMoreElements(); ) { 36 | String propName = (String) e.nextElement(); 37 | result.put( 38 | propName, 39 | LoadedConfigProperty.forNameAndValue(propName, properties.getProperty(propName))); 40 | } 41 | 42 | return loadedConfig = result; 43 | } 44 | 45 | private static Properties mergeSystemProperties( 46 | Map overrideConfig, Properties properties) { 47 | 48 | final Properties finalProperties = new Properties(); 49 | 50 | overrideConfig.values().stream() 51 | .filter(p -> p.valueAsString().isPresent()) 52 | .forEach(p -> finalProperties.put(p.name(), p.valueAsString(null))); 53 | 54 | finalProperties.putAll(properties); 55 | 56 | finalProperties.forEach((key, value) -> System.setProperty((String) key, (String) value)); 57 | 58 | return finalProperties; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /config/src/main/java/io/scalecube/config/utils/ThrowableUtil.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.config.utils; 2 | 3 | public final class ThrowableUtil { 4 | 5 | private ThrowableUtil() { 6 | // Do not instantiate 7 | } 8 | 9 | /** 10 | * Propagates throwable as-is if throwable is instance of {@link RuntimeException} or {@link 11 | * Error}. In other case wraps into {@link RuntimeException}. 12 | * 13 | * @param throwable the throwable to be propagated 14 | * @return runtime exception 15 | */ 16 | public static RuntimeException propagate(Throwable throwable) { 17 | propagateIfInstanceOf(throwable, Error.class); 18 | propagateIfInstanceOf(throwable, RuntimeException.class); 19 | throw new RuntimeException(throwable); 20 | } 21 | 22 | private static void propagateIfInstanceOf( 23 | Throwable throwable, Class type) throws X { 24 | if (type.isInstance(throwable)) { 25 | throw type.cast(throwable); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /config/src/test/java/io/scalecube/config/ObjectPropertyFieldTest.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.config; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertEquals; 4 | import static org.junit.jupiter.api.Assertions.assertFalse; 5 | import static org.junit.jupiter.api.Assertions.assertThrows; 6 | import static org.junit.jupiter.api.Assertions.assertTrue; 7 | 8 | import com.google.common.collect.ImmutableList; 9 | import com.google.common.collect.ImmutableMap; 10 | import java.lang.reflect.Field; 11 | import java.time.Duration; 12 | import java.util.ArrayList; 13 | import java.util.HashMap; 14 | import java.util.List; 15 | import java.util.Map; 16 | import java.util.stream.Collectors; 17 | import java.util.stream.Stream; 18 | import org.junit.jupiter.api.Test; 19 | 20 | class ObjectPropertyFieldTest { 21 | 22 | private static final String propName = "dummy"; 23 | 24 | @Test 25 | void testPrimitiveObjectPropertyField() throws Exception { 26 | PrimitiveClass instance = new PrimitiveClass(); 27 | 28 | Class clazz = PrimitiveClass.class; 29 | ObjectPropertyField field_iii = 30 | new ObjectPropertyField(clazz.getDeclaredField("iii"), propName); 31 | ObjectPropertyField field_ddd = 32 | new ObjectPropertyField(clazz.getDeclaredField("ddd"), propName); 33 | ObjectPropertyField field_bbb = 34 | new ObjectPropertyField(clazz.getDeclaredField("bbb"), propName); 35 | ObjectPropertyField field_lll = 36 | new ObjectPropertyField(clazz.getDeclaredField("lll"), propName); 37 | 38 | field_iii.applyValueParser(instance, "1"); 39 | field_ddd.applyValueParser(instance, "1E+7"); 40 | field_bbb.applyValueParser(instance, "false"); 41 | field_lll.applyValueParser(instance, "1"); 42 | 43 | assertEquals(1, instance.iii); 44 | assertEquals(1e7, instance.ddd); 45 | assertFalse(instance.bbb); 46 | assertEquals(1, instance.lll); 47 | } 48 | 49 | @Test 50 | void testNonPrimitiveObjectPropertyField() throws Exception { 51 | NonPrimitiveClass instance = new NonPrimitiveClass(); 52 | 53 | Class clazz = NonPrimitiveClass.class; 54 | ObjectPropertyField field_string = 55 | new ObjectPropertyField(clazz.getDeclaredField("str"), propName); 56 | ObjectPropertyField field_duration = 57 | new ObjectPropertyField(clazz.getDeclaredField("duration"), propName); 58 | 59 | field_string.applyValueParser(instance, "just str"); 60 | field_duration.applyValueParser(instance, "100ms"); 61 | 62 | assertEquals("just str", instance.str); 63 | assertEquals(Duration.ofMillis(100), instance.duration); 64 | } 65 | 66 | @Test 67 | void testListObjectPropertyField() throws Exception { 68 | TypedListConfigClass instance = new TypedListConfigClass(); 69 | 70 | Class clazz = TypedListConfigClass.class; 71 | ObjectPropertyField field_integerList = 72 | new ObjectPropertyField(clazz.getDeclaredField("integerList"), propName); 73 | 74 | field_integerList.applyValueParser(instance, "1,2,3"); 75 | 76 | assertEquals(Stream.of(1, 2, 3).collect(Collectors.toList()), instance.integerList); 77 | } 78 | 79 | @Test 80 | void testMultimapObjectPropertyField() throws Exception { 81 | Map> expectedMultimap = 82 | ImmutableMap.>builder() 83 | .put("key1", ImmutableList.of(1)) 84 | .put("key2", ImmutableList.of(2, 3, 4)) 85 | .put("key3", ImmutableList.of(5)) 86 | .build(); 87 | TypedMultimapConfigClass instance = new TypedMultimapConfigClass(); 88 | 89 | Field target = instance.getClass().getDeclaredField("integerMultimap"); 90 | ObjectPropertyField fieldIntegerMultimap = new ObjectPropertyField(target, propName); 91 | 92 | fieldIntegerMultimap.applyValueParser(instance, "key1=1,key2=2,3,4,key3=5"); 93 | 94 | assertEquals(expectedMultimap, instance.integerMultimap); 95 | } 96 | 97 | @Test 98 | void testUntypedListNotSupported() { 99 | UntypedListConfigClass instance = new UntypedListConfigClass(); 100 | 101 | Class clazz = UntypedListConfigClass.class; 102 | 103 | assertThrows( 104 | IllegalArgumentException.class, 105 | () -> { 106 | ObjectPropertyField field_list = 107 | new ObjectPropertyField(clazz.getDeclaredField("list"), propName); 108 | field_list.applyValueParser(instance, "1,2,3"); 109 | }, 110 | "ObjectPropertyField: unsupported type on field"); 111 | } 112 | 113 | @Test 114 | void testStaticOrFinalFieldsInConfigClassNotSupported() { 115 | Class clazz = ConfigClassWithStaticOrFinalField.class; 116 | 117 | final IllegalArgumentException exception = 118 | assertThrows( 119 | IllegalArgumentException.class, 120 | () -> { 121 | new ObjectPropertyField(clazz.getDeclaredField("defaultInstance"), propName); 122 | new ObjectPropertyField(clazz.getDeclaredField("finalInt"), propName); 123 | }); 124 | 125 | assertTrue( 126 | exception 127 | .getMessage() 128 | .startsWith("ObjectPropertyField: 'static' or 'final' declaration is not supported")); 129 | } 130 | 131 | private static class PrimitiveClass { 132 | private int iii = 0; 133 | private double ddd = 0; 134 | private boolean bbb = true; 135 | private long lll = 0; 136 | } 137 | 138 | private static class NonPrimitiveClass { 139 | private String str = ""; 140 | private Duration duration = Duration.ofMillis(0); 141 | } 142 | 143 | private static class TypedListConfigClass { 144 | private List integerList = new ArrayList<>(); 145 | } 146 | 147 | private static class TypedMultimapConfigClass { 148 | private Map> integerMultimap = new HashMap<>(); 149 | } 150 | 151 | private static class UntypedListConfigClass { 152 | private List list = new ArrayList<>(); 153 | } 154 | 155 | static class ConfigClassWithStaticOrFinalField { 156 | static final ConfigClassWithStaticOrFinalField defaultInstance = 157 | new ConfigClassWithStaticOrFinalField(); 158 | 159 | private final int finalInt = 1; 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /config/src/test/java/io/scalecube/config/SimpleConfigPropertyManyInstancesTest.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.config; 2 | 3 | import static io.scalecube.config.TestUtil.WAIT_FOR_RELOAD_PERIOD_MILLIS; 4 | import static io.scalecube.config.TestUtil.mapBuilder; 5 | import static io.scalecube.config.TestUtil.newConfigRegistry; 6 | import static io.scalecube.config.TestUtil.toConfigProps; 7 | import static org.junit.jupiter.api.Assertions.assertEquals; 8 | import static org.junit.jupiter.api.Assertions.assertFalse; 9 | import static org.junit.jupiter.api.Assertions.assertTrue; 10 | import static org.mockito.Mockito.verify; 11 | import static org.mockito.Mockito.when; 12 | 13 | import com.google.common.collect.ImmutableList; 14 | import com.google.common.collect.ImmutableMap; 15 | import io.scalecube.config.source.ConfigSource; 16 | import java.util.Collections; 17 | import java.util.concurrent.TimeUnit; 18 | import org.junit.jupiter.api.Test; 19 | import org.junit.jupiter.api.extension.ExtendWith; 20 | import org.mockito.Mock; 21 | import org.mockito.junit.jupiter.MockitoExtension; 22 | 23 | @ExtendWith(MockitoExtension.class) 24 | class SimpleConfigPropertyManyInstancesTest { 25 | 26 | @Mock private ConfigSource configSource; 27 | @Mock private SideEffect sideEffect1; 28 | @Mock private SideEffect sideEffect2; 29 | 30 | @Test 31 | void testManyInstancesValueNullInitially() { 32 | when(configSource.loadConfig()).thenReturn(toConfigProps(mapBuilder().build())); 33 | ConfigRegistryImpl configRegistry = newConfigRegistry(configSource); 34 | 35 | DoubleConfigProperty doubleProperty = configRegistry.doubleProperty("prop"); 36 | assertFalse(doubleProperty.value().isPresent()); 37 | 38 | StringConfigProperty stringProperty = configRegistry.stringProperty("prop"); 39 | assertFalse(stringProperty.value().isPresent()); 40 | } 41 | 42 | @Test 43 | void testReloadValueBecameNotNull() throws Exception { 44 | when(configSource.loadConfig()) 45 | .thenReturn(toConfigProps(mapBuilder().build())) 46 | .thenReturn(toConfigProps(mapBuilder().put("prop", "1.e-3").build())); 47 | ConfigRegistryImpl configRegistry = newConfigRegistry(configSource); 48 | 49 | DoubleConfigProperty doubleProperty = configRegistry.doubleProperty("prop"); 50 | doubleProperty.addCallback((d1, d2) -> sideEffect1.apply(d1, d2)); 51 | assertFalse(doubleProperty.value().isPresent()); 52 | 53 | StringConfigProperty stringProperty = configRegistry.stringProperty("prop"); 54 | stringProperty.addCallback((s1, s2) -> sideEffect2.apply(s1, s2)); 55 | assertFalse(stringProperty.value().isPresent()); 56 | 57 | TimeUnit.MILLISECONDS.sleep(WAIT_FOR_RELOAD_PERIOD_MILLIS); 58 | 59 | assertTrue(doubleProperty.value().isPresent()); 60 | verify(sideEffect1).apply(null, 0.001); 61 | 62 | assertTrue(stringProperty.value().isPresent()); 63 | verify(sideEffect2).apply(null, "1.e-3"); 64 | } 65 | 66 | @Test 67 | void testManyInstancesNoValidationOnBoths() throws Exception { 68 | when(configSource.loadConfig()) 69 | .thenReturn(toConfigProps(mapBuilder().put("prop", "1").build())); 70 | ConfigRegistryImpl configRegistry = newConfigRegistry(configSource); 71 | 72 | ListConfigProperty intListProperty = configRegistry.intListProperty("prop"); 73 | assertTrue(intListProperty.value().isPresent()); 74 | assertEquals(Collections.singletonList(1), intListProperty.value().get()); 75 | 76 | LongConfigProperty longProperty = configRegistry.longProperty("prop"); 77 | assertTrue(longProperty.value().isPresent()); 78 | assertEquals(1, (long) longProperty.value().get()); 79 | } 80 | 81 | @Test 82 | void testReloadValidationPassedOnBoths() throws Exception { 83 | when(configSource.loadConfig()) 84 | .thenReturn(toConfigProps(mapBuilder().put("prop", "1").build())) 85 | .thenReturn(toConfigProps(mapBuilder().put("prop", "42").build())); 86 | ConfigRegistryImpl configRegistry = newConfigRegistry(configSource); 87 | 88 | StringConfigProperty stringProperty = configRegistry.stringProperty("prop"); 89 | stringProperty.addValidator(s -> s.length() >= 1); 90 | stringProperty.addCallback((s1, s2) -> sideEffect1.apply(s1, s2)); 91 | assertTrue(stringProperty.value().isPresent()); 92 | assertEquals("1", stringProperty.valueAsString().get()); 93 | 94 | IntConfigProperty intProperty = configRegistry.intProperty("prop"); 95 | intProperty.addValidator(i -> i >= 1); 96 | intProperty.addCallback((i1, i2) -> sideEffect2.apply(i1, i2)); 97 | assertTrue(intProperty.value().isPresent()); 98 | assertEquals(1, (int) intProperty.value().get()); 99 | 100 | TimeUnit.MILLISECONDS.sleep(WAIT_FOR_RELOAD_PERIOD_MILLIS); 101 | 102 | assertTrue(stringProperty.value().isPresent()); 103 | assertEquals("42", stringProperty.valueAsString().get()); 104 | 105 | assertTrue(intProperty.value().isPresent()); 106 | assertEquals(42, (int) intProperty.value().get()); 107 | 108 | verify(sideEffect1).apply("1", "42"); 109 | verify(sideEffect2).apply(1, 42); 110 | } 111 | 112 | @Test 113 | void testManyInstancesListTypeAndMultimapTypeAndSimplePropertyType() { 114 | when(configSource.loadConfig()) 115 | .thenReturn(toConfigProps(mapBuilder().put("prop", "key=value").build())); 116 | ConfigRegistryImpl configRegistry = newConfigRegistry(configSource); 117 | 118 | StringConfigProperty stringProperty = configRegistry.stringProperty("prop"); 119 | assertEquals("key=value", stringProperty.valueOrThrow()); 120 | 121 | ListConfigProperty stringListProperty = configRegistry.stringListProperty("prop"); 122 | assertEquals(ImmutableList.of("key=value"), stringListProperty.valueOrThrow()); 123 | 124 | MultimapConfigProperty stringMultimapProperty = 125 | configRegistry.stringMultimapProperty("prop"); 126 | assertEquals( 127 | ImmutableMap.of("key", ImmutableList.of("value")), stringMultimapProperty.valueOrThrow()); 128 | } 129 | 130 | public interface SideEffect { 131 | boolean apply(Object t1, Object t2); 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /config/src/test/java/io/scalecube/config/TestUtil.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.config; 2 | 3 | import com.google.common.collect.ImmutableMap; 4 | import io.scalecube.config.source.ConfigSource; 5 | import io.scalecube.config.source.LoadedConfigProperty; 6 | import java.util.HashMap; 7 | import java.util.Map; 8 | 9 | public class TestUtil { 10 | 11 | public static final int RELOAD_PERIOD_SEC = 1; 12 | public static final long WAIT_FOR_RELOAD_PERIOD_MILLIS = RELOAD_PERIOD_SEC * 1500; 13 | 14 | public static Map toConfigProps(Map props) { 15 | Map propertyMap = new HashMap<>(); 16 | for (Map.Entry entry : props.entrySet()) { 17 | propertyMap.put( 18 | entry.getKey(), LoadedConfigProperty.forNameAndValue(entry.getKey(), entry.getValue())); 19 | } 20 | return propertyMap; 21 | } 22 | 23 | public static ConfigRegistryImpl newConfigRegistry(ConfigSource configSource) { 24 | ConfigRegistryImpl configRegistry; 25 | configRegistry = 26 | new ConfigRegistryImpl( 27 | ConfigRegistrySettings.builder() 28 | .jmxEnabled(false) 29 | .keepRecentConfigEvents(0) 30 | .addLastSource("source", configSource) 31 | .reloadIntervalSec(RELOAD_PERIOD_SEC) 32 | .build()); 33 | configRegistry.init(); 34 | return configRegistry; 35 | } 36 | 37 | public static ImmutableMap.Builder mapBuilder() { 38 | return ImmutableMap.builder(); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /config/src/test/java/io/scalecube/config/keyvalue/KeyValueConfigSourceTest.java: -------------------------------------------------------------------------------- 1 | package io.scalecube.config.keyvalue; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertEquals; 4 | import static org.junit.jupiter.api.Assertions.assertThrows; 5 | import static org.mockito.Mockito.doAnswer; 6 | import static org.mockito.Mockito.doReturn; 7 | import static org.mockito.Mockito.doThrow; 8 | 9 | import com.google.common.collect.ImmutableList; 10 | import io.scalecube.config.ConfigProperty; 11 | import io.scalecube.config.ConfigSourceNotAvailableException; 12 | import java.time.Duration; 13 | import java.util.Map; 14 | import org.junit.jupiter.api.BeforeEach; 15 | import org.junit.jupiter.api.Test; 16 | import org.junit.jupiter.api.extension.ExtendWith; 17 | import org.mockito.Mock; 18 | import org.mockito.junit.jupiter.MockitoExtension; 19 | import org.mockito.stubbing.Answer; 20 | 21 | @ExtendWith(MockitoExtension.class) 22 | class KeyValueConfigSourceTest { 23 | 24 | @Mock private KeyValueConfigRepository repository; 25 | 26 | private KeyValueConfigSource configSource; 27 | private String collectionName; 28 | private String g1; 29 | private String g2; 30 | 31 | @BeforeEach 32 | void setup() { 33 | collectionName = "config"; 34 | g1 = "group1"; 35 | g2 = "group2"; 36 | configSource = 37 | KeyValueConfigSource.withRepository(repository, collectionName) 38 | .repositoryTimeout(Duration.ofMillis(300)) 39 | .groups(g1, g2) 40 | .build(); 41 | } 42 | 43 | @Test 44 | void testKeyValueLoadConfig() throws Exception { 45 | KeyValueConfigName n1 = new KeyValueConfigName(g1, collectionName); 46 | KeyValueConfigName n2 = new KeyValueConfigName(g2, collectionName); 47 | KeyValueConfigName root = new KeyValueConfigName(null, collectionName); 48 | KeyValueConfigEntity entity1 = new KeyValueConfigEntity("p1", "v1", n1); 49 | KeyValueConfigEntity entity2 = new KeyValueConfigEntity("p2", "v2", n2); 50 | KeyValueConfigEntity entity3 = new KeyValueConfigEntity("p1", "v1", root); 51 | KeyValueConfigEntity entity4 = new KeyValueConfigEntity("p2", "v2", root); 52 | KeyValueConfigEntity entity5 = new KeyValueConfigEntity("p42", "v42", root); 53 | 54 | doReturn(ImmutableList.of(entity1)).when(repository).findAll(n1); 55 | doReturn(ImmutableList.of(entity2)).when(repository).findAll(n2); 56 | doReturn(ImmutableList.of(entity3, entity4, entity5)).when(repository).findAll(root); 57 | 58 | Map config = configSource.loadConfig(); 59 | 60 | assertEquals(3, config.size()); 61 | assertEquals("v1", config.get("p1").valueAsString().get()); 62 | assertEquals("v2", config.get("p2").valueAsString().get()); 63 | assertEquals("v42", config.get("p42").valueAsString().get()); 64 | } 65 | 66 | @Test 67 | void testKeyValueLoadConfigFindAllThrowsException() throws Exception { 68 | KeyValueConfigName n1 = new KeyValueConfigName(g1, collectionName); 69 | KeyValueConfigName n2 = new KeyValueConfigName(g2, collectionName); 70 | KeyValueConfigName root = new KeyValueConfigName(null, collectionName); 71 | KeyValueConfigEntity entity1 = new KeyValueConfigEntity("p1", "v1", n1); 72 | KeyValueConfigEntity entity2 = new KeyValueConfigEntity("p2", "v2", n2); 73 | 74 | doReturn(ImmutableList.of(entity1)).when(repository).findAll(n1); 75 | doReturn(ImmutableList.of(entity2)).when(repository).findAll(n2); 76 | doThrow(new RuntimeException("some exception")).when(repository).findAll(root); 77 | 78 | Map config = configSource.loadConfig(); 79 | 80 | assertEquals(2, config.size()); 81 | assertEquals("v1", config.get("p1").valueAsString().get()); 82 | assertEquals("v2", config.get("p2").valueAsString().get()); 83 | } 84 | 85 | @Test 86 | void testKeyValueLoadConfigFindAllGettingLong() throws Exception { 87 | KeyValueConfigName n1 = new KeyValueConfigName(g1, collectionName); 88 | KeyValueConfigName n2 = new KeyValueConfigName(g2, collectionName); 89 | KeyValueConfigName root = new KeyValueConfigName(null, collectionName); 90 | KeyValueConfigEntity entity1 = new KeyValueConfigEntity("p1", "v1", n1); 91 | KeyValueConfigEntity entity2 = new KeyValueConfigEntity("p2", "v2", n2); 92 | 93 | doAnswer( 94 | (Answer>) 95 | invocation -> { 96 | Thread.sleep(100); 97 | return ImmutableList.of(entity1); 98 | }) 99 | .when(repository) 100 | .findAll(n1); 101 | 102 | doAnswer( 103 | (Answer>) 104 | invocation -> { 105 | Thread.sleep(100); 106 | return ImmutableList.of(entity2); 107 | }) 108 | .when(repository) 109 | .findAll(n2); 110 | 111 | doThrow(new RuntimeException("some exception")).when(repository).findAll(root); 112 | 113 | Map config = configSource.loadConfig(); 114 | 115 | assertEquals(2, config.size()); 116 | assertEquals("v1", config.get("p1").valueAsString().get()); 117 | assertEquals("v2", config.get("p2").valueAsString().get()); 118 | } 119 | 120 | @Test 121 | void testKeyValueLoadConfigFindAllRepositoryTimeout() throws Exception { 122 | KeyValueConfigName n1 = new KeyValueConfigName(g1, collectionName); 123 | KeyValueConfigName n2 = new KeyValueConfigName(g2, collectionName); 124 | KeyValueConfigName root = new KeyValueConfigName(null, collectionName); 125 | KeyValueConfigEntity entity = new KeyValueConfigEntity("p42", "v42", root); 126 | 127 | doAnswer( 128 | (Answer>) 129 | invocation -> { 130 | Thread.sleep(Long.MAX_VALUE); 131 | throw new RuntimeException("never return"); 132 | }) 133 | .when(repository) 134 | .findAll(n1); 135 | 136 | doAnswer( 137 | (Answer>) 138 | invocation -> { 139 | Thread.sleep(Long.MAX_VALUE); 140 | throw new RuntimeException("never return"); 141 | }) 142 | .when(repository) 143 | .findAll(n2); 144 | 145 | doReturn(ImmutableList.of(entity)).when(repository).findAll(root); 146 | 147 | assertThrows(ConfigSourceNotAvailableException.class, configSource::loadConfig); 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /config/src/test/resources/log4j2-test.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | %level{length=1} %date{MMdd-HHmm:ss,SSS} %logger{1.} %message [%thread]%n 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | 6 | io.scalecube 7 | scalecube-parent 8 | 0.3.1 9 | 10 | 11 | scalecube-config-parent 12 | 0.5.2-SNAPSHOT 13 | pom 14 | 15 | 16 | ScaleCube Config is a configuration management library for JVM based distributed applications. 17 | 18 | 2017 19 | https://github.com/scalecube/scalecube-config 20 | 21 | 22 | 23 | github 24 | GitHub Packages 25 | https://maven.pkg.github.com/scalecube/packages 26 | 27 | false 28 | 29 | 30 | 31 | 32 | 33 | https://github.com/scalecube/scalecube-config 34 | scm:git:https://github.com/scalecube/scalecube-config.git 35 | scm:git:https://github.com/scalecube/scalecube-config.git 36 | 37 | HEAD 38 | 39 | 40 | 41 | 5.1.0 42 | 1.7.36 43 | 44 | 2.27.0 45 | 5.1.1 46 | 1.3 47 | 1.20.1 48 | 33.3.0-jre 49 | 2.20.0 50 | 51 | https://maven.pkg.github.com/scalecube/scalecube-config 52 | 53 | checkstyle-suppressions.xml 54 | 55 | 56 | 57 | config 58 | config-examples 59 | config-vault 60 | 61 | 62 | 63 | 64 | 65 | org.slf4j 66 | slf4j-api 67 | ${slf4j.version} 68 | 69 | 70 | org.apache.logging.log4j 71 | log4j-slf4j-impl 72 | ${log4j.version} 73 | 74 | 75 | org.apache.logging.log4j 76 | log4j-core 77 | ${log4j.version} 78 | 79 | 80 | org.testcontainers 81 | vault 82 | ${testcontainers.version} 83 | 84 | 85 | com.bettercloud 86 | vault-java-driver 87 | ${vault-java-driver.version} 88 | 89 | 90 | com.google.guava 91 | guava 92 | ${guava.version} 93 | 94 | 95 | org.junit.jupiter 96 | junit-jupiter-api 97 | ${junit-jupiter.version} 98 | 99 | 100 | org.junit.jupiter 101 | junit-jupiter-engine 102 | ${junit-jupiter.version} 103 | 104 | 105 | org.junit.jupiter 106 | junit-jupiter-params 107 | ${junit-jupiter.version} 108 | 109 | 110 | org.mockito 111 | mockito-junit-jupiter 112 | ${mockito-junit.version} 113 | 114 | 115 | org.hamcrest 116 | hamcrest-all 117 | ${hamcrest.version} 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | com.google.guava 126 | guava 127 | test 128 | 129 | 130 | org.junit.jupiter 131 | junit-jupiter-engine 132 | test 133 | 134 | 135 | org.junit.jupiter 136 | junit-jupiter-params 137 | test 138 | 139 | 140 | org.mockito 141 | mockito-junit-jupiter 142 | test 143 | 144 | 145 | org.hamcrest 146 | hamcrest-all 147 | test 148 | 149 | 150 | org.apache.logging.log4j 151 | log4j-slf4j-impl 152 | test 153 | 154 | 155 | org.apache.logging.log4j 156 | log4j-core 157 | test 158 | 159 | 160 | org.testcontainers 161 | vault 162 | test 163 | 164 | 165 | 166 | 167 | --------------------------------------------------------------------------------