├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── hocon-property-example ├── pom.xml └── src │ ├── main │ ├── java │ │ └── com │ │ │ └── github │ │ │ └── zeldigas │ │ │ └── example │ │ │ └── hocon │ │ │ ├── AppConfig.java │ │ │ ├── Application.java │ │ │ ├── StringToLocaleConverter.java │ │ │ └── SubConfiguration.java │ └── resources │ │ ├── META-INF │ │ └── spring.factories │ │ └── application.conf │ └── test │ └── java │ └── com │ └── github │ └── zeldigas │ └── example │ └── hocon │ └── AppTest.java ├── pom.xml └── spring-hocon-property-source ├── pom.xml └── src ├── main └── java │ └── com │ └── github │ └── zeldigas │ └── spring │ └── env │ ├── HoconPropertySource.java │ ├── HoconPropertySourceFactory.java │ └── HoconPropertySourceLoader.java └── test ├── java └── com │ └── github │ └── zeldigas │ └── spring │ └── env │ ├── FullApplicationTest.java │ └── HoconPropertySourceLoaderTest.java └── resources ├── application.conf ├── application.yaml └── hocon.conf /.gitignore: -------------------------------------------------------------------------------- 1 | ### JetBrains template 2 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio 3 | 4 | *.iml 5 | 6 | ## Directory-based project format: 7 | .idea/ 8 | # if you remove the above rule, at least ignore the following: 9 | 10 | # User-specific stuff: 11 | # .idea/workspace.xml 12 | # .idea/tasks.xml 13 | # .idea/dictionaries 14 | 15 | # Sensitive or high-churn files: 16 | # .idea/dataSources.ids 17 | # .idea/dataSources.xml 18 | # .idea/sqlDataSources.xml 19 | # .idea/dynamic.xml 20 | # .idea/uiDesigner.xml 21 | 22 | # Gradle: 23 | # .idea/gradle.xml 24 | # .idea/libraries 25 | 26 | # Mongo Explorer plugin: 27 | # .idea/mongoSettings.xml 28 | 29 | ## File-based project format: 30 | *.ipr 31 | *.iws 32 | 33 | ## Plugin-specific files: 34 | 35 | # IntelliJ 36 | /out/ 37 | 38 | # mpeltonen/sbt-idea plugin 39 | .idea_modules/ 40 | 41 | # JIRA plugin 42 | atlassian-ide-plugin.xml 43 | 44 | # Crashlytics plugin (for Android Studio and IntelliJ) 45 | com_crashlytics_export_strings.xml 46 | crashlytics.properties 47 | crashlytics-build.properties 48 | 49 | *.class 50 | 51 | # Mobile Tools for Java (J2ME) 52 | .mtj.tmp/ 53 | 54 | # Package Files # 55 | *.jar 56 | *.war 57 | *.ear 58 | 59 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 60 | hs_err_pid* 61 | 62 | ### STS ### 63 | .apt_generated 64 | .classpath 65 | .factorypath 66 | .project 67 | .settings 68 | .springBeans 69 | 70 | ### IntelliJ IDEA ### 71 | .idea 72 | *.iws 73 | *.iml 74 | *.ipr 75 | 76 | ### NetBeans ### 77 | nbproject/private/ 78 | build/ 79 | nbbuild/ 80 | dist/ 81 | nbdist/ 82 | .nb-gradle/ 83 | 84 | ### Maven ### 85 | target/ 86 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | All notable changes to this project will be documented in this file. 3 | 4 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 5 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 6 | 7 | ## [0.4.0] - 2020-05-25 8 | ### Added 9 | - `@HoconPropertySource` annotation. You can use it to load HOCON files to env, 10 | just like you load `*.properties` files with Spring's `@PropertySource` 11 | ### Changed 12 | - Project is built against SpringBoot 2.2. Dependency still uses `provided` scope 13 | so it should not be a problem to use any version you prefer though, as Spring's API is stable 14 | - Hocon dependency bumped to 1.4.0 15 | ### Fixed 16 | - Empty lists now added to processed properties, so you can override non-empty list with empty. 17 | 18 | Kudos to [FingolfinTEK](https://github.com/FingolfinTEK) for empty list fix and `@HoconPropertySource` feature! 19 | 20 | ## [0.3.0] - 2020-05-25 21 | ### Added 22 | - Support for org.springframework.boot.origin.TextResourceOrigin 23 | ### Fixed 24 | - Closing input stream after reading HOCON file 25 | 26 | ## [0.2.2] - 2020-04-26 27 | ### Added 28 | - Changelog file added to repo 29 | ### Updated 30 | - License file (years interval fixed) 31 | ### Fixed 32 | - Specifying file encoding explicitly to adhere to HOCON spec (#2) 33 | 34 | ## [0.2.1] - 2019-03-18 35 | ### Changed 36 | - Spring Boot 2 as a target version to support. 37 | - Java 8 used as a compilation target 38 | 39 | ## [0.1] - 2016-06-23 40 | ### Added 41 | - Initial support for hocon config files as spring property source 42 | 43 | [0.4.0]: https://github.com/zeldigas/spring-hocon-property-source/compare/0.3.0...0.4.0 44 | [0.3.0]: https://github.com/zeldigas/spring-hocon-property-source/compare/0.2.2...0.3.0 45 | [0.2.2]: https://github.com/zeldigas/spring-hocon-property-source/compare/0.2.1...0.2.2 46 | [0.2.1]: https://github.com/zeldigas/spring-hocon-property-source/compare/0.1...0.2.1 47 | [0.1]: https://github.com/zeldigas/spring-hocon-property-source/releases/tag/0.1 -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016-2020 Dmitry Pavlov and contributors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Spring Hocon PropertySourceLoader 2 | 3 | Project aims to add support for HOCON format in spring boot configuration files. 4 | YAML is great, but some people prefer HOCON more so it's good to have a choice. 5 | 6 | There are 2 modules: 7 | 8 | 1. spring-hocon-property-source - contains property source loader itself 9 | 2. hocon-property-example - contains simple spring-boot-starter-web project with demonstration 10 | of hocon configuration 11 | 12 | # How to use it in my spring boot code? 13 | 14 | **Version 0.4 is aimed for SpringBoot 2.2. For 1.x check version 0.1** 15 | 16 | 1. Add a dependency to your maven project 17 | 18 | 19 | com.github.zeldigas 20 | spring-hocon-property-source 21 | 0.4.0 22 | 23 | 24 | 2. In `META-INF/factories` file add the following line 25 | 26 | org.springframework.boot.env.PropertySourceLoader=com.github.zeldigas.spring.env.HoconPropertySourceLoader 27 | 28 | 3. Place your HOCON configuration to *.conf files 29 | 4. Enjoy! 30 | 31 | Alternatively you can use `@HoconPropertySource("my-hocon.conf")` annotation to load 32 | specific file on classpath just like you can do with `@PropertySource` annotation for 33 | `*.properties` files. -------------------------------------------------------------------------------- /hocon-property-example/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | com.github.zeldigas 8 | hocon-property-example 9 | 0.1-SNAPSHOT 10 | 11 | 12 | 2.2.8.RELEASE 13 | 14 | 15 | 16 | 17 | 18 | maven-compiler-plugin 19 | 20 | 1.8 21 | 1.8 22 | UTF-8 23 | 24 | 25 | 26 | org.springframework.boot 27 | spring-boot-maven-plugin 28 | 29 | 30 | 31 | 32 | 33 | 34 | com.github.zeldigas 35 | spring-hocon-property-source 36 | 0.4.0 37 | 38 | 39 | org.springframework.boot 40 | spring-boot-starter-web 41 | ${spring-boot.version} 42 | 43 | 44 | 45 | 46 | org.springframework.boot 47 | spring-boot-starter-test 48 | ${spring-boot.version} 49 | test 50 | 51 | 52 | org.springframework.boot 53 | spring-boot-starter-webflux 54 | ${spring-boot.version} 55 | test 56 | 57 | 58 | uk.co.datumedge 59 | hamcrest-json 60 | 0.2 61 | test 62 | 63 | 64 | -------------------------------------------------------------------------------- /hocon-property-example/src/main/java/com/github/zeldigas/example/hocon/AppConfig.java: -------------------------------------------------------------------------------- 1 | package com.github.zeldigas.example.hocon; 2 | 3 | import org.springframework.boot.context.properties.ConfigurationProperties; 4 | import org.springframework.stereotype.Component; 5 | 6 | import java.net.URI; 7 | import java.util.Locale; 8 | 9 | @Component 10 | @ConfigurationProperties("my-app") 11 | public class AppConfig { 12 | 13 | private String foo; 14 | private int bar; 15 | private URI aUri; 16 | private Locale targetLocale; 17 | private SubConfiguration configuration; 18 | 19 | public String getFoo() { 20 | return foo; 21 | } 22 | 23 | public void setFoo(String foo) { 24 | this.foo = foo; 25 | } 26 | 27 | public int getBar() { 28 | return bar; 29 | } 30 | 31 | public void setBar(int bar) { 32 | this.bar = bar; 33 | } 34 | 35 | public URI getaUri() { 36 | return aUri; 37 | } 38 | 39 | public void setaUri(URI aUri) { 40 | this.aUri = aUri; 41 | } 42 | 43 | public Locale getTargetLocale() { 44 | return targetLocale; 45 | } 46 | 47 | public void setTargetLocale(Locale targetLocale) { 48 | this.targetLocale = targetLocale; 49 | } 50 | 51 | public SubConfiguration getConfiguration() { 52 | return configuration; 53 | } 54 | 55 | public void setConfiguration(SubConfiguration configuration) { 56 | this.configuration = configuration; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /hocon-property-example/src/main/java/com/github/zeldigas/example/hocon/Application.java: -------------------------------------------------------------------------------- 1 | package com.github.zeldigas.example.hocon; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | import org.springframework.boot.SpringApplication; 5 | import org.springframework.boot.autoconfigure.SpringBootApplication; 6 | import org.springframework.web.bind.annotation.RequestMapping; 7 | import org.springframework.web.bind.annotation.RestController; 8 | 9 | @RestController 10 | @SpringBootApplication 11 | public class Application { 12 | 13 | 14 | @Autowired 15 | private AppConfig appConfig; 16 | 17 | @RequestMapping("/config") 18 | AppConfig config() { 19 | return appConfig; 20 | } 21 | 22 | 23 | public static void main(String[] args) { 24 | SpringApplication.run(Application.class, args); 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /hocon-property-example/src/main/java/com/github/zeldigas/example/hocon/StringToLocaleConverter.java: -------------------------------------------------------------------------------- 1 | package com.github.zeldigas.example.hocon; 2 | 3 | import org.springframework.boot.context.properties.ConfigurationPropertiesBinding; 4 | import org.springframework.core.convert.converter.Converter; 5 | import org.springframework.stereotype.Component; 6 | 7 | import java.util.Locale; 8 | 9 | @Component 10 | @ConfigurationPropertiesBinding 11 | public class StringToLocaleConverter implements Converter { 12 | 13 | @Override 14 | public Locale convert(String s) { 15 | return Locale.forLanguageTag(s); 16 | 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /hocon-property-example/src/main/java/com/github/zeldigas/example/hocon/SubConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.github.zeldigas.example.hocon; 2 | 3 | import java.util.List; 4 | import java.util.Map; 5 | 6 | public class SubConfiguration { 7 | 8 | private List endpoints; 9 | 10 | private Map connectionSettings; 11 | 12 | public List getEndpoints() { 13 | return endpoints; 14 | } 15 | 16 | public void setEndpoints(List endpoints) { 17 | this.endpoints = endpoints; 18 | } 19 | 20 | public Map getConnectionSettings() { 21 | return connectionSettings; 22 | } 23 | 24 | public void setConnectionSettings(Map connectionSettings) { 25 | this.connectionSettings = connectionSettings; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /hocon-property-example/src/main/resources/META-INF/spring.factories: -------------------------------------------------------------------------------- 1 | org.springframework.boot.env.PropertySourceLoader=com.github.zeldigas.spring.env.HoconPropertySourceLoader -------------------------------------------------------------------------------- /hocon-property-example/src/main/resources/application.conf: -------------------------------------------------------------------------------- 1 | spring { 2 | datasource { 3 | url: "jdbc:hsqldb:file:testdb" 4 | username: sa 5 | } 6 | thymeleaf.cache = false 7 | } 8 | 9 | server.port: "${port:8085}" 10 | 11 | myApp { 12 | foo: Hello 13 | bar: 42 14 | aUri: "https://example.org/hello" 15 | targetLocale: en-US 16 | configuration { 17 | endpoints: [one, two, three] 18 | connectionSettings.one: "hello" 19 | two.two_sub: "world!" 20 | } 21 | } -------------------------------------------------------------------------------- /hocon-property-example/src/test/java/com/github/zeldigas/example/hocon/AppTest.java: -------------------------------------------------------------------------------- 1 | package com.github.zeldigas.example.hocon; 2 | 3 | import org.junit.After; 4 | import org.junit.Before; 5 | import org.junit.Test; 6 | import org.springframework.boot.SpringApplication; 7 | import org.springframework.context.ConfigurableApplicationContext; 8 | import org.springframework.web.reactive.function.client.WebClient; 9 | 10 | import static org.junit.Assert.assertThat; 11 | import static uk.co.datumedge.hamcrest.json.SameJSONAs.sameJSONAs; 12 | 13 | 14 | public class AppTest { 15 | 16 | 17 | private ConfigurableApplicationContext context; 18 | 19 | @Before 20 | public void setUp() throws Exception { 21 | context = SpringApplication.run(Application.class); 22 | } 23 | 24 | @After 25 | public void tearDown() throws Exception { 26 | context.close(); 27 | } 28 | 29 | @Test 30 | public void applicationStartsAndReturnHoconConfig() { 31 | WebClient client = WebClient.create(); 32 | 33 | String result = client.get().uri("http://localhost:8085/config").retrieve() 34 | .bodyToMono(String.class) 35 | .block(); 36 | 37 | 38 | assertThat(result, sameJSONAs("{\"foo\":\"Hello\",\"bar\":42,\"aUri\":\"https://example.org/hello\",\"targetLocale\":\"en-us\",\"configuration\":{\"endpoints\":[\"one\",\"two\",\"three\"],\"connectionSettings\":{\"one\":\"hello\"}}}")); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | com.github.zeldigas 8 | spring-hocon-property-source-parent 9 | 0.1-SNAPSHOT 10 | pom 11 | 12 | 13 | spring-hocon-property-source 14 | hocon-property-example 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /spring-hocon-property-source/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | com.github.zeldigas 8 | spring-hocon-property-source 9 | 0.4.0 10 | 11 | Spring HOCON property source 12 | Property source loader for Spring that add support for HOCON format files as property sources 13 | 14 | https://github.com/zeldigas/spring-hocon-property-source 15 | 16 | 17 | scm:git:git@github.com:zeldigas/spring-hocon-property-source.git 18 | scm:git:git@github.com:zeldigas/spring-hocon-property-source.git 19 | git@github.com:zeldigas/spring-hocon-property-source.git 20 | 21 | 22 | 23 | 24 | MIT License 25 | http://www.opensource.org/licenses/mit-license.php 26 | 27 | 28 | 29 | 30 | 31 | Dmitry Pavlov 32 | zeldigas@gmail.com 33 | 34 | 35 | 36 | 37 | 38 | ossrh 39 | https://oss.sonatype.org/content/repositories/snapshots 40 | 41 | 42 | ossrh 43 | https://oss.sonatype.org/service/local/staging/deploy/maven2/ 44 | 45 | 46 | 47 | 48 | UTF-8 49 | 2.2.8.RELEASE 50 | 1.4.0 51 | 52 | 53 | 54 | 3.2.1 55 | 56 | 57 | 58 | 59 | 60 | maven-compiler-plugin 61 | 62 | 1.8 63 | 1.8 64 | UTF-8 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | release 73 | 74 | 75 | 76 | org.apache.maven.plugins 77 | maven-source-plugin 78 | 3.2.0 79 | 80 | 81 | attach-sources 82 | 83 | jar-no-fork 84 | 85 | 86 | 87 | 88 | 89 | org.apache.maven.plugins 90 | maven-javadoc-plugin 91 | 3.2.0 92 | 93 | 94 | attach-javadocs 95 | 96 | jar 97 | 98 | 99 | 100 | 101 | true 102 | 103 | 104 | 105 | io.github.zlika 106 | reproducible-build-maven-plugin 107 | 0.11 108 | 109 | 110 | 111 | strip-jar 112 | 113 | 114 | 115 | 116 | 117 | org.apache.maven.plugins 118 | maven-gpg-plugin 119 | 1.6 120 | 121 | 122 | sign-artifacts 123 | verify 124 | 125 | sign 126 | 127 | 128 | 129 | 130 | 131 | --pinentry-mode 132 | loopback 133 | 134 | ${gpg.keyname} 135 | 136 | 137 | 138 | org.sonatype.plugins 139 | nexus-staging-maven-plugin 140 | 1.6.8 141 | true 142 | 143 | ossrh 144 | https://oss.sonatype.org/ 145 | true 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | com.typesafe 156 | config 157 | ${hocon.version} 158 | 159 | 160 | org.springframework.boot 161 | spring-boot 162 | ${spring-boot.version} 163 | provided 164 | 165 | 166 | 167 | junit 168 | junit 169 | 4.13.1 170 | test 171 | 172 | 173 | org.hamcrest 174 | hamcrest-all 175 | 1.3 176 | test 177 | 178 | 179 | org.yaml 180 | snakeyaml 181 | 1.26 182 | test 183 | 184 | 185 | org.springframework.boot 186 | spring-boot-starter-test 187 | ${spring-boot.version} 188 | test 189 | 190 | 191 | -------------------------------------------------------------------------------- /spring-hocon-property-source/src/main/java/com/github/zeldigas/spring/env/HoconPropertySource.java: -------------------------------------------------------------------------------- 1 | package com.github.zeldigas.spring.env; 2 | 3 | import org.springframework.context.annotation.PropertySource; 4 | import org.springframework.core.annotation.AliasFor; 5 | 6 | import java.lang.annotation.Documented; 7 | import java.lang.annotation.ElementType; 8 | import java.lang.annotation.Retention; 9 | import java.lang.annotation.RetentionPolicy; 10 | import java.lang.annotation.Target; 11 | 12 | /** 13 | * A utility meta annotation to allow for easier use of HOCON files as property sources; 14 | * the annotation allows consumers to use @{@link HoconPropertySource} with .conf files in the same way 15 | * they would use {@link PropertySource} with .properties files by extending the {@link PropertySource} 16 | * annotation with the default factory class of {@link HoconPropertySourceFactory}. 17 | */ 18 | @Target(ElementType.TYPE) 19 | @Retention(RetentionPolicy.RUNTIME) 20 | @Documented 21 | @PropertySource(value = "", factory = HoconPropertySourceFactory.class) 22 | public @interface HoconPropertySource { 23 | 24 | /** 25 | * @see PropertySource#name() 26 | */ 27 | @AliasFor(annotation = PropertySource.class) 28 | String name() default ""; 29 | 30 | /** 31 | * @see PropertySource#value() 32 | */ 33 | @AliasFor(annotation = PropertySource.class) 34 | String[] value(); 35 | 36 | /** 37 | * @see PropertySource#ignoreResourceNotFound() 38 | */ 39 | @AliasFor(annotation = PropertySource.class) 40 | boolean ignoreResourceNotFound() default false; 41 | 42 | /** 43 | * @see PropertySource#encoding() 44 | */ 45 | @AliasFor(annotation = PropertySource.class) 46 | String encoding() default ""; 47 | 48 | } -------------------------------------------------------------------------------- /spring-hocon-property-source/src/main/java/com/github/zeldigas/spring/env/HoconPropertySourceFactory.java: -------------------------------------------------------------------------------- 1 | package com.github.zeldigas.spring.env; 2 | 3 | import com.typesafe.config.Config; 4 | import com.typesafe.config.ConfigFactory; 5 | import com.typesafe.config.ConfigList; 6 | import com.typesafe.config.ConfigObject; 7 | import com.typesafe.config.ConfigParseOptions; 8 | import com.typesafe.config.ConfigSyntax; 9 | import com.typesafe.config.ConfigValue; 10 | import com.typesafe.config.ConfigValueFactory; 11 | import org.springframework.boot.env.OriginTrackedMapPropertySource; 12 | import org.springframework.boot.origin.Origin; 13 | import org.springframework.boot.origin.OriginTrackedValue; 14 | import org.springframework.boot.origin.TextResourceOrigin; 15 | import org.springframework.core.env.PropertySource; 16 | import org.springframework.core.io.Resource; 17 | import org.springframework.core.io.support.EncodedResource; 18 | import org.springframework.core.io.support.PropertySourceFactory; 19 | 20 | import java.io.IOException; 21 | import java.io.InputStream; 22 | import java.io.InputStreamReader; 23 | import java.nio.charset.StandardCharsets; 24 | import java.util.LinkedHashMap; 25 | import java.util.Map; 26 | import java.util.Optional; 27 | 28 | /** 29 | * A {@link PropertySourceFactory} factory that loads a HOCON file into an instance of {@link PropertySource}. 30 | */ 31 | public class HoconPropertySourceFactory implements PropertySourceFactory { 32 | private static final ConfigParseOptions PARSE_OPTIONS = 33 | ConfigParseOptions.defaults().setSyntax(ConfigSyntax.CONF); 34 | 35 | @Override 36 | public PropertySource createPropertySource(String name, EncodedResource encoded) throws IOException { 37 | Resource resource = encoded.getResource(); 38 | String actualName = Optional.ofNullable(name).orElseGet(resource::getFilename); 39 | Map source = toFlatMap(resource, parseHoconFrom(resource)); 40 | return new OriginTrackedMapPropertySource(actualName, source); 41 | } 42 | 43 | private Config parseHoconFrom(Resource resource) throws IOException { 44 | try (InputStream inputStream = resource.getInputStream(); 45 | InputStreamReader reader = new InputStreamReader(inputStream, StandardCharsets.UTF_8)) { 46 | return ConfigFactory.parseReader(reader, PARSE_OPTIONS).resolve(); 47 | } 48 | } 49 | 50 | private Map toFlatMap(Resource resource, Config config) { 51 | Map properties = new LinkedHashMap<>(); 52 | toFlatMap(properties, "", resource, config); 53 | return properties; 54 | } 55 | 56 | private void toFlatMap(Map properties, String parentKey, Resource resource, Config config) { 57 | final String prefix = "".equals(parentKey) ? "" : parentKey + "."; 58 | 59 | for (Map.Entry entry : config.entrySet()) { 60 | String propertyKey = prefix + entry.getKey(); 61 | addConfigValuePropertyTo(properties, propertyKey, resource, entry.getValue()); 62 | } 63 | } 64 | 65 | private void addConfigValuePropertyTo(Map properties, String key, Resource resource, ConfigValue value) { 66 | if (value instanceof ConfigList) { 67 | processListValue(properties, key, resource, (ConfigList) value); 68 | } else if (value instanceof ConfigObject) { 69 | processObjectValue(properties, key, resource, (ConfigObject) value); 70 | } else { 71 | processScalarValue(properties, key, resource, value); 72 | } 73 | } 74 | 75 | private void processListValue(Map properties, String key, Resource resource, ConfigList value) { 76 | if (value.isEmpty()) { 77 | addConfigValuePropertyTo(properties, key, resource, ConfigValueFactory.fromAnyRef("")); 78 | return; 79 | } 80 | 81 | for (int i = 0; i < value.size(); i++) { 82 | // Used to properly populate lists in @ConfigurationProperties beans 83 | String propertyName = String.format("%s[%d]", key, i); 84 | ConfigValue propertyValue = value.get(i); 85 | addConfigValuePropertyTo(properties, propertyName, resource, propertyValue); 86 | } 87 | } 88 | 89 | private void processObjectValue(Map properties, String key, Resource resource, ConfigObject value) { 90 | toFlatMap(properties, key, resource, value.toConfig()); 91 | } 92 | 93 | private void processScalarValue(Map properties, String key, Resource resource, ConfigValue value) { 94 | properties.put(key, value.unwrapped()); 95 | Origin origin = new TextResourceOrigin(resource, new TextResourceOrigin.Location(value.origin().lineNumber() - 1, 0)); 96 | properties.put(key, OriginTrackedValue.of(value.unwrapped(), origin)); 97 | } 98 | 99 | } 100 | -------------------------------------------------------------------------------- /spring-hocon-property-source/src/main/java/com/github/zeldigas/spring/env/HoconPropertySourceLoader.java: -------------------------------------------------------------------------------- 1 | package com.github.zeldigas.spring.env; 2 | 3 | import org.springframework.boot.env.PropertySourceLoader; 4 | import org.springframework.core.env.PropertySource; 5 | import org.springframework.core.io.Resource; 6 | import org.springframework.core.io.support.EncodedResource; 7 | import org.springframework.core.io.support.PropertySourceFactory; 8 | 9 | import java.io.IOException; 10 | import java.util.Collections; 11 | import java.util.List; 12 | 13 | /** 14 | * Strategy to load '.conf' files in 15 | * HOCON 16 | * format into a {@link PropertySource}. 17 | */ 18 | public class HoconPropertySourceLoader implements PropertySourceLoader { 19 | 20 | private static final PropertySourceFactory HOCON_PROPERTY_SOURCE_LOADER = new HoconPropertySourceFactory(); 21 | 22 | @Override 23 | public String[] getFileExtensions() { 24 | return new String[]{"conf"}; 25 | } 26 | 27 | @Override 28 | public List> load(String name, Resource resource) throws IOException { 29 | EncodedResource encodedResource = new EncodedResource(resource); 30 | PropertySource propertySource = HOCON_PROPERTY_SOURCE_LOADER.createPropertySource(name, encodedResource); 31 | return Collections.singletonList(propertySource); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /spring-hocon-property-source/src/test/java/com/github/zeldigas/spring/env/FullApplicationTest.java: -------------------------------------------------------------------------------- 1 | package com.github.zeldigas.spring.env; 2 | 3 | import org.junit.Test; 4 | import org.junit.runner.RunWith; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.boot.autoconfigure.SpringBootApplication; 7 | import org.springframework.boot.test.context.SpringBootTest; 8 | import org.springframework.core.env.Environment; 9 | import org.springframework.test.context.junit4.SpringRunner; 10 | 11 | import static org.hamcrest.CoreMatchers.is; 12 | import static org.hamcrest.MatcherAssert.assertThat; 13 | 14 | @RunWith(SpringRunner.class) 15 | @SpringBootTest("myApp.configuration.endpoints=[]") 16 | public class FullApplicationTest { 17 | 18 | @Autowired 19 | private Environment environment; 20 | 21 | @Test 22 | public void hoconPropertySourceAnnotationWorks() { 23 | assertThat(environment.getProperty("my.hocon.property"), is("foo")); 24 | } 25 | 26 | @Test 27 | public void emptyLists() { 28 | assertThat(environment.getProperty("empty.list"), is("")); 29 | } 30 | 31 | @HoconPropertySource("classpath:hocon.conf") 32 | @SpringBootApplication 33 | static class TestSpringApplication { 34 | 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /spring-hocon-property-source/src/test/java/com/github/zeldigas/spring/env/HoconPropertySourceLoaderTest.java: -------------------------------------------------------------------------------- 1 | package com.github.zeldigas.spring.env; 2 | 3 | import org.junit.Test; 4 | import org.springframework.boot.env.OriginTrackedMapPropertySource; 5 | import org.springframework.boot.env.PropertySourceLoader; 6 | import org.springframework.boot.env.YamlPropertySourceLoader; 7 | import org.springframework.core.env.EnumerablePropertySource; 8 | import org.springframework.core.env.PropertySource; 9 | import org.springframework.core.io.ClassPathResource; 10 | 11 | import java.io.IOException; 12 | import java.util.Arrays; 13 | import java.util.List; 14 | 15 | import static org.hamcrest.Matchers.is; 16 | import static org.junit.Assert.assertThat; 17 | 18 | public class HoconPropertySourceLoaderTest { 19 | 20 | @Test 21 | public void propertyParseIsEqualToYaml() throws Exception { 22 | HoconPropertySourceLoader hoconLoader = new HoconPropertySourceLoader(); 23 | YamlPropertySourceLoader yamlLoader = new YamlPropertySourceLoader(); 24 | 25 | EnumerablePropertySource hoconParse = loadProperties(hoconLoader, "/application.conf"); 26 | EnumerablePropertySource yamlParse = loadProperties(yamlLoader, "/application.yaml"); 27 | 28 | verifyPropertyKeysAreSame(hoconParse, yamlParse); 29 | verifyPropertyValuesAreSame(hoconParse, yamlParse); 30 | } 31 | 32 | private void verifyPropertyKeysAreSame(EnumerablePropertySource hoconParse, EnumerablePropertySource yamlParse) { 33 | String[] hoconProperties = hoconParse.getPropertyNames(); 34 | Arrays.sort(hoconProperties); 35 | String[] yamlProperties = yamlParse.getPropertyNames(); 36 | Arrays.sort(yamlProperties); 37 | 38 | assertThat(hoconProperties, is(yamlProperties)); 39 | } 40 | 41 | private void verifyPropertyValuesAreSame(EnumerablePropertySource hoconParse, EnumerablePropertySource yamlParse) { 42 | for (String name : hoconParse.getPropertyNames()) { 43 | assertThat(hoconParse.getProperty(name), is(yamlParse.getProperty(name))); 44 | } 45 | } 46 | 47 | @SuppressWarnings("unchecked") 48 | private > T loadProperties(PropertySourceLoader propertySourceLoader, String path) throws IOException { 49 | List> source = propertySourceLoader.load("hocon", new ClassPathResource(path)); 50 | assertThat("One property source expected", source.size(), is(1)); 51 | return (T) source.get(0); 52 | } 53 | 54 | @Test 55 | public void propertyOriginIsCorrectlyDetermined() throws IOException { 56 | OriginTrackedMapPropertySource hoconParse = loadProperties(new HoconPropertySourceLoader(), "/application.conf"); 57 | 58 | verifyPropertyHasOriginOnLine(hoconParse, "spring.datasource.url", 3); 59 | verifyPropertyHasOriginOnLine(hoconParse, "server.port", 9); 60 | verifyPropertyHasOriginOnLine(hoconParse, "myApp.configuration.connectionSettings.two.two_sub", 20); 61 | } 62 | 63 | private void verifyPropertyHasOriginOnLine(OriginTrackedMapPropertySource hoconParse, String name, int lineNumber) { 64 | Object propertyOrigin = hoconParse.getOrigin(name); 65 | assertThat(propertyOrigin.toString(), is("class path resource [application.conf]:" + lineNumber + ":1")); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /spring-hocon-property-source/src/test/resources/application.conf: -------------------------------------------------------------------------------- 1 | spring { 2 | datasource { 3 | url: "jdbc:hsqldb:file:testdb" 4 | username: sa 5 | } 6 | thymeleaf.cache = false 7 | } 8 | 9 | server.port: "${port:8085}" 10 | 11 | myApp { 12 | oo: Hello 13 | bar: 42 14 | aUri: "https://example.org/hello" 15 | targetLocale: en-US 16 | configuration { 17 | endpoints = ["one", "two", "three"] 18 | connectionSettings { 19 | one: "hello" 20 | two.two_sub: "world!" 21 | } 22 | } 23 | } 24 | 25 | empty.list = [] -------------------------------------------------------------------------------- /spring-hocon-property-source/src/test/resources/application.yaml: -------------------------------------------------------------------------------- 1 | spring: 2 | datasource: 3 | url: "jdbc:hsqldb:file:testdb" 4 | username: sa 5 | thymeleaf: 6 | cache: false 7 | server: 8 | port: ${port:8085} 9 | 10 | myApp: 11 | oo: Hello 12 | bar: 42 13 | aUri: "https://example.org/hello" 14 | targetLocale: en-US 15 | configuration: 16 | endpoints: 17 | - "one" 18 | - "two" 19 | - "three" 20 | connectionSettings : 21 | one: "hello" 22 | two: 23 | two_sub: "world!" 24 | 25 | empty.list: [] -------------------------------------------------------------------------------- /spring-hocon-property-source/src/test/resources/hocon.conf: -------------------------------------------------------------------------------- 1 | my.hocon.property = foo 2 | empty.list = [one, two, three] --------------------------------------------------------------------------------