1   /**
2    * Logback: the reliable, generic, fast and flexible logging framework. Copyright (C) 1999-2015, QOS.ch. All rights
3    * reserved.
4    *
5    * This program and the accompanying materials are dual-licensed under either the terms of the Eclipse Public License
6    * v1.0 as published by the Eclipse Foundation
7    *
8    * or (per the licensee's choosing)
9    *
10   * under the terms of the GNU Lesser General Public License version 2.1 as published by the Free Software Foundation.
11   */
12  package ch.qos.logback.core.joran;
13  
14  import static ch.qos.logback.core.CoreConstants.SAFE_JORAN_CONFIGURATION;
15  import static ch.qos.logback.core.spi.ConfigurationEvent.*;
16  
17  import java.io.File;
18  import java.io.FileInputStream;
19  import java.io.IOException;
20  import java.io.InputStream;
21  import java.net.URL;
22  import java.net.URLConnection;
23  import java.util.List;
24  import java.util.concurrent.locks.ReentrantLock;
25  
26  import org.xml.sax.InputSource;
27  
28  import ch.qos.logback.core.Context;
29  import ch.qos.logback.core.joran.event.SaxEvent;
30  import ch.qos.logback.core.joran.event.SaxEventRecorder;
31  import ch.qos.logback.core.joran.spi.DefaultNestedComponentRegistry;
32  import ch.qos.logback.core.joran.spi.ElementPath;
33  import ch.qos.logback.core.joran.spi.SaxEventInterpretationContext;
34  import ch.qos.logback.core.joran.spi.JoranException;
35  import ch.qos.logback.core.joran.spi.RuleStore;
36  import ch.qos.logback.core.joran.spi.SaxEventInterpreter;
37  import ch.qos.logback.core.joran.spi.SimpleRuleStore;
38  import ch.qos.logback.core.joran.util.ConfigurationWatchListUtil;
39  import ch.qos.logback.core.model.Model;
40  import ch.qos.logback.core.model.processor.DefaultProcessor;
41  import ch.qos.logback.core.model.processor.ModelInterpretationContext;
42  import ch.qos.logback.core.spi.ContextAwareBase;
43  import ch.qos.logback.core.spi.ErrorCodes;
44  import ch.qos.logback.core.status.StatusUtil;
45  
46  public abstract class GenericXMLConfigurator extends ContextAwareBase {
47  
48      protected SaxEventInterpreter saxEventInterpreter;
49      protected ModelInterpretationContext modelInterpretationContext;
50  
51      public ModelInterpretationContext getModelInterpretationContext() {
52          return this.modelInterpretationContext;
53      }
54      private RuleStore ruleStore;
55  
56      public final void doConfigure(URL url) throws JoranException {
57          InputStream in = null;
58          try {
59              informContextOfURLUsedForConfiguration(getContext(), url);
60              URLConnection urlConnection = url.openConnection();
61              // per http://jira.qos.ch/browse/LOGBACK-117  LBCORE-105
62              // per http://jira.qos.ch/browse/LOGBACK-163  LBCORE-127
63              urlConnection.setUseCaches(false);
64  
65              in = urlConnection.getInputStream();
66              doConfigure(in, url.toExternalForm());
67          } catch (IOException ioe) {
68              String errMsg = "Could not open URL [" + url + "].";
69              addError(errMsg, ioe);
70              throw new JoranException(errMsg, ioe);
71          } finally {
72              if (in != null) {
73                  try {
74                      in.close();
75                  } catch (IOException ioe) {
76                      String errMsg = "Could not close input stream";
77                      addError(errMsg, ioe);
78                      throw new JoranException(errMsg, ioe);
79                  }
80              }
81          }
82      }
83  
84      public final void doConfigure(String filename) throws JoranException {
85          doConfigure(new File(filename));
86      }
87  
88      public final void doConfigure(File file) throws JoranException {
89          FileInputStream fis = null;
90          try {
91              URL url = file.toURI().toURL();
92              informContextOfURLUsedForConfiguration(getContext(), url);
93              fis = new FileInputStream(file);
94              doConfigure(fis, url.toExternalForm());
95          } catch (IOException ioe) {
96              String errMsg = "Could not open [" + file.getPath() + "].";
97              addError(errMsg, ioe);
98              throw new JoranException(errMsg, ioe);
99          } finally {
100             if (fis != null) {
101                 try {
102                     fis.close();
103                 } catch (java.io.IOException ioe) {
104                     String errMsg = "Could not close [" + file.getName() + "].";
105                     addError(errMsg, ioe);
106                     throw new JoranException(errMsg, ioe);
107                 }
108             }
109         }
110     }
111 
112     public static void informContextOfURLUsedForConfiguration(Context context, URL url) {
113         ConfigurationWatchListUtil.setMainWatchURL(context, url);
114     }
115 
116     public final void doConfigure(InputStream inputStream) throws JoranException {
117         doConfigure(new InputSource(inputStream));
118     }
119 
120     public final void doConfigure(InputStream inputStream, String systemId) throws JoranException {
121         InputSource inputSource = new InputSource(inputStream);
122         inputSource.setSystemId(systemId);
123         doConfigure(inputSource);
124     }
125 
126     protected abstract void addElementSelectorAndActionAssociations(RuleStore rs);
127 
128     protected abstract void setImplicitRuleSupplier(SaxEventInterpreter interpreter);
129 
130     protected void addDefaultNestedComponentRegistryRules(DefaultNestedComponentRegistry registry) {
131         // nothing by default
132     }
133 
134     protected ElementPath initialElementPath() {
135         return new ElementPath();
136     }
137 
138     protected void buildSaxEventInterpreter(List<SaxEvent> saxEvents) {
139         RuleStore rs = getRuleStore();
140         addElementSelectorAndActionAssociations(rs);
141         this.saxEventInterpreter = new SaxEventInterpreter(context, rs, initialElementPath(), saxEvents);
142         SaxEventInterpretationContext interpretationContext = saxEventInterpreter.getSaxEventInterpretationContext();
143         interpretationContext.setContext(context);
144         setImplicitRuleSupplier(saxEventInterpreter);
145     }
146 
147     public RuleStore getRuleStore() {
148         if(this.ruleStore == null) {
149             this.ruleStore = new SimpleRuleStore(context);
150         }
151         return this.ruleStore;
152     }
153 
154     protected void buildModelInterpretationContext() {
155         this.modelInterpretationContext = new ModelInterpretationContext(context);
156         addDefaultNestedComponentRegistryRules(modelInterpretationContext.getDefaultNestedComponentRegistry());
157     }
158 
159     // this is the most inner form of doConfigure whereto other doConfigure
160     // methods ultimately delegate
161     public final void doConfigure(final InputSource inputSource) throws JoranException {
162 
163         context.fireConfigurationEvent(newConfigurationStartedEvent(this));
164         long threshold = System.currentTimeMillis();
165 
166         SaxEventRecorder recorder = populateSaxEventRecorder(inputSource);
167         List<SaxEvent> saxEvents = recorder.getSaxEventList();
168         if (saxEvents.isEmpty()) {
169             addWarn("Empty sax event list");
170             return;
171         }
172         Model top = buildModelFromSaxEventList(recorder.getSaxEventList());
173         if (top == null) {
174             addError(ErrorCodes.EMPTY_MODEL_STACK);
175             return;
176         }
177         sanityCheck(top);
178         processModel(top);
179 
180         // no exceptions at this level
181         StatusUtil statusUtil = new StatusUtil(context);
182         if (statusUtil.noXMLParsingErrorsOccurred(threshold)) {
183             addInfo("Registering current configuration as safe fallback point");
184             registerSafeConfiguration(top);
185             context.fireConfigurationEvent(newConfigurationEndedSuccessfullyEvent(this));
186         } else {
187             context.fireConfigurationEvent(newConfigurationEndedWithXMLParsingErrorsEvent(this));
188         }
189 
190 
191     }
192 
193     public SaxEventRecorder populateSaxEventRecorder(final InputSource inputSource) throws JoranException {
194         SaxEventRecorder recorder = new SaxEventRecorder(context);
195         recorder.recordEvents(inputSource);
196         return recorder;
197     }
198 
199     public Model buildModelFromSaxEventList(List<SaxEvent> saxEvents) throws JoranException {
200         buildSaxEventInterpreter(saxEvents);
201         playSaxEvents();
202         Model top = saxEventInterpreter.getSaxEventInterpretationContext().peekModel();
203         return top;
204     }
205 
206     private void playSaxEvents() throws JoranException {
207         saxEventInterpreter.getEventPlayer().play();
208     }
209 
210     public void processModel(Model model) {
211         buildModelInterpretationContext();
212         this.modelInterpretationContext.setTopModel(model);
213         modelInterpretationContext.setConfiguratorHint(this);
214         DefaultProcessor defaultProcessor = new DefaultProcessor(context, this.modelInterpretationContext);
215         addModelHandlerAssociations(defaultProcessor);
216 
217         // disallow simultaneous configurations of the same context
218         ReentrantLock configurationLock   = context.getConfigurationLock();
219 
220         try {
221             configurationLock.lock();
222             defaultProcessor.process(model);
223         } finally {
224             configurationLock.unlock();
225         }
226     }
227 
228     /**
229      * Perform sanity check and issue warning if necessary.
230      *
231      * Default implementation does nothing.
232      *
233      * @param topModel
234      * @since 1.3.2 and 1.4.2
235      */
236     protected void sanityCheck(Model topModel) {
237 
238     }
239 
240     protected void addModelHandlerAssociations(DefaultProcessor defaultProcessor) {
241     }
242 
243     /**
244      * Register the current event list in currently in the interpreter as a safe
245      * configuration point.
246      *
247      * @since 0.9.30
248      */
249     public void registerSafeConfiguration(Model top) {
250         context.putObject(SAFE_JORAN_CONFIGURATION, top);
251     }
252 
253     /**
254      * Recall the event list previously registered as a safe point.
255      */
256     public Model recallSafeConfiguration() {
257         return (Model) context.getObject(SAFE_JORAN_CONFIGURATION);
258     }
259 }