├── .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]
--------------------------------------------------------------------------------