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.IOException;
025import java.io.InputStream;
026import java.net.URI;
027import java.net.URISyntaxException;
028import java.net.URL;
029import java.util.Objects;
030
031/**
032 * Represents the source for the logging configuration.
033 */
034public class ConfigurationSource {
035    /**
036     * ConfigurationSource to use with Configurations that do not require a "real" configuration source.
037     */
038    public static final ConfigurationSource NULL_SOURCE = new ConfigurationSource(new byte[0]);
039
040    private final File file;
041    private final URL url;
042    private final String location;
043    private final InputStream stream;
044    private final byte[] data;
045
046    /**
047     * Constructs a new {@code ConfigurationSource} with the specified input stream that originated from the specified
048     * file.
049     *
050     * @param stream the input stream
051     * @param file the file where the input stream originated
052     */
053    public ConfigurationSource(final InputStream stream, final File file) {
054        this.stream = Objects.requireNonNull(stream, "stream is null");
055        this.file = Objects.requireNonNull(file, "file is null");
056        this.location = file.getAbsolutePath();
057        this.url = null;
058        this.data = null;
059    }
060
061    /**
062     * Constructs a new {@code ConfigurationSource} with the specified input stream that originated from the specified
063     * url.
064     *
065     * @param stream the input stream
066     * @param url the URL where the input stream originated
067     */
068    public ConfigurationSource(final InputStream stream, final URL url) {
069        this.stream = Objects.requireNonNull(stream, "stream is null");
070        this.url = Objects.requireNonNull(url, "URL is null");
071        this.location = url.toString();
072        this.file = null;
073        this.data = null;
074    }
075
076    /**
077     * Constructs a new {@code ConfigurationSource} with the specified input stream. Since the stream is the only source
078     * of data, this constructor makes a copy of the stream contents.
079     *
080     * @param stream the input stream
081     * @throws IOException if an exception occurred reading from the specified stream
082     */
083    public ConfigurationSource(final InputStream stream) throws IOException {
084        this(toByteArray(stream));
085    }
086
087    private ConfigurationSource(final byte[] data) {
088        this.data = Objects.requireNonNull(data, "data is null");
089        this.stream = new ByteArrayInputStream(data);
090        this.file = null;
091        this.url = null;
092        this.location = null;
093    }
094
095    /**
096     * Returns the contents of the specified {@code InputStream} as a byte array.
097     *
098     * @param inputStream the stream to read
099     * @return the contents of the specified stream
100     * @throws IOException if a problem occurred reading from the stream
101     */
102    private static byte[] toByteArray(final InputStream inputStream) throws IOException {
103        final int buffSize = Math.max(4096, inputStream.available());
104        final ByteArrayOutputStream contents = new ByteArrayOutputStream(buffSize);
105        final byte[] buff = new byte[buffSize];
106
107        int length = inputStream.read(buff);
108        while (length > 0) {
109            contents.write(buff, 0, length);
110            length = inputStream.read(buff);
111        }
112        return contents.toByteArray();
113    }
114
115    /**
116     * Returns the file configuration source, or {@code null} if this configuration source is based on an URL or has
117     * neither a file nor an URL.
118     *
119     * @return the configuration source file, or {@code null}
120     */
121    public File getFile() {
122        return file;
123    }
124
125    /**
126     * Returns the configuration source URL, or {@code null} if this configuration source is based on a file or has
127     * neither a file nor an URL.
128     *
129     * @return the configuration source URL, or {@code null}
130     */
131    public URL getURL() {
132        return url;
133    }
134
135    /**
136     * Returns a URI representing the configuration resource or null if it cannot be determined.
137     * @return The URI.
138     */
139    public URI getURI() {
140        URI sourceURI = null;
141        if (url != null) {
142            try {
143                sourceURI = url.toURI();
144            } catch (URISyntaxException ex) {
145                    /* Ignore the exception */
146            }
147        }
148        if (sourceURI == null && file != null) {
149            sourceURI = file.toURI();
150        }
151        if (sourceURI == null && location != null) {
152            try {
153                sourceURI = new URI(location);
154            } catch (URISyntaxException ex) {
155                // Assume the scheme was missing.
156                try {
157                    sourceURI = new URI("file://" + location);
158                } catch (URISyntaxException uriEx) {
159                    /* Ignore the exception */
160                }
161            }
162        }
163        return sourceURI;
164    }
165
166    /**
167     * Returns a string describing the configuration source file or URL, or {@code null} if this configuration source
168     * has neither a file nor an URL.
169     *
170     * @return a string describing the configuration source file or URL, or {@code null}
171     */
172    public String getLocation() {
173        return location;
174    }
175
176    /**
177     * Returns the input stream that this configuration source was constructed with.
178     *
179     * @return the input stream that this configuration source was constructed with.
180     */
181    public InputStream getInputStream() {
182        return stream;
183    }
184
185    /**
186     * Returns a new {@code ConfigurationSource} whose input stream is reset to the beginning.
187     *
188     * @return a new {@code ConfigurationSource}
189     * @throws IOException if a problem occurred while opening the new input stream
190     */
191    public ConfigurationSource resetInputStream() throws IOException {
192        if (file != null) {
193            return new ConfigurationSource(new FileInputStream(file), file);
194        } else if (url != null) {
195            return new ConfigurationSource(url.openStream(), url);
196        } else {
197            return new ConfigurationSource(data);
198        }
199    }
200
201    @Override
202    public String toString() {
203        if (location != null) {
204            return location;
205        }
206        if (this == NULL_SOURCE) {
207            return "NULL_SOURCE";
208        }
209        final int length = data == null ? -1 : data.length;
210        return "stream (" + length + " bytes, unknown location)";
211    }
212}