001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements. See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache license, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License. You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the license for the specific language governing permissions and
015 * limitations under the license.
016 */
017package org.apache.logging.log4j.core.config;
018
019import java.io.ByteArrayOutputStream;
020import java.io.IOException;
021import java.io.InputStream;
022import java.net.HttpURLConnection;
023import java.net.MalformedURLException;
024import java.net.URL;
025import java.util.List;
026
027import org.apache.logging.log4j.Logger;
028import org.apache.logging.log4j.core.config.plugins.Plugin;
029import org.apache.logging.log4j.core.config.plugins.PluginAliases;
030import org.apache.logging.log4j.core.net.UrlConnectionFactory;
031import org.apache.logging.log4j.core.net.ssl.SslConfiguration;
032import org.apache.logging.log4j.core.net.ssl.SslConfigurationFactory;
033import org.apache.logging.log4j.core.util.AbstractWatcher;
034import org.apache.logging.log4j.core.util.Source;
035import org.apache.logging.log4j.core.util.Watcher;
036import org.apache.logging.log4j.status.StatusLogger;
037
038/**
039 *
040 */
041@Plugin(name = "http", category = Watcher.CATEGORY, elementType = Watcher.ELEMENT_TYPE, printObject = true)
042@PluginAliases("https")
043public class HttpWatcher extends AbstractWatcher {
044
045    private Logger LOGGER = StatusLogger.getLogger();
046
047    private SslConfiguration sslConfiguration;
048    private URL url;
049    private volatile long lastModifiedMillis;
050    private static final int NOT_MODIFIED = 304;
051    private static final int OK = 200;
052    private static final int BUF_SIZE = 1024;
053    private static final String HTTP = "http";
054    private static final String HTTPS = "https";
055
056    public HttpWatcher(final Configuration configuration, final Reconfigurable reconfigurable,
057        final List<ConfigurationListener> configurationListeners, long lastModifiedMillis) {
058        super(configuration, reconfigurable, configurationListeners);
059        sslConfiguration = SslConfigurationFactory.getSslConfiguration();
060        this.lastModifiedMillis = lastModifiedMillis;
061    }
062
063    @Override
064    public long getLastModified() {
065        return lastModifiedMillis;
066    }
067
068    @Override
069    public boolean isModified() {
070        return refreshConfiguration();
071    }
072
073    @Override
074    public void watching(Source source) {
075        if (!source.getURI().getScheme().equals(HTTP) && !source.getURI().getScheme().equals(HTTPS)) {
076            throw new IllegalArgumentException(
077                "HttpWatcher requires a url using the HTTP or HTTPS protocol, not " + source.getURI().getScheme());
078        }
079        try {
080            url = source.getURI().toURL();
081        } catch (MalformedURLException ex) {
082            throw new IllegalArgumentException("Invalid URL for HttpWatcher " + source.getURI(), ex);
083        }
084        super.watching(source);
085    }
086
087    @Override
088    public Watcher newWatcher(Reconfigurable reconfigurable, List<ConfigurationListener> listeners,
089        long lastModifiedMillis) {
090        HttpWatcher watcher = new HttpWatcher(getConfiguration(), reconfigurable, listeners, lastModifiedMillis);
091        if (getSource() != null) {
092            watcher.watching(getSource());
093        }
094        return watcher;
095    }
096
097    private boolean refreshConfiguration() {
098        try {
099            final HttpURLConnection urlConnection = UrlConnectionFactory.createConnection(url, lastModifiedMillis,
100                sslConfiguration);
101            urlConnection.connect();
102
103            try {
104                int code = urlConnection.getResponseCode();
105                switch (code) {
106                    case NOT_MODIFIED: {
107                        LOGGER.debug("Configuration Not Modified");
108                        return false;
109                    }
110                    case OK: {
111                        try (InputStream is = urlConnection.getInputStream()) {
112                            ConfigurationSource configSource = getConfiguration().getConfigurationSource();
113                            configSource.setData(readStream(is));
114                            lastModifiedMillis = urlConnection.getLastModified();
115                            configSource.setModifiedMillis(lastModifiedMillis);
116                            LOGGER.debug("Content was modified for {}", url.toString());
117                            return true;
118                        } catch (final IOException e) {
119                            try (InputStream es = urlConnection.getErrorStream()) {
120                                LOGGER.info("Error accessing configuration at {}: {}", url, readStream(es));
121                            } catch (final IOException ioe) {
122                                LOGGER.error("Error accessing configuration at {}: {}", url, e.getMessage());
123                            }
124                            return false;
125                        }
126                    }
127                    default: {
128                        if (code < 0) {
129                            LOGGER.info("Invalid response code returned");
130                        } else {
131                            LOGGER.info("Unexpected response code returned {}", code);
132                        }
133                        return false;
134                    }
135                }
136            } catch (final IOException ioe) {
137                LOGGER.error("Error accessing configuration at {}: {}", url, ioe.getMessage());
138            }
139        } catch (final IOException ioe) {
140            LOGGER.error("Error connecting to configuration at {}: {}", url, ioe.getMessage());
141        }
142        return false;
143    }
144
145    private byte[] readStream(InputStream is) throws IOException {
146        ByteArrayOutputStream result = new ByteArrayOutputStream();
147        byte[] buffer = new byte[BUF_SIZE];
148        int length;
149        while ((length = is.read(buffer)) != -1) {
150            result.write(buffer, 0, length);
151        }
152        return result.toByteArray();
153    }
154}