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.net.URLConnection;
32  import java.util.Objects;
33  
34  import org.apache.logging.log4j.Level;
35  import org.apache.logging.log4j.core.net.UrlConnectionFactory;
36  import org.apache.logging.log4j.core.util.FileUtils;
37  import org.apache.logging.log4j.core.util.Loader;
38  import org.apache.logging.log4j.core.util.Source;
39  import org.apache.logging.log4j.util.LoaderUtil;
40  
41  /**
42   * Represents the source for the logging configuration.
43   */
44  public class ConfigurationSource {
45  
46      /**
47       * ConfigurationSource to use with Configurations that do not require a "real" configuration source.
48       */
49      public static final ConfigurationSource NULL_SOURCE = new ConfigurationSource(new byte[0], null, 0);
50      private static final String HTTPS = "https";
51      private static final String HTTP = "http";
52  
53      private final File file;
54      private final URL url;
55      private final String location;
56      private final InputStream stream;
57      private volatile byte[] data;
58      private volatile Source source = null;
59      private final long lastModified;
60      // Set when the configuration has been updated so reset can use it for the next lastModified timestamp.
61      private volatile long modifiedMillis;
62  
63      /**
64       * Constructs a new {@code ConfigurationSource} with the specified input stream that originated from the specified
65       * file.
66       *
67       * @param stream the input stream
68       * @param file the file where the input stream originated
69       */
70      public ConfigurationSource(final InputStream stream, final File file) {
71          this.stream = Objects.requireNonNull(stream, "stream is null");
72          this.file = Objects.requireNonNull(file, "file is null");
73          this.location = file.getAbsolutePath();
74          this.url = null;
75          this.data = null;
76          long modified = 0;
77          try {
78              modified = file.lastModified();
79          } catch (Exception ex) {
80              // There is a problem with the file. It will be handled somewhere else.
81          }
82          this.lastModified = modified;
83      }
84  
85      /**
86       * Constructs a new {@code ConfigurationSource} with the specified input stream that originated from the specified
87       * url.
88       *
89       * @param stream the input stream
90       * @param url the URL where the input stream originated
91       */
92      public ConfigurationSource(final InputStream stream, final URL url) {
93          this.stream = Objects.requireNonNull(stream, "stream is null");
94          this.url = Objects.requireNonNull(url, "URL is null");
95          this.location = url.toString();
96          this.file = null;
97          this.data = null;
98          this.lastModified = 0;
99      }
100 
101     /**
102      * Constructs a new {@code ConfigurationSource} with the specified input stream that originated from the specified
103      * url.
104      *
105      * @param stream the input stream
106      * @param url the URL where the input stream originated
107      * @param lastModified when the source was last modified.
108      */
109     public ConfigurationSource(final InputStream stream, final URL url, long lastModified) {
110         this.stream = Objects.requireNonNull(stream, "stream is null");
111         this.url = Objects.requireNonNull(url, "URL is null");
112         this.location = url.toString();
113         this.file = null;
114         this.data = null;
115         this.lastModified = lastModified;
116     }
117 
118     /**
119      * Constructs a new {@code ConfigurationSource} with the specified input stream. Since the stream is the only source
120      * of data, this constructor makes a copy of the stream contents.
121      *
122      * @param stream the input stream
123      * @throws IOException if an exception occurred reading from the specified stream
124      */
125     public ConfigurationSource(final InputStream stream) throws IOException {
126         this(toByteArray(stream), null, 0);
127     }
128 
129     public ConfigurationSource(final Source source, final byte[] data, long lastModified) throws IOException {
130         Objects.requireNonNull(source, "source is null");
131         this.data = Objects.requireNonNull(data, "data is null");
132         this.stream = new ByteArrayInputStream(data);
133         this.file = source.getFile();
134         this.url = source.getURI().toURL();
135         this.location = source.getLocation();
136         this.lastModified = lastModified;
137     }
138 
139     private ConfigurationSource(final byte[] data, final URL url, long lastModified) {
140         Objects.requireNonNull(data, "data is null");
141         this.stream = new ByteArrayInputStream(data);
142         this.file = null;
143         this.url = url;
144         this.location = null;
145         this.lastModified = lastModified;
146     }
147 
148     /**
149      * Returns the contents of the specified {@code InputStream} as a byte array.
150      *
151      * @param inputStream the stream to read
152      * @return the contents of the specified stream
153      * @throws IOException if a problem occurred reading from the stream
154      */
155     private static byte[] toByteArray(final InputStream inputStream) throws IOException {
156         final int buffSize = Math.max(4096, inputStream.available());
157         final ByteArrayOutputStream contents = new ByteArrayOutputStream(buffSize);
158         final byte[] buff = new byte[buffSize];
159 
160         int length = inputStream.read(buff);
161         while (length > 0) {
162             contents.write(buff, 0, length);
163             length = inputStream.read(buff);
164         }
165         return contents.toByteArray();
166     }
167 
168     /**
169      * Returns the file configuration source, or {@code null} if this configuration source is based on an URL or has
170      * neither a file nor an URL.
171      *
172      * @return the configuration source file, or {@code null}
173      */
174     public File getFile() {
175         return file;
176     }
177 
178     /**
179      * Returns the configuration source URL, or {@code null} if this configuration source is based on a file or has
180      * neither a file nor an URL.
181      *
182      * @return the configuration source URL, or {@code null}
183      */
184     public URL getURL() {
185         return url;
186     }
187 
188     public void setSource(Source source) {
189         this.source = source;
190     }
191 
192     public void setData(byte[] data) {
193         this.data = data;
194     }
195 
196     public void setModifiedMillis(long modifiedMillis) {
197         this.modifiedMillis = modifiedMillis;
198     }
199 
200     /**
201      * Returns a URI representing the configuration resource or null if it cannot be determined.
202      * @return The URI.
203      */
204     public URI getURI() {
205         URI sourceURI = null;
206         if (url != null) {
207             try {
208                 sourceURI = url.toURI();
209             } catch (final URISyntaxException ex) {
210                     /* Ignore the exception */
211             }
212         }
213         if (sourceURI == null && file != null) {
214             sourceURI = file.toURI();
215         }
216         if (sourceURI == null && location != null) {
217             try {
218                 sourceURI = new URI(location);
219             } catch (final URISyntaxException ex) {
220                 // Assume the scheme was missing.
221                 try {
222                     sourceURI = new URI("file://" + location);
223                 } catch (final URISyntaxException uriEx) {
224                     /* Ignore the exception */
225                 }
226             }
227         }
228         return sourceURI;
229     }
230 
231     /**
232      * Returns the time the resource was last modified or 0 if it is not available.
233      * @return the last modified time of the resource.
234      */
235     public long getLastModified() {
236         return lastModified;
237     }
238 
239     /**
240      * Returns a string describing the configuration source file or URL, or {@code null} if this configuration source
241      * has neither a file nor an URL.
242      *
243      * @return a string describing the configuration source file or URL, or {@code null}
244      */
245     public String getLocation() {
246         return location;
247     }
248 
249     /**
250      * Returns the input stream that this configuration source was constructed with.
251      *
252      * @return the input stream that this configuration source was constructed with.
253      */
254     public InputStream getInputStream() {
255         return stream;
256     }
257 
258     /**
259      * Returns a new {@code ConfigurationSource} whose input stream is reset to the beginning.
260      *
261      * @return a new {@code ConfigurationSource}
262      * @throws IOException if a problem occurred while opening the new input stream
263      */
264     public ConfigurationSource resetInputStream() throws IOException {
265         if (source != null) {
266             return new ConfigurationSource(source, data, this.lastModified);
267         } else if (file != null) {
268             return new ConfigurationSource(new FileInputStream(file), file);
269         } else if (url != null && data != null) {
270             // Creates a ConfigurationSource without accessing the URL since the data was provided.
271             return new ConfigurationSource(data, url, modifiedMillis == 0 ? lastModified : modifiedMillis);
272         } else if (url != null) {
273             return fromUri(getURI());
274         } else if (data != null) {
275             return new ConfigurationSource(data, null, lastModified);
276         }
277         return null;
278     }
279 
280     @Override
281     public String toString() {
282         if (location != null) {
283             return location;
284         }
285         if (this == NULL_SOURCE) {
286             return "NULL_SOURCE";
287         }
288         final int length = data == null ? -1 : data.length;
289         return "stream (" + length + " bytes, unknown location)";
290     }
291 
292     /**
293      * Loads the configuration from a URI.
294      * @param configLocation A URI representing the location of the configuration.
295      * @return The ConfigurationSource for the configuration.
296      */
297     public static ConfigurationSource fromUri(final URI configLocation) {
298         final File configFile = FileUtils.fileFromUri(configLocation);
299         if (configFile != null && configFile.exists() && configFile.canRead()) {
300             try {
301                 return new ConfigurationSource(new FileInputStream(configFile), configFile);
302             } catch (final FileNotFoundException ex) {
303                 ConfigurationFactory.LOGGER.error("Cannot locate file {}", configLocation.getPath(), ex);
304             }
305         }
306         if (ConfigurationFactory.isClassLoaderUri(configLocation)) {
307             final ClassLoader loader = LoaderUtil.getThreadContextClassLoader();
308             final String path = ConfigurationFactory.extractClassLoaderUriPath(configLocation);
309             final ConfigurationSource source = fromResource(path, loader);
310             if (source != null) {
311                 return source;
312             }
313         }
314         if (!configLocation.isAbsolute()) { // LOG4J2-704 avoid confusing error message thrown by uri.toURL()
315             ConfigurationFactory.LOGGER.error("File not found in file system or classpath: {}", configLocation.toString());
316             return null;
317         }
318         try {
319             URL url = configLocation.toURL();
320             URLConnection urlConnection = UrlConnectionFactory.createConnection(url);
321             InputStream is = urlConnection.getInputStream();
322             long lastModified = urlConnection.getLastModified();
323             return new ConfigurationSource(is, configLocation.toURL(), lastModified);
324         } catch (final MalformedURLException ex) {
325             ConfigurationFactory.LOGGER.error("Invalid URL {}", configLocation.toString(), ex);
326         } catch (final Exception ex) {
327             ConfigurationFactory.LOGGER.error("Unable to access {}", configLocation.toString(), ex);
328         }
329         return null;
330     }
331 
332     /**
333      * Retrieves the configuration via the ClassLoader.
334      * @param resource The resource to load.
335      * @param loader The default ClassLoader to use.
336      * @return The ConfigurationSource for the configuration.
337      */
338     public static ConfigurationSource fromResource(final String resource, final ClassLoader loader) {
339         final URL url = Loader.getResource(resource, loader);
340         if (url == null) {
341             return null;
342         }
343         InputStream is = null;
344         try {
345             is = url.openStream();
346         } catch (final IOException ioe) {
347             ConfigurationFactory.LOGGER.catching(Level.DEBUG, ioe);
348             return null;
349         }
350         if (is == null) {
351             return null;
352         }
353 
354         if (FileUtils.isFile(url)) {
355             try {
356                 return new ConfigurationSource(is, FileUtils.fileFromUri(url.toURI()));
357             } catch (final URISyntaxException ex) {
358                 // Just ignore the exception.
359                 ConfigurationFactory.LOGGER.catching(Level.DEBUG, ex);
360             }
361         }
362         return new ConfigurationSource(is, url);
363     }
364 }