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