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.List;
019import java.util.concurrent.ScheduledFuture;
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.ConfigurationEvent;
029import ch.qos.logback.core.spi.ContextAwareBase;
030import ch.qos.logback.core.status.StatusUtil;
031
032import static ch.qos.logback.core.spi.ConfigurationEvent.newConfigurationChangeDetectorRunningEvent;
033import static ch.qos.logback.core.spi.ConfigurationEvent.newConfigurationEndedEvent;
034
035public class ReconfigureOnChangeTask extends ContextAwareBase implements Runnable {
036
037    public static final String DETECTED_CHANGE_IN_CONFIGURATION_FILES = "Detected change in configuration files.";
038    static final String RE_REGISTERING_PREVIOUS_SAFE_CONFIGURATION = "Re-registering previous fallback configuration once more as a fallback configuration point";
039    static final String FALLING_BACK_TO_SAFE_CONFIGURATION = "Given previous errors, falling back to previously registered safe configuration.";
040
041    long birthdate = System.currentTimeMillis();
042    List<ReconfigureOnChangeTaskListener> listeners = null;
043
044    ScheduledFuture<?> scheduledFuture;
045
046    @Override
047    public void run() {
048        context.fireConfigurationEvent(newConfigurationChangeDetectorRunningEvent(this));
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        context.fireConfigurationEvent(ConfigurationEvent.newConfigurationChangeDetectedEvent(this));
066        cancelFutureInvocationsOfThisTaskInstance();
067
068        URL mainConfigurationURL = configurationWatchList.getMainURL();
069
070        addInfo(DETECTED_CHANGE_IN_CONFIGURATION_FILES);
071        addInfo(CoreConstants.RESET_MSG_PREFIX + "named [" + context.getName() + "]");
072
073        LoggerContext lc = (LoggerContext) context;
074        if (mainConfigurationURL.toString().endsWith("xml")) {
075            performXMLConfiguration(lc, mainConfigurationURL);
076        } else if (mainConfigurationURL.toString().endsWith("groovy")) {
077            addError("Groovy configuration disabled due to Java 9 compilation issues.");
078        }
079        //fireDoneReconfiguring();
080    }
081
082    private void cancelFutureInvocationsOfThisTaskInstance() {
083        boolean result = scheduledFuture.cancel(false);
084        if(!result) {
085            addWarn("could not cancel "+ this.toString());
086        }
087    }
088
089    private void performXMLConfiguration(LoggerContext lc, URL mainConfigurationURL) {
090        JoranConfigurator jc = new JoranConfigurator();
091        jc.setContext(context);
092        StatusUtil statusUtil = new StatusUtil(context);
093        Model failsafeTop = jc.recallSafeConfiguration();
094        URL mainURL = ConfigurationWatchListUtil.getMainWatchURL(context);
095        lc.reset();
096        long threshold = System.currentTimeMillis();
097        try {
098            jc.doConfigure(mainConfigurationURL);
099            // e.g. IncludeAction will add a status regarding XML parsing errors but no exception will reach here
100            if (statusUtil.hasXMLParsingErrors(threshold)) {
101                fallbackConfiguration(lc, failsafeTop, mainURL);
102            }
103        } catch (JoranException e) {
104            addWarn("Exception occurred during reconfiguration", e);
105            fallbackConfiguration(lc, failsafeTop, mainURL);
106        }
107    }
108
109    private void fallbackConfiguration(LoggerContext lc, Model failsafeTop, URL mainURL) {
110        // failsafe events are used only in case of errors. Therefore, we must *not*
111        // invoke file inclusion since the included files may be the cause of the error.
112
113        // List<SaxEvent> failsafeEvents = removeIncludeEvents(eventList);
114        JoranConfigurator joranConfigurator = new JoranConfigurator();
115        joranConfigurator.setContext(context);
116        ConfigurationWatchList oldCWL = ConfigurationWatchListUtil.getConfigurationWatchList(context);
117        ConfigurationWatchList newCWL = oldCWL.buildClone();
118
119        if (failsafeTop == null) {
120            addWarn("No previous configuration to fall back on.");
121            return;
122        } else {
123            addWarn(FALLING_BACK_TO_SAFE_CONFIGURATION);
124            addInfo("Safe model "+failsafeTop);
125            try {
126                lc.reset();
127                ConfigurationWatchListUtil.registerConfigurationWatchList(context, newCWL);
128                ModelUtil.resetForReuse(failsafeTop);
129                joranConfigurator.processModel(failsafeTop);
130                addInfo(RE_REGISTERING_PREVIOUS_SAFE_CONFIGURATION);
131                joranConfigurator.registerSafeConfiguration(failsafeTop);
132                context.fireConfigurationEvent(newConfigurationEndedEvent(this));
133                addInfo("after registerSafeConfiguration");
134            } catch (Exception e) {
135                addError("Unexpected exception thrown by a configuration considered safe.", e);
136            }
137        }
138    }
139
140    @Override
141    public String toString() {
142        return "ReconfigureOnChangeTask(born:" + birthdate + ")";
143    }
144
145
146    public void setScheduredFuture(ScheduledFuture<?> aScheduledFuture) {
147        this.scheduledFuture = aScheduledFuture;
148    }
149}