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