001/**
002 * Logback: the reliable, generic, fast and flexible logging framework.
003 * Copyright (C) 1999-2022, 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 */
014package ch.qos.logback.classic.joran;
015
016import java.io.File;
017import java.net.URL;
018import java.util.ArrayList;
019import java.util.List;
020
021import ch.qos.logback.classic.LoggerContext;
022import ch.qos.logback.core.CoreConstants;
023import ch.qos.logback.core.joran.spi.ConfigurationWatchList;
024import ch.qos.logback.core.joran.spi.JoranException;
025import ch.qos.logback.core.joran.util.ConfigurationWatchListUtil;
026import ch.qos.logback.core.model.Model;
027import ch.qos.logback.core.model.ModelUtil;
028import ch.qos.logback.core.spi.ContextAwareBase;
029import ch.qos.logback.core.status.StatusUtil;
030
031public class ReconfigureOnChangeTask extends ContextAwareBase implements Runnable {
032
033    public static final String DETECTED_CHANGE_IN_CONFIGURATION_FILES = "Detected change in configuration files.";
034    static final String RE_REGISTERING_PREVIOUS_SAFE_CONFIGURATION = "Re-registering previous fallback configuration once more as a fallback configuration point";
035    static final String FALLING_BACK_TO_SAFE_CONFIGURATION = "Given previous errors, falling back to previously registered safe configuration.";
036
037    long birthdate = System.currentTimeMillis();
038    List<ReconfigureOnChangeTaskListener> listeners = null;
039
040    void addListener(ReconfigureOnChangeTaskListener listener) {
041        if (listeners == null)
042            listeners = new ArrayList<ReconfigureOnChangeTaskListener>(1);
043        listeners.add(listener);
044    }
045
046    @Override
047    public void run() {
048        fireEnteredRunMethod();
049
050        ConfigurationWatchList configurationWatchList = ConfigurationWatchListUtil.getConfigurationWatchList(context);
051        if (configurationWatchList == null) {
052            addWarn("Empty ConfigurationWatchList in context");
053            return;
054        }
055
056        List<File> filesToWatch = configurationWatchList.getCopyOfFileWatchList();
057        if (filesToWatch == null || filesToWatch.isEmpty()) {
058            addInfo("Empty watch file list. Disabling ");
059            return;
060        }
061
062        if (!configurationWatchList.changeDetected()) {
063            return;
064        }
065        fireChangeDetected();
066        URL mainConfigurationURL = configurationWatchList.getMainURL();
067
068        addInfo(DETECTED_CHANGE_IN_CONFIGURATION_FILES);
069        addInfo(CoreConstants.RESET_MSG_PREFIX + "named [" + context.getName() + "]");
070
071        LoggerContext lc = (LoggerContext) context;
072        if (mainConfigurationURL.toString().endsWith("xml")) {
073            performXMLConfiguration(lc, mainConfigurationURL);
074        } else if (mainConfigurationURL.toString().endsWith("groovy")) {
075            addError("Groovy configuration disabled due to Java 9 compilation issues.");
076        }
077        fireDoneReconfiguring();
078    }
079
080    private void fireEnteredRunMethod() {
081        if (listeners == null)
082            return;
083
084        for (ReconfigureOnChangeTaskListener listener : listeners)
085            listener.enteredRunMethod();
086    }
087
088    private void fireChangeDetected() {
089        if (listeners == null)
090            return;
091
092        for (ReconfigureOnChangeTaskListener listener : listeners)
093            listener.changeDetected();
094    }
095
096    private void fireDoneReconfiguring() {
097        if (listeners == null)
098            return;
099
100        for (ReconfigureOnChangeTaskListener listener : listeners)
101            listener.doneReconfiguring();
102    }
103
104    private void performXMLConfiguration(LoggerContext lc, URL mainConfigurationURL) {
105        JoranConfigurator jc = new JoranConfigurator();
106        jc.setContext(context);
107        StatusUtil statusUtil = new StatusUtil(context);
108        Model failsafeTop = jc.recallSafeConfiguration();
109        URL mainURL = ConfigurationWatchListUtil.getMainWatchURL(context);
110        lc.reset();
111        long threshold = System.currentTimeMillis();
112        try {
113            jc.doConfigure(mainConfigurationURL);
114            if (statusUtil.hasXMLParsingErrors(threshold)) {
115                fallbackConfiguration(lc, failsafeTop, mainURL);
116            }
117        } catch (JoranException e) {
118            fallbackConfiguration(lc, failsafeTop, mainURL);
119        }
120    }
121
122//    private List<SaxEvent> removeIncludeEvents(List<SaxEvent> unsanitizedEventList) {
123//        List<SaxEvent> sanitizedEvents = new ArrayList<SaxEvent>();
124//        if (unsanitizedEventList == null)
125//            return sanitizedEvents;
126//
127//        for (SaxEvent e : unsanitizedEventList) {
128//            if (!"include".equalsIgnoreCase(e.getLocalName()))
129//                sanitizedEvents.add(e);
130//
131//        }
132//        return sanitizedEvents;
133//    }
134
135    private void fallbackConfiguration(LoggerContext lc, Model failsafeTop, URL mainURL) {
136        // failsafe events are used only in case of errors. Therefore, we must *not*
137        // invoke file inclusion since the included files may be the cause of the error.
138
139        // List<SaxEvent> failsafeEvents = removeIncludeEvents(eventList);
140        JoranConfigurator joranConfigurator = new JoranConfigurator();
141        joranConfigurator.setContext(context);
142        ConfigurationWatchList oldCWL = ConfigurationWatchListUtil.getConfigurationWatchList(context);
143        ConfigurationWatchList newCWL = oldCWL.buildClone();
144
145        if (failsafeTop == null) {
146            addWarn("No previous configuration to fall back on.");
147            return;
148        } else {
149            addWarn(FALLING_BACK_TO_SAFE_CONFIGURATION);
150            addInfo("Safe model "+failsafeTop);
151            try {
152                lc.reset();
153                ConfigurationWatchListUtil.registerConfigurationWatchList(context, newCWL);
154                ModelUtil.resetForReuse(failsafeTop);
155                joranConfigurator.processModel(failsafeTop);
156                addInfo(RE_REGISTERING_PREVIOUS_SAFE_CONFIGURATION);
157                joranConfigurator.registerSafeConfiguration(failsafeTop);
158
159                addInfo("after registerSafeConfiguration");
160            } catch (Exception e) {
161                addError("Unexpected exception thrown by a configuration considered safe.", e);
162            }
163        }
164    }
165
166    @Override
167    public String toString() {
168        return "ReconfigureOnChangeTask(born:" + birthdate + ")";
169    }
170}