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 * ConfigurationSource to use with Configurations that do not require a "real" configuration source. 044 */ 045 public static final ConfigurationSource NULL_SOURCE = new ConfigurationSource(new byte[0]); 046 047 private final File file; 048 private final URL url; 049 private final String location; 050 private final InputStream stream; 051 private final byte[] data; 052 053 /** 054 * Constructs a new {@code ConfigurationSource} with the specified input stream that originated from the specified 055 * file. 056 * 057 * @param stream the input stream 058 * @param file the file where the input stream originated 059 */ 060 public ConfigurationSource(final InputStream stream, final File file) { 061 this.stream = Objects.requireNonNull(stream, "stream is null"); 062 this.file = Objects.requireNonNull(file, "file is null"); 063 this.location = file.getAbsolutePath(); 064 this.url = null; 065 this.data = null; 066 } 067 068 /** 069 * Constructs a new {@code ConfigurationSource} with the specified input stream that originated from the specified 070 * url. 071 * 072 * @param stream the input stream 073 * @param url the URL where the input stream originated 074 */ 075 public ConfigurationSource(final InputStream stream, final URL url) { 076 this.stream = Objects.requireNonNull(stream, "stream is null"); 077 this.url = Objects.requireNonNull(url, "URL is null"); 078 this.location = url.toString(); 079 this.file = null; 080 this.data = null; 081 } 082 083 /** 084 * Constructs a new {@code ConfigurationSource} with the specified input stream. Since the stream is the only source 085 * of data, this constructor makes a copy of the stream contents. 086 * 087 * @param stream the input stream 088 * @throws IOException if an exception occurred reading from the specified stream 089 */ 090 public ConfigurationSource(final InputStream stream) throws IOException { 091 this(toByteArray(stream)); 092 } 093 094 private ConfigurationSource(final byte[] data) { 095 this.data = Objects.requireNonNull(data, "data is null"); 096 this.stream = new ByteArrayInputStream(data); 097 this.file = null; 098 this.url = null; 099 this.location = null; 100 } 101 102 /** 103 * Returns the contents of the specified {@code InputStream} as a byte array. 104 * 105 * @param inputStream the stream to read 106 * @return the contents of the specified stream 107 * @throws IOException if a problem occurred reading from the stream 108 */ 109 private static byte[] toByteArray(final InputStream inputStream) throws IOException { 110 final int buffSize = Math.max(4096, inputStream.available()); 111 final ByteArrayOutputStream contents = new ByteArrayOutputStream(buffSize); 112 final byte[] buff = new byte[buffSize]; 113 114 int length = inputStream.read(buff); 115 while (length > 0) { 116 contents.write(buff, 0, length); 117 length = inputStream.read(buff); 118 } 119 return contents.toByteArray(); 120 } 121 122 /** 123 * Returns the file configuration source, or {@code null} if this configuration source is based on an URL or has 124 * neither a file nor an URL. 125 * 126 * @return the configuration source file, or {@code null} 127 */ 128 public File getFile() { 129 return file; 130 } 131 132 /** 133 * Returns the configuration source URL, or {@code null} if this configuration source is based on a file or has 134 * neither a file nor an URL. 135 * 136 * @return the configuration source URL, or {@code null} 137 */ 138 public URL getURL() { 139 return url; 140 } 141 142 /** 143 * Returns a URI representing the configuration resource or null if it cannot be determined. 144 * @return The URI. 145 */ 146 public URI getURI() { 147 URI sourceURI = null; 148 if (url != null) { 149 try { 150 sourceURI = url.toURI(); 151 } catch (final URISyntaxException ex) { 152 /* Ignore the exception */ 153 } 154 } 155 if (sourceURI == null && file != null) { 156 sourceURI = file.toURI(); 157 } 158 if (sourceURI == null && location != null) { 159 try { 160 sourceURI = new URI(location); 161 } catch (final URISyntaxException ex) { 162 // Assume the scheme was missing. 163 try { 164 sourceURI = new URI("file://" + location); 165 } catch (final URISyntaxException uriEx) { 166 /* Ignore the exception */ 167 } 168 } 169 } 170 return sourceURI; 171 } 172 173 /** 174 * Returns a string describing the configuration source file or URL, or {@code null} if this configuration source 175 * has neither a file nor an URL. 176 * 177 * @return a string describing the configuration source file or URL, or {@code null} 178 */ 179 public String getLocation() { 180 return location; 181 } 182 183 /** 184 * Returns the input stream that this configuration source was constructed with. 185 * 186 * @return the input stream that this configuration source was constructed with. 187 */ 188 public InputStream getInputStream() { 189 return stream; 190 } 191 192 /** 193 * Returns a new {@code ConfigurationSource} whose input stream is reset to the beginning. 194 * 195 * @return a new {@code ConfigurationSource} 196 * @throws IOException if a problem occurred while opening the new input stream 197 */ 198 public ConfigurationSource resetInputStream() throws IOException { 199 if (file != null) { 200 return new ConfigurationSource(new FileInputStream(file), file); 201 } else if (url != null) { 202 return new ConfigurationSource(url.openStream(), url); 203 } else { 204 return new ConfigurationSource(data); 205 } 206 } 207 208 @Override 209 public String toString() { 210 if (location != null) { 211 return location; 212 } 213 if (this == NULL_SOURCE) { 214 return "NULL_SOURCE"; 215 } 216 final int length = data == null ? -1 : data.length; 217 return "stream (" + length + " bytes, unknown location)"; 218 } 219 220 /** 221 * Loads the configuration from a URI. 222 * @param configLocation A URI representing the location of the configuration. 223 * @return The ConfigurationSource for the configuration. 224 */ 225 public static ConfigurationSource fromUri(final URI configLocation) { 226 final File configFile = FileUtils.fileFromUri(configLocation); 227 if (configFile != null && configFile.exists() && configFile.canRead()) { 228 try { 229 return new ConfigurationSource(new FileInputStream(configFile), configFile); 230 } catch (final FileNotFoundException ex) { 231 ConfigurationFactory.LOGGER.error("Cannot locate file {}", configLocation.getPath(), ex); 232 } 233 } 234 if (ConfigurationFactory.isClassLoaderUri(configLocation)) { 235 final ClassLoader loader = LoaderUtil.getThreadContextClassLoader(); 236 final String path = ConfigurationFactory.extractClassLoaderUriPath(configLocation); 237 final ConfigurationSource source = fromResource(path, loader); 238 if (source != null) { 239 return source; 240 } 241 } 242 if (!configLocation.isAbsolute()) { // LOG4J2-704 avoid confusing error message thrown by uri.toURL() 243 ConfigurationFactory.LOGGER.error("File not found in file system or classpath: {}", configLocation.toString()); 244 return null; 245 } 246 try { 247 return new ConfigurationSource(configLocation.toURL().openStream(), configLocation.toURL()); 248 } catch (final MalformedURLException ex) { 249 ConfigurationFactory.LOGGER.error("Invalid URL {}", configLocation.toString(), ex); 250 } catch (final Exception ex) { 251 ConfigurationFactory.LOGGER.error("Unable to access {}", configLocation.toString(), ex); 252 } 253 return null; 254 } 255 256 /** 257 * Retrieves the configuration via the ClassLoader. 258 * @param resource The resource to load. 259 * @param loader The default ClassLoader to use. 260 * @return The ConfigurationSource for the configuration. 261 */ 262 public static ConfigurationSource fromResource(final String resource, final ClassLoader loader) { 263 final URL url = Loader.getResource(resource, loader); 264 if (url == null) { 265 return null; 266 } 267 InputStream is = null; 268 try { 269 is = url.openStream(); 270 } catch (final IOException ioe) { 271 ConfigurationFactory.LOGGER.catching(Level.DEBUG, ioe); 272 return null; 273 } 274 if (is == null) { 275 return null; 276 } 277 278 if (FileUtils.isFile(url)) { 279 try { 280 return new ConfigurationSource(is, FileUtils.fileFromUri(url.toURI())); 281 } catch (final URISyntaxException ex) { 282 // Just ignore the exception. 283 ConfigurationFactory.LOGGER.catching(Level.DEBUG, ex); 284 } 285 } 286 return new ConfigurationSource(is, url); 287 } 288}