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      /**
44       * ConfigurationSource to use with Configurations that do not require a "real" configuration source.
45       */
46      public static final ConfigurationSource NULL_SOURCE = new ConfigurationSource(new byte[0]);
47  
48      private final File file;
49      private final URL url;
50      private final String location;
51      private final InputStream stream;
52      private final byte[] data;
53  
54      /**
55       * Constructs a new {@code ConfigurationSource} with the specified input stream that originated from the specified
56       * file.
57       *
58       * @param stream the input stream
59       * @param file the file where the input stream originated
60       */
61      public ConfigurationSource(final InputStream stream, final File file) {
62          this.stream = Objects.requireNonNull(stream, "stream is null");
63          this.file = Objects.requireNonNull(file, "file is null");
64          this.location = file.getAbsolutePath();
65          this.url = null;
66          this.data = null;
67      }
68  
69      /**
70       * Constructs a new {@code ConfigurationSource} with the specified input stream that originated from the specified
71       * url.
72       *
73       * @param stream the input stream
74       * @param url the URL where the input stream originated
75       */
76      public ConfigurationSource(final InputStream stream, final URL url) {
77          this.stream = Objects.requireNonNull(stream, "stream is null");
78          this.url = Objects.requireNonNull(url, "URL is null");
79          this.location = url.toString();
80          this.file = null;
81          this.data = null;
82      }
83  
84      /**
85       * Constructs a new {@code ConfigurationSource} with the specified input stream. Since the stream is the only source
86       * of data, this constructor makes a copy of the stream contents.
87       *
88       * @param stream the input stream
89       * @throws IOException if an exception occurred reading from the specified stream
90       */
91      public ConfigurationSource(final InputStream stream) throws IOException {
92          this(toByteArray(stream));
93      }
94  
95      private ConfigurationSource(final byte[] data) {
96          this.data = Objects.requireNonNull(data, "data is null");
97          this.stream = new ByteArrayInputStream(data);
98          this.file = null;
99          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 }