001/*
002 * Logback: the reliable, generic, fast and flexible logging framework.
003 * Copyright (C) 1999-2024, QOS.ch. All rights reserved.
004 *
005 * This program and the accompanying materials are dual-licensed under
006 * either the terms of the Eclipse Public License v1.0 as published by
007 * the Eclipse Foundation
008 *
009 *   or (per the licensee's choosing)
010 *
011 * under the terms of the GNU Lesser General Public License version 2.1
012 * as published by the Free Software Foundation.
013 */
014
015package ch.qos.logback.classic.joran;
016
017import ch.qos.logback.classic.Level;
018import ch.qos.logback.classic.Logger;
019import ch.qos.logback.classic.LoggerContext;
020import ch.qos.logback.core.Context;
021import ch.qos.logback.core.joran.spi.JoranException;
022import ch.qos.logback.core.model.util.VariableSubstitutionsHelper;
023import ch.qos.logback.core.spi.ContextAwareBase;
024
025import java.io.File;
026import java.io.FileInputStream;
027import java.io.IOException;
028import java.io.InputStream;
029import java.net.URL;
030import java.net.URLConnection;
031import java.util.*;
032
033import static ch.qos.logback.core.CoreConstants.DOT;
034
035public class PropertyConfigurator extends ContextAwareBase {
036
037    static Comparator<String> LENGTH_COMPARATOR = new Comparator<String>() {
038        @Override
039        public int compare(String o1, String o2) {
040            int len1 = o1 == null ? 0 : o1.length();
041            int len2 = o2 == null ? 0 : o2.length();
042            // longer strings first
043            return len2 - len1;
044        }
045    };
046
047    static final String LOGBACK_PREFIX = "logback";
048    static final String LOGBACK_ROOT_LOGGER_PREFIX = LOGBACK_PREFIX + DOT + "root";
049    static final int LOGBACK_ROOT_LOGGER_PREFIX_LENGTH = LOGBACK_ROOT_LOGGER_PREFIX.length();
050
051    static final String LOGBACK_LOGGER_PREFIX = LOGBACK_PREFIX + DOT + "logger" + DOT;
052    static final int LOGBACK_LOGGER_PREFIX_LENGTH = LOGBACK_LOGGER_PREFIX.length();
053
054    VariableSubstitutionsHelper variableSubstitutionsHelper;
055
056    LoggerContext getLoggerContext() {
057        return (LoggerContext) getContext();
058    }
059
060    @Override
061    public void setContext(Context context) {
062        super.setContext(context);
063    }
064
065    public void doConfigure(URL url) throws JoranException {
066        try {
067            URLConnection urlConnection = url.openConnection();
068            // per http://jira.qos.ch/browse/LOGBACK-117
069            // per http://jira.qos.ch/browse/LOGBACK-163
070            urlConnection.setUseCaches(false);
071            InputStream in = urlConnection.getInputStream();
072            doConfigure(in);
073        } catch (IOException ioe) {
074            String errMsg = "Could not open URL [" + url + "].";
075            addError(errMsg, ioe);
076            throw new JoranException(errMsg, ioe);
077        }
078    }
079
080    public void doConfigure(File file) throws JoranException {
081        try(FileInputStream fileInputStream = new FileInputStream(file)) {
082            doConfigure(fileInputStream);
083        } catch (IOException e) {
084            throw new JoranException("Failed to load file "+file, e);
085        }
086    }
087
088    public void doConfigure(String filename) throws JoranException {
089        doConfigure(new File(filename));
090    }
091
092    public void doConfigure(InputStream inputStream) throws JoranException {
093        Properties props = new Properties();
094        try {
095            props.load(inputStream);
096        } catch (IOException e) {
097            throw new JoranException("Failed to load from input stream", e);
098        } finally {
099            close(inputStream);
100        }
101
102        doConfigure(props);
103    }
104
105    private void close(InputStream inputStream) throws JoranException {
106        if(inputStream != null) {
107            try {
108                inputStream.close();
109            } catch (IOException e) {
110                throw new JoranException("failed to close stream", e);
111            }
112        }
113    }
114
115    void doConfigure(Properties properties) {
116        Map<String, String> variablesMap = extractVariablesMap(properties);
117        Map<String, String> instructionMap = extractLogbackInstructionMap(properties);
118
119        this.variableSubstitutionsHelper = new VariableSubstitutionsHelper(context, variablesMap);
120        configureLoggers(instructionMap);
121        configureRootLogger(instructionMap);
122    }
123
124    void configureRootLogger(Map<String, String> instructionMap) {
125        String val = subst(instructionMap.get(LOGBACK_ROOT_LOGGER_PREFIX));
126        if (val != null) {
127            setLevel(org.slf4j.Logger.ROOT_LOGGER_NAME, val);
128        }
129    }
130
131    void configureLoggers(Map<String, String> instructionMap) {
132
133        for (String key : instructionMap.keySet()) {
134            if (key.startsWith(LOGBACK_LOGGER_PREFIX)) {
135                String loggerName = key.substring(LOGBACK_LOGGER_PREFIX_LENGTH);
136                String value = subst(instructionMap.get(key));
137                setLevel(loggerName, value);
138            }
139        }
140    }
141
142    private void setLevel(String loggerName, String val) {
143        Logger logger = getLoggerContext().getLogger(loggerName);
144        Level level = Level.toLevel(val);
145        logger.setLevel(level);
146    }
147
148    private Map<String, String> extractVariablesMap(Properties properties) {
149        Map<String, String> variablesMap = new HashMap<>();
150        for (String key : properties.stringPropertyNames()) {
151            if (key != null && !key.startsWith(LOGBACK_PREFIX)) {
152                variablesMap.put(key, properties.getProperty(key));
153            }
154        }
155
156        return variablesMap;
157    }
158
159    private Map<String, String> extractLogbackInstructionMap(Properties properties) {
160        Map<String, String> instructionMap = new TreeMap<>(LENGTH_COMPARATOR);
161        for (String key : properties.stringPropertyNames()) {
162            if (key != null && key.startsWith(LOGBACK_PREFIX)) {
163                instructionMap.put(key, properties.getProperty(key));
164            }
165        }
166        return instructionMap;
167    }
168
169    public String subst(String ref) {
170
171        String substituted = variableSubstitutionsHelper.subst(ref);
172        if (ref != null && !ref.equals(substituted)) {
173            addInfo("value \"" + substituted + "\" substituted for \"" + ref + "\"");
174        }
175        return substituted;
176    }
177
178}