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.util.Objects; 032 033import org.apache.logging.log4j.Level; 034import org.apache.logging.log4j.core.util.FileUtils; 035import org.apache.logging.log4j.core.util.Loader; 036import org.apache.logging.log4j.util.LoaderUtil; 037 038/** 039 * Represents the source for the logging configuration. 040 */ 041public class ConfigurationSource { 042 043 /** 044 * ConfigurationSource to use with Configurations that do not require a "real" configuration source. 045 */ 046 public static final ConfigurationSource NULL_SOURCE = new ConfigurationSource(new byte[0]); 047 048 private final File file; 049 private final URL url; 050 private final String location; 051 private final InputStream stream; 052 private final byte[] data; 053 054 /** 055 * Constructs a new {@code ConfigurationSource} with the specified input stream that originated from the specified 056 * file. 057 * 058 * @param stream the input stream 059 * @param file the file where the input stream originated 060 */ 061 public ConfigurationSource(final InputStream stream, final File file) { 062 this.stream = Objects.requireNonNull(stream, "stream is null"); 063 this.file = Objects.requireNonNull(file, "file is null"); 064 this.location = file.getAbsolutePath(); 065 this.url = null; 066 this.data = null; 067 } 068 069 /** 070 * Constructs a new {@code ConfigurationSource} with the specified input stream that originated from the specified 071 * url. 072 * 073 * @param stream the input stream 074 * @param url the URL where the input stream originated 075 */ 076 public ConfigurationSource(final InputStream stream, final URL url) { 077 this.stream = Objects.requireNonNull(stream, "stream is null"); 078 this.url = Objects.requireNonNull(url, "URL is null"); 079 this.location = url.toString(); 080 this.file = null; 081 this.data = null; 082 } 083 084 /** 085 * Constructs a new {@code ConfigurationSource} with the specified input stream. Since the stream is the only source 086 * of data, this constructor makes a copy of the stream contents. 087 * 088 * @param stream the input stream 089 * @throws IOException if an exception occurred reading from the specified stream 090 */ 091 public ConfigurationSource(final InputStream stream) throws IOException { 092 this(toByteArray(stream)); 093 } 094 095 private ConfigurationSource(final byte[] data) { 096 this.data = Objects.requireNonNull(data, "data is null"); 097 this.stream = new ByteArrayInputStream(data); 098 this.file = null; 099 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}