View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements. See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache license, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License. You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the license for the specific language governing permissions and
15   * limitations under the license.
16   */
17  
18  package org.apache.logging.log4j.core.config;
19  
20  import java.io.ByteArrayInputStream;
21  import java.io.ByteArrayOutputStream;
22  import java.io.File;
23  import java.io.FileInputStream;
24  import java.io.FileNotFoundException;
25  import java.io.IOException;
26  import java.io.InputStream;
27  import java.net.MalformedURLException;
28  import java.net.URI;
29  import java.net.URISyntaxException;
30  import java.net.URL;
31  import java.util.Objects;
32  
33  import org.apache.logging.log4j.Level;
34  import org.apache.logging.log4j.core.util.FileUtils;
35  import org.apache.logging.log4j.core.util.Loader;
36  import org.apache.logging.log4j.util.LoaderUtil;
37  
38  /**
39   * Represents the source for the logging configuration.
40   */
41  public class ConfigurationSource {
42      /**
43       * ConfigurationSource to use with Configurations that do not require a "real" configuration source.
44       */
45      public static final ConfigurationSource NULL_SOURCE = new ConfigurationSource(new byte[0]);
46  
47      private final File file;
48      private final URL url;
49      private final String location;
50      private final InputStream stream;
51      private final byte[] data;
52  
53      /**
54       * Constructs a new {@code ConfigurationSource} with the specified input stream that originated from the specified
55       * file.
56       *
57       * @param stream the input stream
58       * @param file the file where the input stream originated
59       */
60      public ConfigurationSource(final InputStream stream, final File file) {
61          this.stream = Objects.requireNonNull(stream, "stream is null");
62          this.file = Objects.requireNonNull(file, "file is null");
63          this.location = file.getAbsolutePath();
64          this.url = null;
65          this.data = null;
66      }
67  
68      /**
69       * Constructs a new {@code ConfigurationSource} with the specified input stream that originated from the specified
70       * url.
71       *
72       * @param stream the input stream
73       * @param url the URL where the input stream originated
74       */
75      public ConfigurationSource(final InputStream stream, final URL url) {
76          this.stream = Objects.requireNonNull(stream, "stream is null");
77          this.url = Objects.requireNonNull(url, "URL is null");
78          this.location = url.toString();
79          this.file = null;
80          this.data = null;
81      }
82  
83      /**
84       * Constructs a new {@code ConfigurationSource} with the specified input stream. Since the stream is the only source
85       * of data, this constructor makes a copy of the stream contents.
86       *
87       * @param stream the input stream
88       * @throws IOException if an exception occurred reading from the specified stream
89       */
90      public ConfigurationSource(final InputStream stream) throws IOException {
91          this(toByteArray(stream));
92      }
93  
94      private ConfigurationSource(final byte[] data) {
95          this.data = Objects.requireNonNull(data, "data is null");
96          this.stream = new ByteArrayInputStream(data);
97          this.file = null;
98          this.url = null;
99          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 }