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 */
017
018package org.apache.logging.log4j.core.config;
019
020import java.io.ByteArrayInputStream;
021import java.io.ByteArrayOutputStream;
022import java.io.File;
023import java.io.FileInputStream;
024import java.io.FileNotFoundException;
025import java.io.IOException;
026import java.io.InputStream;
027import java.net.MalformedURLException;
028import java.net.URI;
029import java.net.URISyntaxException;
030import java.net.URL;
031import java.util.Objects;
032
033import org.apache.logging.log4j.Level;
034import org.apache.logging.log4j.core.util.FileUtils;
035import org.apache.logging.log4j.core.util.Loader;
036import org.apache.logging.log4j.util.LoaderUtil;
037
038/**
039 * Represents the source for the logging configuration.
040 */
041public class ConfigurationSource {
042
043    /**
044     * ConfigurationSource to use with Configurations that do not require a "real" configuration source.
045     */
046    public static final ConfigurationSource NULL_SOURCE = new ConfigurationSource(new byte[0]);
047
048    private final File file;
049    private final URL url;
050    private final String location;
051    private final InputStream stream;
052    private final byte[] data;
053
054    /**
055     * Constructs a new {@code ConfigurationSource} with the specified input stream that originated from the specified
056     * file.
057     *
058     * @param stream the input stream
059     * @param file the file where the input stream originated
060     */
061    public ConfigurationSource(final InputStream stream, final File file) {
062        this.stream = Objects.requireNonNull(stream, "stream is null");
063        this.file = Objects.requireNonNull(file, "file is null");
064        this.location = file.getAbsolutePath();
065        this.url = null;
066        this.data = null;
067    }
068
069    /**
070     * Constructs a new {@code ConfigurationSource} with the specified input stream that originated from the specified
071     * url.
072     *
073     * @param stream the input stream
074     * @param url the URL where the input stream originated
075     */
076    public ConfigurationSource(final InputStream stream, final URL url) {
077        this.stream = Objects.requireNonNull(stream, "stream is null");
078        this.url = Objects.requireNonNull(url, "URL is null");
079        this.location = url.toString();
080        this.file = null;
081        this.data = null;
082    }
083
084    /**
085     * Constructs a new {@code ConfigurationSource} with the specified input stream. Since the stream is the only source
086     * of data, this constructor makes a copy of the stream contents.
087     *
088     * @param stream the input stream
089     * @throws IOException if an exception occurred reading from the specified stream
090     */
091    public ConfigurationSource(final InputStream stream) throws IOException {
092        this(toByteArray(stream));
093    }
094
095    private ConfigurationSource(final byte[] data) {
096        this.data = Objects.requireNonNull(data, "data is null");
097        this.stream = new ByteArrayInputStream(data);
098        this.file = null;
099        this.url = null;
100        this.location = null;
101    }
102
103    /**
104     * Returns the contents of the specified {@code InputStream} as a byte array.
105     *
106     * @param inputStream the stream to read
107     * @return the contents of the specified stream
108     * @throws IOException if a problem occurred reading from the stream
109     */
110    private static byte[] toByteArray(final InputStream inputStream) throws IOException {
111        final int buffSize = Math.max(4096, inputStream.available());
112        final ByteArrayOutputStream contents = new ByteArrayOutputStream(buffSize);
113        final byte[] buff = new byte[buffSize];
114
115        int length = inputStream.read(buff);
116        while (length > 0) {
117            contents.write(buff, 0, length);
118            length = inputStream.read(buff);
119        }
120        return contents.toByteArray();
121    }
122
123    /**
124     * Returns the file configuration source, or {@code null} if this configuration source is based on an URL or has
125     * neither a file nor an URL.
126     *
127     * @return the configuration source file, or {@code null}
128     */
129    public File getFile() {
130        return file;
131    }
132
133    /**
134     * Returns the configuration source URL, or {@code null} if this configuration source is based on a file or has
135     * neither a file nor an URL.
136     *
137     * @return the configuration source URL, or {@code null}
138     */
139    public URL getURL() {
140        return url;
141    }
142
143    /**
144     * Returns a URI representing the configuration resource or null if it cannot be determined.
145     * @return The URI.
146     */
147    public URI getURI() {
148        URI sourceURI = null;
149        if (url != null) {
150            try {
151                sourceURI = url.toURI();
152            } catch (final URISyntaxException ex) {
153                    /* Ignore the exception */
154            }
155        }
156        if (sourceURI == null && file != null) {
157            sourceURI = file.toURI();
158        }
159        if (sourceURI == null && location != null) {
160            try {
161                sourceURI = new URI(location);
162            } catch (final URISyntaxException ex) {
163                // Assume the scheme was missing.
164                try {
165                    sourceURI = new URI("file://" + location);
166                } catch (final URISyntaxException uriEx) {
167                    /* Ignore the exception */
168                }
169            }
170        }
171        return sourceURI;
172    }
173
174    /**
175     * Returns a string describing the configuration source file or URL, or {@code null} if this configuration source
176     * has neither a file nor an URL.
177     *
178     * @return a string describing the configuration source file or URL, or {@code null}
179     */
180    public String getLocation() {
181        return location;
182    }
183
184    /**
185     * Returns the input stream that this configuration source was constructed with.
186     *
187     * @return the input stream that this configuration source was constructed with.
188     */
189    public InputStream getInputStream() {
190        return stream;
191    }
192
193    /**
194     * Returns a new {@code ConfigurationSource} whose input stream is reset to the beginning.
195     *
196     * @return a new {@code ConfigurationSource}
197     * @throws IOException if a problem occurred while opening the new input stream
198     */
199    public ConfigurationSource resetInputStream() throws IOException {
200        if (file != null) {
201            return new ConfigurationSource(new FileInputStream(file), file);
202        } else if (url != null) {
203            return new ConfigurationSource(url.openStream(), url);
204        } else {
205            return new ConfigurationSource(data);
206        }
207    }
208
209    @Override
210    public String toString() {
211        if (location != null) {
212            return location;
213        }
214        if (this == NULL_SOURCE) {
215            return "NULL_SOURCE";
216        }
217        final int length = data == null ? -1 : data.length;
218        return "stream (" + length + " bytes, unknown location)";
219    }
220
221    /**
222     * Loads the configuration from a URI.
223     * @param configLocation A URI representing the location of the configuration.
224     * @return The ConfigurationSource for the configuration.
225     */
226    public static ConfigurationSource fromUri(final URI configLocation) {
227        final File configFile = FileUtils.fileFromUri(configLocation);
228        if (configFile != null && configFile.exists() && configFile.canRead()) {
229            try {
230                return new ConfigurationSource(new FileInputStream(configFile), configFile);
231            } catch (final FileNotFoundException ex) {
232                ConfigurationFactory.LOGGER.error("Cannot locate file {}", configLocation.getPath(), ex);
233            }
234        }
235        if (ConfigurationFactory.isClassLoaderUri(configLocation)) {
236            final ClassLoader loader = LoaderUtil.getThreadContextClassLoader();
237            final String path = ConfigurationFactory.extractClassLoaderUriPath(configLocation);
238            final ConfigurationSource source = fromResource(path, loader);
239            if (source != null) {
240                return source;
241            }
242        }
243        if (!configLocation.isAbsolute()) { // LOG4J2-704 avoid confusing error message thrown by uri.toURL()
244            ConfigurationFactory.LOGGER.error("File not found in file system or classpath: {}", configLocation.toString());
245            return null;
246        }
247        try {
248            return new ConfigurationSource(configLocation.toURL().openStream(), configLocation.toURL());
249        } catch (final MalformedURLException ex) {
250            ConfigurationFactory.LOGGER.error("Invalid URL {}", configLocation.toString(), ex);
251        } catch (final Exception ex) {
252            ConfigurationFactory.LOGGER.error("Unable to access {}", configLocation.toString(), ex);
253        }
254        return null;
255    }
256
257    /**
258     * Retrieves the configuration via the ClassLoader.
259     * @param resource The resource to load.
260     * @param loader The default ClassLoader to use.
261     * @return The ConfigurationSource for the configuration.
262     */
263    public static ConfigurationSource fromResource(final String resource, final ClassLoader loader) {
264        final URL url = Loader.getResource(resource, loader);
265        if (url == null) {
266            return null;
267        }
268        InputStream is = null;
269        try {
270            is = url.openStream();
271        } catch (final IOException ioe) {
272            ConfigurationFactory.LOGGER.catching(Level.DEBUG, ioe);
273            return null;
274        }
275        if (is == null) {
276            return null;
277        }
278
279        if (FileUtils.isFile(url)) {
280            try {
281                return new ConfigurationSource(is, FileUtils.fileFromUri(url.toURI()));
282            } catch (final URISyntaxException ex) {
283                // Just ignore the exception.
284                ConfigurationFactory.LOGGER.catching(Level.DEBUG, ex);
285            }
286        }
287        return new ConfigurationSource(is, url);
288    }
289}