001/*
002 * Logback: the reliable, generic, fast and flexible logging framework.
003 * Copyright (C) 1999-2026, 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 v2.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.CoreConstants.PROPERTIES_FILE_EXTENSION;
033import static ch.qos.logback.core.spi.ConfigurationEvent.*;
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    public static final String RE_REGISTERING_PREVIOUS_SAFE_CONFIGURATION = "Re-registering previous fallback configuration once more as a fallback configuration point";
039    public 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        if (configurationWatchList.emptyWatchLists()) {
057            addInfo("Both watch lists are empty. Disabling ");
058            return;
059        }
060
061        File changedFile = configurationWatchList.changeDetectedInFile();
062        URL changedURL = configurationWatchList.changeDetectedInURL();
063
064        if (changedFile == null && changedURL == null) {
065            return;
066        }
067
068        context.fireConfigurationEvent(ConfigurationEvent.newConfigurationChangeDetectedEvent(this));
069        addInfo(DETECTED_CHANGE_IN_CONFIGURATION_FILES);
070        if(changedFile != null) {
071            changeInFile(changedFile, configurationWatchList);
072        }
073
074        if(changedURL != null) {
075            changeInURL(changedURL);
076        }
077    }
078
079    private void changeInURL(URL url) {
080        String path = url.getPath();
081        if(path.endsWith(PROPERTIES_FILE_EXTENSION)) {
082            runPropertiesConfigurator(url);
083        }
084    }
085    private void changeInFile(File changedFile, ConfigurationWatchList configurationWatchList) {
086
087        if(changedFile.getName().endsWith(PROPERTIES_FILE_EXTENSION)) {
088            runPropertiesConfigurator(changedFile);
089            return;
090        }
091
092        // ======== fuller processing below
093        addInfo(CoreConstants.RESET_MSG_PREFIX + "named [" + context.getName() + "]");
094        cancelFutureInvocationsOfThisTaskInstance();
095        URL mainConfigurationURL = configurationWatchList.getTopURL();
096
097        LoggerContext lc = (LoggerContext) context;
098        if (mainConfigurationURL.toString().endsWith("xml")) {
099            performXMLConfiguration(lc, mainConfigurationURL);
100        }
101    }
102
103    private void runPropertiesConfigurator(Object changedObject) {
104        addInfo("Will run PropertyConfigurator on "+changedObject);
105        PropertiesConfigurator propertiesConfigurator = new PropertiesConfigurator();
106        propertiesConfigurator.setContext(context);
107        try {
108            if(changedObject instanceof File) {
109                File changedFile = (File) changedObject;
110                propertiesConfigurator.doConfigure(changedFile);
111            } else if(changedObject instanceof URL) {
112                URL changedURL = (URL) changedObject;
113                propertiesConfigurator.doConfigure(changedURL);
114            }
115            context.fireConfigurationEvent(newPartialConfigurationEndedSuccessfullyEvent(this));
116        } catch (JoranException e) {
117            addError("Failed to reload "+ changedObject);
118        }
119    }
120
121    private void cancelFutureInvocationsOfThisTaskInstance() {
122        boolean result = scheduledFuture.cancel(false);
123        if(!result) {
124            addWarn("could not cancel "+ this.toString());
125        }
126    }
127
128    private void performXMLConfiguration(LoggerContext loggerContext, URL mainConfigurationURL) {
129
130        JoranConfigurator jc = new JoranConfigurator();
131        jc.setContext(loggerContext);
132        StatusUtil statusUtil = new StatusUtil(loggerContext);
133        Model failsafeTop = jc.recallSafeConfiguration();
134        URL topURL = ConfigurationWatchListUtil.getMainWatchURL(context);
135        addInfo("Resetting loggerContext ["+loggerContext.getName()+"]");
136        loggerContext.reset();
137        long threshold = System.currentTimeMillis();
138        try {
139            jc.doConfigure(mainConfigurationURL);
140            // e.g. IncludeAction will add a status regarding XML parsing errors but no exception will reach here
141            if (statusUtil.hasXMLParsingErrors(threshold)) {
142                fallbackConfiguration(loggerContext, failsafeTop, topURL);
143            }
144        } catch (JoranException e) {
145            addWarn("Exception occurred during reconfiguration", e);
146            fallbackConfiguration(loggerContext, failsafeTop, topURL);
147        }
148    }
149
150    private void fallbackConfiguration(LoggerContext loggerContext, Model failsafeTopModel, URL topURL) {
151        // failsafe events are used only in case of errors. Therefore, we must *not*
152        // invoke file inclusion since the included files may be the cause of the error.
153
154        // List<SaxEvent> failsafeEvents = removeIncludeEvents(eventList);
155        JoranConfigurator joranConfigurator = new JoranConfigurator();
156        joranConfigurator.setContext(loggerContext);
157        joranConfigurator.setTopURL(topURL);
158
159//        ConfigurationWatchList oldCWL = ConfigurationWatchListUtil.getConfigurationWatchList(loggerContext);
160//        System.out.println("--------oldCWL:"+oldCWL);
161//        ConfigurationWatchList newCWL = oldCWL.buildClone();
162
163        if (failsafeTopModel == null) {
164            addWarn("No previous configuration to fall back on.");
165            return;
166        } else {
167            addWarn(FALLING_BACK_TO_SAFE_CONFIGURATION);
168            addInfo("Safe model "+failsafeTopModel);
169            try {
170                loggerContext.reset();
171//                ConfigurationWatchListUtil.registerConfigurationWatchList(context, newCWL);
172                ModelUtil.resetForReuse(failsafeTopModel);
173                joranConfigurator.processModel(failsafeTopModel);
174                addInfo(RE_REGISTERING_PREVIOUS_SAFE_CONFIGURATION);
175                joranConfigurator.registerSafeConfiguration(failsafeTopModel);
176                context.fireConfigurationEvent(newConfigurationEndedSuccessfullyEvent(this));
177            } catch (Exception e) {
178                addError("Unexpected exception thrown by a configuration considered safe.", e);
179            }
180        }
181    }
182
183    @Override
184    public String toString() {
185        return "ReconfigureOnChangeTask(born:" + birthdate + ")";
186    }
187
188    /**
189     * Contains typo. Replaced by {@link #setScheduledFuture(ScheduledFuture)}.
190     * @param aScheduledFuture
191     * @deprecated
192     */
193    @Deprecated
194    public void setScheduredFuture(ScheduledFuture<?> aScheduledFuture) {
195        setScheduledFuture(aScheduledFuture);
196    }
197
198    /**
199     * Replaces {@link #setScheduredFuture(ScheduledFuture)}
200     * @param aScheduledFuture
201     * @since 1.5.19
202     */
203    public void setScheduledFuture(ScheduledFuture<?> aScheduledFuture) {
204        this.scheduledFuture = aScheduledFuture;
205    }
206}