1 package ch.qos.logback.classic.joran;
2
3 import java.io.File;
4 import java.net.URL;
5 import java.util.ArrayList;
6 import java.util.List;
7
8 import ch.qos.logback.classic.LoggerContext;
9 import ch.qos.logback.classic.gaffer.GafferUtil;
10 import ch.qos.logback.classic.util.EnvUtil;
11 import ch.qos.logback.core.CoreConstants;
12 import ch.qos.logback.core.joran.event.SaxEvent;
13 import ch.qos.logback.core.joran.spi.ConfigurationWatchList;
14 import ch.qos.logback.core.joran.spi.JoranException;
15 import ch.qos.logback.core.joran.util.ConfigurationWatchListUtil;
16 import ch.qos.logback.core.spi.ContextAwareBase;
17 import ch.qos.logback.core.status.StatusUtil;
18
19 public class ReconfigureOnChangeTask extends ContextAwareBase implements Runnable {
20
21     public static final String DETECTED_CHANGE_IN_CONFIGURATION_FILES = "Detected change in configuration files.";
22     static final String RE_REGISTERING_PREVIOUS_SAFE_CONFIGURATION = "Re-registering previous fallback configuration once more as a fallback configuration point";
23     static final String FALLING_BACK_TO_SAFE_CONFIGURATION = "Given previous errors, falling back to previously registered safe configuration.";
24
25     
26     
27     long birthdate = System.currentTimeMillis();
28     List<ReconfigureOnChangeTaskListener> listeners;
29     
30     
31     void addListener(ReconfigureOnChangeTaskListener listener) {
32         if(listeners==null)
33             listeners = new ArrayList<ReconfigureOnChangeTaskListener>();
34         listeners.add(listener);
35     }
36     
37     @Override
38     public void run() {
39         fireEnteredRunMethod();
40         
41         ConfigurationWatchList configurationWatchList = ConfigurationWatchListUtil.getConfigurationWatchList(context);
42         if (configurationWatchList == null) {
43             addWarn("Empty ConfigurationWatchList in context");
44             return;
45         }
46
47         List<File> filesToWatch = configurationWatchList.getCopyOfFileWatchList();
48         if (filesToWatch == null || filesToWatch.isEmpty()) {
49             addInfo("Empty watch file list. Disabling ");
50             return;
51         }
52
53         if (!configurationWatchList.changeDetected()) {
54             return;
55         }
56
57         fireChangeDetected();
58         URL mainConfigurationURL = configurationWatchList.getMainURL();
59
60         addInfo(DETECTED_CHANGE_IN_CONFIGURATION_FILES);
61         addInfo(CoreConstants.RESET_MSG_PREFIX + "named [" + context.getName() + "]");
62
63         LoggerContext lc = (LoggerContext) context;
64         if (mainConfigurationURL.toString().endsWith("xml")) {
65             performXMLConfiguration(lc, mainConfigurationURL);
66         } else if (mainConfigurationURL.toString().endsWith("groovy")) {
67             if (EnvUtil.isGroovyAvailable()) {
68                 lc.reset();
69                 // avoid directly referring to GafferConfigurator so as to avoid
70                 // loading groovy.lang.GroovyObject . See also http://jira.qos.ch/browse/LBCLASSIC-214
71                 GafferUtil.runGafferConfiguratorOn(lc, this, mainConfigurationURL);
72             } else {
73                 addError("Groovy classes are not available on the class path. ABORTING INITIALIZATION.");
74             }
75         }
76         fireDoneReconfiguring();
77     }
78
79     private void fireEnteredRunMethod() {
80         if(listeners == null)
81             return;
82         
83         for(ReconfigureOnChangeTaskListener listener: listeners)
84             listener.enteredRunMethod();
85     }
86
87     private void fireChangeDetected() {
88         if(listeners == null)
89             return;
90         
91         for(ReconfigureOnChangeTaskListener listener: listeners)
92             listener.changeDetected();
93     }
94
95
96     private void fireDoneReconfiguring() {
97         if(listeners == null)
98             return;
99         
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         List<SaxEvent> eventList = jc.recallSafeConfiguration();
109
110         URL mainURL = ConfigurationWatchListUtil.getMainWatchURL(context);
111         lc.reset();
112         long threshold = System.currentTimeMillis();
113         try {
114             jc.doConfigure(mainConfigurationURL);
115             if (statusUtil.hasXMLParsingErrors(threshold)) {
116                 fallbackConfiguration(lc, eventList, mainURL);
117             }
118         } catch (JoranException e) {
119             fallbackConfiguration(lc, eventList, mainURL);
120         }
121     }
122
123     private List<SaxEvent> removeIncludeEvents(List<SaxEvent> unsanitizedEventList) {
124         List<SaxEvent> sanitizedEvents = new ArrayList<SaxEvent>();
125         if (unsanitizedEventList == null)
126             return sanitizedEvents;
127
128         for (SaxEvent e : unsanitizedEventList) {
129             if (!"include".equalsIgnoreCase(e.getLocalName()))
130                 sanitizedEvents.add(e);
131
132         }
133         return sanitizedEvents;
134     }
135
136     private void fallbackConfiguration(LoggerContext lc, List<SaxEvent> eventList, URL mainURL) {
137         // failsafe events are used only in case of errors. Therefore, we must *not*
138         // invoke file inclusion since the included files may be the cause of the error.
139
140         List<SaxEvent> failsafeEvents = removeIncludeEvents(eventList);
141         JoranConfigurator joranConfigurator = new JoranConfigurator();
142         joranConfigurator.setContext(context);
143         ConfigurationWatchList oldCWL = ConfigurationWatchListUtil.getConfigurationWatchList(context);
144         ConfigurationWatchList newCWL = oldCWL.buildClone();
145         
146         if (failsafeEvents == null || failsafeEvents.isEmpty()) {
147             addWarn("No previous configuration to fall back on.");
148         } else {
149             addWarn(FALLING_BACK_TO_SAFE_CONFIGURATION);
150             try {
151                 lc.reset();
152                 ConfigurationWatchListUtil.registerConfigurationWatchList(context, newCWL);
153                 joranConfigurator.doConfigure(failsafeEvents);
154                 addInfo(RE_REGISTERING_PREVIOUS_SAFE_CONFIGURATION);
155                 joranConfigurator.registerSafeConfiguration(eventList);
156                 
157                 addInfo("after registerSafeConfiguration: " + eventList);
158             } catch (JoranException e) {
159                 addError("Unexpected exception thrown by a configuration considered safe.", e);
160             }
161         }
162     }
163
164     @Override
165     public String toString() {
166         return "ReconfigureOnChangeTask(born:" + birthdate + ")";
167     }
168 }
169