diff --git a/README.md b/README.md index 1576e6df..c056ba09 100644 --- a/README.md +++ b/README.md @@ -393,6 +393,12 @@ FYI: - Custom loaders always have the highest priority: last added -> first used. - Custom loader implementation must be stateless and must have a default(no-argument) `public` constructor. +### SPI +[Service-provider loading facility](https://docs.oracle.com/javase/8/docs/api/java/util/ServiceLoader.html) (introduced in JDK 1.6) can be used to *automatically* add custom loader implementation to the `ConfigFactory`. Add file *META-INF\services\net.cactusthorn.config.core.loader.Loader* in the class path. +e.g. +- [core module](https://github.com/Gmugra/net.cactusthorn.config/blob/main/core/src/main/resources/META-INF/services/net.cactusthorn.config.core.loader.Loader) +- [tests module](https://github.com/Gmugra/net.cactusthorn.config/blob/main/tests/src/main/resources/META-INF/services/net.cactusthorn.config.core.loader.Loader) + ### System properties and/or environment variables in sources URIs Syntax: {*name*} e.g. diff --git a/core/src/main/java/net/cactusthorn/config/core/ConfigFactory.java b/core/src/main/java/net/cactusthorn/config/core/ConfigFactory.java index 9d0431cc..d0c2904a 100644 --- a/core/src/main/java/net/cactusthorn/config/core/ConfigFactory.java +++ b/core/src/main/java/net/cactusthorn/config/core/ConfigFactory.java @@ -29,8 +29,10 @@ import java.net.URI; import java.util.ArrayDeque; import java.util.Collections; +import java.util.Iterator; import java.util.LinkedHashSet; import java.util.Map; +import java.util.ServiceLoader; import java.util.concurrent.ConcurrentHashMap; import java.util.function.Function; import java.util.stream.Collectors; @@ -40,13 +42,6 @@ import net.cactusthorn.config.core.loader.LoadStrategy; import net.cactusthorn.config.core.loader.Loader; import net.cactusthorn.config.core.loader.Loaders; -import net.cactusthorn.config.core.loader.standard.ClasspathJarManifestLoader; -import net.cactusthorn.config.core.loader.standard.ClasspathPropertiesLoader; -import net.cactusthorn.config.core.loader.standard.ClasspathXMLLoader; -import net.cactusthorn.config.core.loader.standard.SystemEnvLoader; -import net.cactusthorn.config.core.loader.standard.SystemPropertiesLoader; -import net.cactusthorn.config.core.loader.standard.UrlPropertiesLoader; -import net.cactusthorn.config.core.loader.standard.UrlXMLLoader; import net.cactusthorn.config.core.util.ConfigBuilder; public final class ConfigFactory { @@ -75,13 +70,10 @@ public static final class Builder { private LoadStrategy loadStrategy = LoadStrategy.MERGE; private Builder() { - loaders.add(new ClasspathPropertiesLoader()); - loaders.add(new SystemPropertiesLoader()); - loaders.add(new SystemEnvLoader()); - loaders.add(new UrlPropertiesLoader()); - loaders.add(new ClasspathXMLLoader()); - loaders.add(new UrlXMLLoader()); - loaders.add(new ClasspathJarManifestLoader()); + ServiceLoader serviceLoader = ServiceLoader.load(Loader.class); + for (Iterator it = serviceLoader.iterator(); it.hasNext();) { + loaders.add(it.next()); + } } public Builder addLoader(Loader loader) { diff --git a/core/src/main/resources/META-INF/services/net.cactusthorn.config.core.loader.Loader b/core/src/main/resources/META-INF/services/net.cactusthorn.config.core.loader.Loader new file mode 100644 index 00000000..81736b4f --- /dev/null +++ b/core/src/main/resources/META-INF/services/net.cactusthorn.config.core.loader.Loader @@ -0,0 +1,7 @@ +net.cactusthorn.config.core.loader.standard.ClasspathJarManifestLoader +net.cactusthorn.config.core.loader.standard.ClasspathPropertiesLoader +net.cactusthorn.config.core.loader.standard.ClasspathXMLLoader +net.cactusthorn.config.core.loader.standard.SystemEnvLoader +net.cactusthorn.config.core.loader.standard.SystemPropertiesLoader +net.cactusthorn.config.core.loader.standard.UrlPropertiesLoader +net.cactusthorn.config.core.loader.standard.UrlXMLLoader \ No newline at end of file diff --git a/tests/src/main/java/net/cactusthorn/config/tests/loader/SinglePropertyLoader.java b/tests/src/main/java/net/cactusthorn/config/tests/loader/SinglePropertyLoader.java new file mode 100644 index 00000000..bd734791 --- /dev/null +++ b/tests/src/main/java/net/cactusthorn/config/tests/loader/SinglePropertyLoader.java @@ -0,0 +1,39 @@ +/* +* Copyright (C) 2021, Alexei Khatskevich +* +* Licensed under the BSD 3-Clause license. +* You may obtain a copy of the License at +* +* https://github.com/Gmugra/net.cactusthorn.config/blob/main/LICENSE +* +* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ +package net.cactusthorn.config.tests.loader; + +import java.net.URI; +import java.util.HashMap; +import java.util.Map; + +import net.cactusthorn.config.core.loader.Loader; + +public class SinglePropertyLoader implements Loader { + + @Override public boolean accept(URI uri) { + return uri.toString().equals("single:property"); + } + + @Override public Map load(URI uri, ClassLoader classLoader) { + Map result = new HashMap<>(); + result.put("key", "value"); + return result; + } +} diff --git a/tests/src/main/resources/META-INF/services/net.cactusthorn.config.core.loader.Loader b/tests/src/main/resources/META-INF/services/net.cactusthorn.config.core.loader.Loader new file mode 100644 index 00000000..8fbf3065 --- /dev/null +++ b/tests/src/main/resources/META-INF/services/net.cactusthorn.config.core.loader.Loader @@ -0,0 +1 @@ +net.cactusthorn.config.tests.loader.SinglePropertyLoader \ No newline at end of file diff --git a/tests/src/test/java/net/cactusthorn/config/tests/loader/SinglePropertyLoaderTest.java b/tests/src/test/java/net/cactusthorn/config/tests/loader/SinglePropertyLoaderTest.java new file mode 100644 index 00000000..5d3cd822 --- /dev/null +++ b/tests/src/test/java/net/cactusthorn/config/tests/loader/SinglePropertyLoaderTest.java @@ -0,0 +1,56 @@ +/* +* Copyright (C) 2021, Alexei Khatskevich +* +* Licensed under the BSD 3-Clause license. +* You may obtain a copy of the License at +* +* https://github.com/Gmugra/net.cactusthorn.config/blob/main/LICENSE +* +* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ +package net.cactusthorn.config.tests.loader; + +import static org.junit.jupiter.api.Assertions.*; + +import java.net.URI; +import java.util.Map; + +import org.junit.jupiter.api.Test; + +import net.cactusthorn.config.core.ConfigFactory; +import net.cactusthorn.config.core.loader.ConfigHolder; +import net.cactusthorn.config.core.loader.Loader; + +public class SinglePropertyLoaderTest { + + private static final Loader LOADER = new SinglePropertyLoader(); + private static final ClassLoader CL = SinglePropertyLoaderTest.class.getClassLoader(); + private static final URI SPURI = URI.create("single:property"); + + @Test public void accept() { + assertTrue(LOADER.accept(SPURI)); + } + + @Test public void notAccept() { + assertFalse(LOADER.accept(URI.create("classpath://a.properties"))); + } + + @Test public void load() { + Map values = LOADER.load(SPURI, CL); + assertEquals("value", values.get("key")); + } + + @Test public void loadFactory() { + ConfigHolder holder = ConfigFactory.builder().addSource("single:property").build().configHolder(); + assertEquals("value", holder.getString("key")); + } +}