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.net.URLConnection; 032import java.util.Objects; 033 034import org.apache.logging.log4j.Level; 035import org.apache.logging.log4j.core.net.UrlConnectionFactory; 036import org.apache.logging.log4j.core.util.FileUtils; 037import org.apache.logging.log4j.core.util.Loader; 038import org.apache.logging.log4j.core.util.Source; 039import org.apache.logging.log4j.util.LoaderUtil; 040 041/** 042 * Represents the source for the logging configuration. 043 */ 044public class ConfigurationSource { 045 046 /** 047 * ConfigurationSource to use with Configurations that do not require a "real" configuration source. 048 */ 049 public static final ConfigurationSource NULL_SOURCE = new ConfigurationSource(new byte[0], null, 0); 050 private static final String HTTPS = "https"; 051 private static final String HTTP = "http"; 052 053 private final File file; 054 private final URL url; 055 private final String location; 056 private final InputStream stream; 057 private volatile byte[] data; 058 private volatile Source source = null; 059 private final long lastModified; 060 // Set when the configuration has been updated so reset can use it for the next lastModified timestamp. 061 private volatile long modifiedMillis; 062 063 /** 064 * Constructs a new {@code ConfigurationSource} with the specified input stream that originated from the specified 065 * file. 066 * 067 * @param stream the input stream 068 * @param file the file where the input stream originated 069 */ 070 public ConfigurationSource(final InputStream stream, final File file) { 071 this.stream = Objects.requireNonNull(stream, "stream is null"); 072 this.file = Objects.requireNonNull(file, "file is null"); 073 this.location = file.getAbsolutePath(); 074 this.url = null; 075 this.data = null; 076 long modified = 0; 077 try { 078 modified = file.lastModified(); 079 } catch (Exception ex) { 080 // There is a problem with the file. It will be handled somewhere else. 081 } 082 this.lastModified = modified; 083 } 084 085 /** 086 * Constructs a new {@code ConfigurationSource} with the specified input stream that originated from the specified 087 * url. 088 * 089 * @param stream the input stream 090 * @param url the URL where the input stream originated 091 */ 092 public ConfigurationSource(final InputStream stream, final URL url) { 093 this.stream = Objects.requireNonNull(stream, "stream is null"); 094 this.url = Objects.requireNonNull(url, "URL is null"); 095 this.location = url.toString(); 096 this.file = null; 097 this.data = null; 098 this.lastModified = 0; 099 } 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}