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