View Javadoc
1   /**
2    * Logback: the reliable, generic, fast and flexible logging framework.
3    * Copyright (C) 1999-2015, QOS.ch. All rights reserved.
4    *
5    * This program and the accompanying materials are dual-licensed under
6    * either the terms of the Eclipse Public License v1.0 as published by
7    * the Eclipse Foundation
8    *
9    *   or (per the licensee's choosing)
10   *
11   * under the terms of the GNU Lesser General Public License version 2.1
12   * as published by the Free Software Foundation.
13   */
14  package ch.qos.logback.core.joran.spi;
15  
16  import java.util.List;
17  import java.util.Stack;
18  import java.util.function.Supplier;
19  
20  import org.xml.sax.Attributes;
21  import org.xml.sax.Locator;
22  
23  import ch.qos.logback.core.Context;
24  import ch.qos.logback.core.joran.action.Action;
25  import ch.qos.logback.core.joran.action.NOPAction;
26  import ch.qos.logback.core.joran.event.BodyEvent;
27  import ch.qos.logback.core.joran.event.EndEvent;
28  import ch.qos.logback.core.joran.event.SaxEvent;
29  import ch.qos.logback.core.joran.event.StartEvent;
30  import ch.qos.logback.core.spi.ContextAwareImpl;
31  
32  /**
33   * {@code SaxEventInterpreter} is Joran's driving class for handling "low-level"
34   * SAX events. It extends SAX {@link org.xml.sax.helpers.DefaultHandler
35   * DefaultHandler} which invokes various {@link Action actions} according to
36   * predefined patterns.
37   * 
38   * <p>
39   * Patterns are kept in a {@link RuleStore} which is programmed to store and
40   * then later produce the applicable actions for a given pattern.
41   * 
42   * <p>
43   * The pattern corresponding to a top level &lt;a&gt; element is the string "a".
44   * 
45   * <p>
46   * The pattern corresponding to an element &lt;b&gt; embedded within a top level
47   * &lt;a&gt; element is the string {@code "a/b"}.
48   * 
49   * <p>
50   * The pattern corresponding to an &lt;b&gt; and any level of nesting is
51   * "&#42;/b. Thus, the &#42; character placed at the beginning of a pattern
52   * serves as a wildcard for the level of nesting.
53   * 
54   * Conceptually, this is very similar to the API of commons-digester. Joran
55   * offers several small advantages. First and foremost, it offers support for
56   * implicit actions which result in a significant leap in flexibility. Second,
57   * in our opinion better error reporting capability. Third, it is self-reliant.
58   * It does not depend on other APIs, in particular commons-logging which is too
59   * unreliable. Last but not least, Joran is quite tiny and is expected to remain
60   * so.
61   * 
62   * @author Ceki G&uuml;lc&uuml;
63   * 
64   */
65  public class SaxEventInterpreter {
66      private static Action NOP_ACTION_SINGLETON = new NOPAction();
67  
68      final private RuleStore ruleStore;
69      final private SaxEventInterpretationContext interpretationContext;
70      private Supplier<Action> implicitActionSupplier;
71      final private CAI_WithLocatorSupport cai;
72      private ElementPath elementPath;
73      Locator locator;
74      EventPlayer eventPlayer;
75      Context context;
76      
77      /**
78       * The <id>actionStack</id> contain the action that is executing
79       * for the given XML element.
80       * 
81       * An action is pushed by the {link #startElement} and popped by
82       * {@link #endElement}.
83       * 
84       */
85      Stack<Action> actionStack;
86  
87      /**
88       * If the skip nested is set, then we skip all its nested elements until it is
89       * set back to null at when the element's end is reached.
90       */
91      ElementPath skip = null;
92  
93      public SaxEventInterpreter(Context context, RuleStore rs, ElementPath initialElementPath, List<SaxEvent> saxEvents) {
94          this.context = context;
95          this.cai = new CAI_WithLocatorSupport(context, this);
96          ruleStore = rs;
97          interpretationContext = new SaxEventInterpretationContext(context, this);
98          this.elementPath = initialElementPath;
99          actionStack = new Stack<>();
100         eventPlayer = new EventPlayer(this, saxEvents);
101     }
102 
103     public EventPlayer getEventPlayer() {
104         return eventPlayer;
105     }
106 
107     public ElementPath getCopyOfElementPath() {
108         return elementPath.duplicate();
109     }
110 
111     public SaxEventInterpretationContext getSaxEventInterpretationContext() {
112         return interpretationContext;
113     }
114 
115     public void startDocument() {
116     }
117 
118     public void startElement(StartEvent se) {
119         setDocumentLocator(se.getLocator());
120         startElement(se.namespaceURI, se.localName, se.qName, se.attributes);
121     }
122 
123     private void startElement(String namespaceURI, String localName, String qName, Attributes atts) {
124 
125         String tagName = getTagName(localName, qName);
126    
127         
128         elementPath.push(tagName);
129    
130         if (skip != null) {
131             // every startElement pushes an action list
132             pushEmptyActionOntoActionStack();
133             return;
134         }
135 
136         Action applicableAction = getApplicableAction(elementPath, atts);
137         if (applicableAction != null) {
138             actionStack.add(applicableAction);
139             callBeginAction(applicableAction, tagName, atts);
140         } else {
141             // every startElement pushes an action list
142             pushEmptyActionOntoActionStack();
143             String errMsg = "no applicable action for [" + tagName + "], current ElementPath  is [" + elementPath + "]";
144             cai.addError(errMsg);
145         }
146     }
147 
148     /**
149      * This method is used to
150      */
151     private void pushEmptyActionOntoActionStack() {
152        actionStack.push(NOP_ACTION_SINGLETON);       
153     }
154 
155     public void characters(BodyEvent be) {
156 
157         setDocumentLocator(be.locator);
158 
159         String body = be.getText();
160         Action applicableAction = actionStack.peek();
161 
162         if (body != null) {
163             body = body.trim();
164             if (body.length() > 0) {
165                 callBodyAction(applicableAction, body);
166             }
167         }
168     }
169 
170     public void endElement(EndEvent endEvent) {
171         setDocumentLocator(endEvent.locator);
172         endElement(endEvent.namespaceURI, endEvent.localName, endEvent.qName);
173     }
174 
175     private void endElement(String namespaceURI, String localName, String qName) {
176         // given that an action is always pushed for every startElement, we
177         // need to always pop for every endElement
178         Action applicableAction =  actionStack.pop();
179 
180         if (skip != null) {
181             if (skip.equals(elementPath)) {
182                 skip = null;
183             }
184         } else if (applicableAction != NOP_ACTION_SINGLETON) {
185             callEndAction(applicableAction, getTagName(localName, qName));
186         }
187 
188         // given that we always push, we must also pop the pattern
189         elementPath.pop();
190     }
191 
192     public Locator getLocator() {
193         return locator;
194     }
195 
196     // having the locator set as parsing progresses is quite ugly
197     public void setDocumentLocator(Locator l) {
198         locator = l;
199     }
200 
201     String getTagName(String localName, String qName) {
202         String tagName = localName;
203 
204         if ((tagName == null) || (tagName.length() < 1)) {
205             tagName = qName;
206         }
207 
208         return tagName;
209     }
210 
211     public void setImplicitActionSupplier(Supplier<Action> actionSupplier) {
212         this.implicitActionSupplier = actionSupplier;
213     }
214 
215     /**
216      * Return the list of applicable patterns for this
217      */
218     Action getApplicableAction(ElementPath elementPath, Attributes attributes) {
219         Supplier<Action> applicableActionSupplier = ruleStore.matchActions(elementPath);
220 
221         if (applicableActionSupplier != null) {
222             Action applicableAction = applicableActionSupplier.get();
223             applicableAction.setContext(context);
224             return applicableAction;
225         } else {
226             Action implicitAction = implicitActionSupplier.get();
227             implicitAction.setContext(context);
228             return implicitAction;
229         }
230     }
231 
232     void callBeginAction(Action applicableAction, String tagName, Attributes atts) {
233         if (applicableAction == null) {
234             return;
235         }
236 
237         // now let us invoke the action. We catch and report any eventual
238         // exceptions
239         try {
240              applicableAction.begin(interpretationContext, tagName, atts);
241         } catch (ActionException e) {
242             skip = elementPath.duplicate();
243             cai.addError("ActionException in Action for tag [" + tagName + "]", e);
244         } catch (RuntimeException e) {
245             skip = elementPath.duplicate();
246             cai.addError("RuntimeException in Action for tag [" + tagName + "]", e);
247         }
248         
249     }
250 
251     private void callBodyAction(Action applicableAction, String body) {
252         if (applicableAction == null) {
253             return;
254         }
255 
256         try {
257             applicableAction.body(interpretationContext, body);
258         } catch (ActionException ae) {
259             cai.addError("Exception in body() method for action [" + applicableAction + "]", ae);
260         }
261     }
262 
263     private void callEndAction(Action applicableAction, String tagName) {
264         if (applicableAction == null) {
265             return;
266         }
267 
268         try {
269             applicableAction.end(interpretationContext, tagName);
270         } catch (ActionException ae) {
271             // at this point endAction, there is no point in skipping children as
272             // they have been already processed
273             cai.addError("ActionException in Action for tag [" + tagName + "]", ae);
274         } catch (RuntimeException e) {
275             // no point in setting skip
276             cai.addError("RuntimeException in Action for tag [" + tagName + "]", e);
277         }
278     }
279 
280     public RuleStore getRuleStore() {
281         return ruleStore;
282     }
283 }
284 
285 /**
286  * When {@link SaxEventInterpreter} class is used as the origin of an
287  * {@link ContextAwareImpl} instance, then XML locator information is lost. This
288  * class preserves locator information (as a string).
289  * 
290  * @author ceki
291  */
292 class CAI_WithLocatorSupport extends ContextAwareImpl {
293 
294     CAI_WithLocatorSupport(Context context, SaxEventInterpreter interpreter) {
295         super(context, interpreter);
296     }
297 
298     @Override
299     protected Object getOrigin() {
300         SaxEventInterpreter i = (SaxEventInterpreter) super.getOrigin();
301         Locator locator = i.locator;
302         if (locator != null) {
303             return SaxEventInterpreter.class.getName() + "@" + locator.getLineNumber() + ":"
304                     + locator.getColumnNumber();
305         } else {
306             return SaxEventInterpreter.class.getName() + "@NA:NA";
307         }
308     }
309 }