All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.helidon.config.ClasspathConfigSource Maven / Gradle / Ivy

There is a newer version: 4.1.1
Show newest version
/*
 * Copyright (c) 2017, 2023 Oracle and/or its affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package io.helidon.config;

import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.Collection;
import java.util.Enumeration;
import java.util.LinkedList;
import java.util.List;
import java.util.Optional;
import java.util.function.Function;

import io.helidon.common.LazyValue;
import io.helidon.common.media.type.MediaType;
import io.helidon.common.media.type.MediaTypes;
import io.helidon.config.spi.ConfigParser;
import io.helidon.config.spi.ConfigParser.Content;
import io.helidon.config.spi.ConfigSource;
import io.helidon.config.spi.ParsableSource;

/**
 * {@link io.helidon.config.spi.ConfigSource} implementation that loads configuration content from a resource on a classpath.
 * Classpath config source does not support changes (neither through polling nor through change notifications).
 */
public class ClasspathConfigSource extends AbstractConfigSource implements ConfigSource,
                                                                           ParsableSource {
    private final String resource;
    private final URL resourceUrl;
    private final LazyValue> mediaType;

    ClasspathConfigSource(Builder builder) {
        super(builder);

        this.resource = builder.resource;
        this.resourceUrl = builder.url;

        mediaType = LazyValue.create(() -> {
            if (resourceUrl == null) {
                return MediaTypes.detectType(resource);
            } else {
                return MediaTypes.detectType(resourceUrl);
            }
        });
    }

    /**
     * Initializes config source instance from configuration properties.
     * 

* Mandatory {@code properties}, see {@link io.helidon.config.ConfigSources#classpath(String)}: *

    *
  • {@code resource} - type {@code String}
  • *
* Optional {@code properties}: see {@link AbstractConfigSourceBuilder#config(io.helidon.config.Config)}. * * @param metaConfig meta-configuration used to initialize returned config source instance from. * @return new instance of config source described by {@code metaConfig} * @throws MissingValueException in case the configuration tree does not contain all expected sub-nodes * required by the mapper implementation to provide instance of Java type. * @throws ConfigMappingException in case the mapper fails to map the (existing) configuration tree represented by the * supplied configuration node to an instance of a given Java type. * @see io.helidon.config.ConfigSources#classpath(String) * @see AbstractConfigSourceBuilder#config(Config) */ public static ClasspathConfigSource create(Config metaConfig) throws ConfigMappingException, MissingValueException { return builder() .config(metaConfig) .build(); } /** * Create a config source for the first resource on the classpath. * * @param resource resource to find * @return a config source based on the classpath resource */ public static ClasspathConfigSource create(String resource) { return builder().resource(resource).build(); } /** * Create config source for each resource on the classpath. * * @param resource resource to find * @return a collection of sources for each resource present on the classpath, always at least one */ public static Collection createAll(String resource) { Enumeration resources = findAllResources(resource); if (resources.hasMoreElements()) { List sources = new LinkedList<>(); while (resources.hasMoreElements()) { URL url = resources.nextElement(); sources.add(builder().url(url).build()); } return sources; } else { // there is none - let the default source handle it, to manage optional vs. mandatory // with configuration and not an empty list return List.of(create(resource)); } } /** * Create config source for each resource on the classpath. * * @param metaConfig meta configuration of the config source * @return a collection of sources for each resource present on the classpath */ public static List createAll(Config metaConfig) { // this must fail if the resource is not defined String resource = metaConfig.get("resource").asString().get(); Enumeration resources = findAllResources(resource); if (resources.hasMoreElements()) { List sources = new LinkedList<>(); while (resources.hasMoreElements()) { URL url = resources.nextElement(); sources.add(builder() .config(metaConfig) // url must be configured after meta config, to override the default .url(url) .build()); } return sources; } else { // there is none - let the default source handle it, to manage optional vs. mandatory // with configuration and not an empty list return List.of(create(metaConfig)); } } /** * Create a new fluent API builder for classpath config source. * * @return a new builder instance */ public static Builder builder() { return new Builder(); } @Override protected String uid() { return (null == resourceUrl) ? resource : resourceUrl.toString(); } @Override public Optional load() throws ConfigException { if (resourceUrl == null) { return Optional.empty(); } InputStream inputStream; try { inputStream = resourceUrl.openStream(); } catch (IOException e) { throw new ConfigException("Failed to read configuration from classpath, resource: " + resource, e); } Content.Builder builder = Content.builder() .data(inputStream); mediaType.get().ifPresent(builder::mediaType); return Optional.of(builder.build()); } @Override public Function> relativeResolver() { return it -> { // this works the same on windows and Unix systems (classpath is always forward slashes) int lastSlash = resource.lastIndexOf('/'); String resourceToFind; if (lastSlash > -1) { resourceToFind = resource.substring(0, lastSlash + 1) + it; } else { resourceToFind = it; } URL resourceUrl = ClasspathConfigSource.class.getClassLoader().getResource(resourceToFind); if (resourceUrl == null) { return Optional.empty(); } try { return Optional.of(resourceUrl.openStream()); } catch (IOException e) { throw new ConfigException("Failed to read configuration from classpath, resource: " + resourceToFind, e); } }; } @Override public String toString() { return "classpath: " + resource; } @Override public Optional mediaType() { return super.mediaType(); } @Override public Optional parser() { return super.parser(); } private static Enumeration findAllResources(String resource) { String cleaned = resource.startsWith("/") ? resource.substring(1) : resource; try { return Thread.currentThread() .getContextClassLoader() .getResources(cleaned); } catch (IOException e) { throw new ConfigException("Could not access config resource " + resource, e); } } /** * Classpath ConfigSource Builder. *

* It allows to configure following properties: *

    *
  • {@code resource} - configuration resource name;
  • *
  • {@code optional} - is existence of configuration resource mandatory (by default) or is {@code optional}?
  • *
  • {@code media-type} - configuration content media type to be used to look for appropriate {@link ConfigParser};
  • *
  • {@code parser} - or directly set {@link ConfigParser} instance to be used to parse the source;
  • *
*

* If the ConfigSource is {@code mandatory} and a {@code resource} does not exist * then {@link io.helidon.config.spi.ParsableSource#load} throws {@link ConfigException}. *

* If {@code media-type} not set it tries to guess it from resource extension. */ public static final class Builder extends AbstractConfigSourceBuilder implements ParsableSource.Builder, io.helidon.common.Builder { private URL url; private String resource; /** * Initialize builder. */ private Builder() { } /** * Builds new instance of Classpath ConfigSource. *

* If {@code media-type} not set it tries to guess it from resource extension before parsing. * * @return new instance of Classpath ConfigSource. */ @Override public ClasspathConfigSource build() { return new ClasspathConfigSource(this); } /** * {@inheritDoc} *

    *
  • {@code resource} - the classpath resource to load
  • *
* @param metaConfig configuration properties used to configure a builder instance. * @return updated builder instance */ @Override public Builder config(Config metaConfig) { metaConfig.get("resource").asString().ifPresent(this::resource); return super.config(metaConfig); } @Override public Builder parser(ConfigParser parser) { return super.parser(parser); } @Override public Builder mediaType(MediaType mediaType) { return super.mediaType(mediaType); } /** * Configure the classpath resource to load the configuration from. * * @param resource resource on classpath * @return updated builder instance */ public Builder resource(String resource) { String cleaned = resource.startsWith("/") ? resource.substring(1) : resource; this.resource = resource; // the URL may not exist, and that is fine - maybe we are an optional config source this.url = Thread.currentThread() .getContextClassLoader() .getResource(cleaned); return this; } private Builder url(URL url) { this.url = url; return this; } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy