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

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

There is a newer version: 4.1.1
Show newest version
/*
 * Copyright (c) 2019, 2020 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.HttpURLConnection;
import java.net.URL;
import java.net.URLConnection;
import java.nio.charset.Charset;
import java.time.Instant;
import java.util.Optional;
import java.util.logging.Level;
import java.util.logging.Logger;

import io.helidon.common.media.type.MediaTypes;
import io.helidon.config.spi.ChangeWatcher;
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;
import io.helidon.config.spi.PollableSource;
import io.helidon.config.spi.PollingStrategy;
import io.helidon.config.spi.WatchableSource;

/**
 * {@link ConfigSource} implementation that loads configuration content from specified endpoint URL.
 *
 * @see AbstractConfigSourceBuilder
 */
public final class UrlConfigSource extends AbstractConfigSource
        implements WatchableSource, ParsableSource, PollableSource {

    private static final Logger LOGGER = Logger.getLogger(UrlConfigSource.class.getName());

    private static final String GET_METHOD = "GET";
    private static final String URL_KEY = "url";
    private static final int STATUS_NOT_FOUND = 404;

    private final URL url;

    private UrlConfigSource(Builder builder) {
        super(builder);

        this.url = builder.url;
    }

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

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

    *
  • {@code url} - type {@link URL}
  • *
* Optional {@code properties}: see {@link AbstractConfigSourceBuilder#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#url(URL) * @see AbstractConfigSourceBuilder#config(Config) */ public static UrlConfigSource create(Config metaConfig) throws ConfigMappingException, MissingValueException { return builder() .config(metaConfig) .build(); } /** * A new fluent API builder. * * @return a new builder instance */ public static Builder builder() { return new Builder(); } @Override protected String uid() { return url.toString(); } @Override public URL target() { return url; } @Override public Class targetType() { return URL.class; } @Override public Optional parser() { return super.parser(); } @Override public Optional mediaType() { return super.mediaType(); } @Override public Optional pollingStrategy() { return super.pollingStrategy(); } @Override public Optional> changeWatcher() { return super.changeWatcher(); } @Override public boolean isModified(Instant stamp) { return UrlHelper.isModified(url, stamp); } @Override public Optional load() throws ConfigException { try { URLConnection urlConnection = url.openConnection(); if (urlConnection instanceof HttpURLConnection) { return httpContent((HttpURLConnection) urlConnection); } else { return genericContent(urlConnection); } } catch (ConfigException ex) { throw ex; } catch (Exception ex) { throw new ConfigException("Configuration at url '" + url + "' is not accessible.", ex); } } private Optional genericContent(URLConnection urlConnection) throws IOException { InputStream is = urlConnection.getInputStream(); Content.Builder builder = Content.builder() .data(is) .stamp(Instant.now()); this.probeContentType().ifPresent(builder::mediaType); return Optional.ofNullable(builder.build()); } private Optional httpContent(HttpURLConnection connection) throws IOException { connection.setRequestMethod(GET_METHOD); try { connection.connect(); } catch (IOException e) { // considering this to be unavailable LOGGER.log(Level.FINEST, "Failed to connect to " + url + ", considering this source to be missing", e); return Optional.empty(); } if (STATUS_NOT_FOUND == connection.getResponseCode()) { return Optional.empty(); } Optional mediaType = mediaType(connection.getContentType()); final Instant timestamp; if (connection.getLastModified() == 0) { timestamp = Instant.now(); LOGGER.fine("Missing GET '" + url + "' response header 'Last-Modified'. Used current time '" + timestamp + "' as a content timestamp."); } else { timestamp = Instant.ofEpochMilli(connection.getLastModified()); } InputStream inputStream = connection.getInputStream(); Charset charset = ConfigUtils.getContentCharset(connection.getContentEncoding()); Content.Builder builder = Content.builder(); builder.data(inputStream); builder.charset(charset); builder.stamp(timestamp); mediaType.ifPresent(builder::mediaType); return Optional.of(builder.build()); } private Optional mediaType(String responseMediaType) { return mediaType() .or(() -> Optional.ofNullable(responseMediaType)) .or(() -> { Optional mediaType = probeContentType(); if (LOGGER.isLoggable(Level.FINE)) { LOGGER.fine("HTTP response does not contain content-type, used guessed one: " + mediaType + "."); } return mediaType; }); } private Optional probeContentType() { return MediaTypes.detectType(url); } /** * Url ConfigSource Builder. *

* It allows to configure following properties: *

    *
  • {@code url} - configuration endpoint URL;
  • *
  • {@code mandatory} - 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 {@code media-type} not set it uses HTTP response header {@code content-type}. * If {@code media-type} not returned it tries to guess it from url suffix. */ public static final class Builder extends AbstractConfigSourceBuilder implements PollableSource.Builder, WatchableSource.Builder, ParsableSource.Builder, io.helidon.common.Builder { private URL url; /** * Initialize builder. */ private Builder() { } /** * URL of the configuration. * * @param url of configuration source * @return updated builder instance */ public Builder url(URL url) { this.url = url; return this; } /** * {@inheritDoc} *

    *
  • {@code url} - URL of the configuration source
  • *
* @param metaConfig configuration properties used to configure a builder instance. * @return updated builder instance */ @Override public Builder config(Config metaConfig) { metaConfig.get(URL_KEY).as(URL.class).ifPresent(this::url); return super.config(metaConfig); } /** * Builds new instance of Url ConfigSource. *

* If {@code media-type} not set it tries to use {@code content-type} response header or guesses it from file extension. * * @return new instance of Url ConfigSource. */ @Override public UrlConfigSource build() { if (null == url) { throw new IllegalArgumentException("url must be provided"); } return new UrlConfigSource(this); } @Override public Builder parser(ConfigParser parser) { return super.parser(parser); } @Override public Builder mediaType(String mediaType) { return super.mediaType(mediaType); } @Override public Builder changeWatcher(ChangeWatcher changeWatcher) { return super.changeWatcher(changeWatcher); } @Override public Builder pollingStrategy(PollingStrategy pollingStrategy) { return super.pollingStrategy(pollingStrategy); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy