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.ArrayList;
17  import java.util.Iterator;
18  import java.util.List;
19  import java.util.Map;
20  import java.util.Stack;
21  import java.util.Vector;
22  
23  import org.xml.sax.Attributes;
24  import org.xml.sax.Locator;
25  
26  import ch.qos.logback.core.Context;
27  import ch.qos.logback.core.joran.action.Action;
28  import ch.qos.logback.core.joran.action.ImplicitAction;
29  import ch.qos.logback.core.joran.event.BodyEvent;
30  import ch.qos.logback.core.joran.event.EndEvent;
31  import ch.qos.logback.core.joran.event.StartEvent;
32  import ch.qos.logback.core.spi.ContextAwareImpl;
33  
34  /**
35   * {@code Interpreter} is Joran's main driving class. It extends SAX
36   * {@link org.xml.sax.helpers.DefaultHandler DefaultHandler} which invokes
37   * various {@link Action actions} according to predefined patterns.
38   * 
39   * <p>
40   * Patterns are kept in a {@link RuleStore} which is programmed to store and
41   * then later produce the applicable actions for a given pattern.
42   * 
43   * <p>
44   * The pattern corresponding to a top level &lt;a&gt; element is the string
45   * "a".
46   * 
47   * <p>
48   * The pattern corresponding to an element &lt;b&gt; embedded within a top level
49   * &lt;a&gt; element is the string {@code "a/b"}.
50   * 
51   * <p>
52   * The pattern corresponding to an &lt;b&gt; and any level of nesting is
53   * "&#42;/b. Thus, the &#42; character placed at the beginning of a pattern
54   * serves as a wildcard for the level of nesting.
55   * 
56   * Conceptually, this is very similar to the API of commons-digester. Joran
57   * offers several small advantages. First and foremost, it offers support for
58   * implicit actions which result in a significant leap in flexibility. Second,
59   * in our opinion better error reporting capability. Third, it is self-reliant.
60   * It does not depend on other APIs, in particular commons-logging which is too
61   * unreliable. Last but not least, Joran is quite tiny and is expected to remain
62   * so.
63   * 
64   * @author Ceki G&uuml;lc&uuml;
65   * 
66   */
67  public class Interpreter {
68      private static List<Action> EMPTY_LIST = new Vector<Action>(0);
69  
70      final private RuleStore ruleStore;
71      final private InterpretationContext interpretationContext;
72      final private ArrayList<ImplicitAction> implicitActions;
73      final private CAI_WithLocatorSupport cai;
74      private ElementPath elementPath;
75      Locator locator;
76      EventPlayer eventPlayer;
77  
78      /**
79       * The <id>actionListStack</id> contains a list of actions that are executing
80       * for the given XML element.
81       * 
82       * A list of actions is pushed by the {link #startElement} and popped by
83       * {@link #endElement}.
84       * 
85       */
86      Stack<List<Action>> actionListStack;
87  
88      /**
89       * If the skip nested is set, then we skip all its nested elements until it is
90       * set back to null at when the element's end is reached.
91       */
92      ElementPath skip = null;
93  
94      public Interpreter(Context context, RuleStore rs, ElementPath initialElementPath) {
95          this.cai = new CAI_WithLocatorSupport(context, this);
96          ruleStore = rs;
97          interpretationContext = new InterpretationContext(context, this);
98          implicitActions = new ArrayList<ImplicitAction>(3);
99          this.elementPath = initialElementPath;
100         actionListStack = new Stack<List<Action>>();
101         eventPlayer = new EventPlayer(this);
102     }
103 
104     public EventPlayer getEventPlayer() {
105         return eventPlayer;
106     }
107 
108     public void setInterpretationContextPropertiesMap(Map<String, String> propertiesMap) {
109         interpretationContext.setPropertiesMap(propertiesMap);
110     }
111 
112     /**
113      * @deprecated replaced by {@link #getInterpretationContext()}
114      */
115     public InterpretationContext getExecutionContext() {
116         return getInterpretationContext();
117     }
118 
119     public InterpretationContext getInterpretationContext() {
120         return interpretationContext;
121     }
122 
123     public void startDocument() {
124     }
125 
126     public void startElement(StartEvent se) {
127         setDocumentLocator(se.getLocator());
128         startElement(se.namespaceURI, se.localName, se.qName, se.attributes);
129     }
130 
131     private void startElement(String namespaceURI, String localName, String qName, Attributes atts) {
132 
133         String tagName = getTagName(localName, qName);
134         elementPath.push(tagName);
135 
136         if (skip != null) {
137             // every startElement pushes an action list
138             pushEmptyActionList();
139             return;
140         }
141 
142         List<Action> applicableActionList = getApplicableActionList(elementPath, atts);
143         if (applicableActionList != null) {
144             actionListStack.add(applicableActionList);
145             callBeginAction(applicableActionList, tagName, atts);
146         } else {
147             // every startElement pushes an action list
148             pushEmptyActionList();
149             String errMsg = "no applicable action for [" + tagName + "], current ElementPath  is [" + elementPath + "]";
150             cai.addError(errMsg);
151         }
152     }
153 
154     /**
155      * This method is used to
156      */
157     private void pushEmptyActionList() {
158         actionListStack.add(EMPTY_LIST);
159     }
160 
161     public void characters(BodyEvent be) {
162 
163         setDocumentLocator(be.locator);
164 
165         String body = be.getText();
166         List<Action> applicableActionList = actionListStack.peek();
167 
168         if (body != null) {
169             body = body.trim();
170             if (body.length() > 0) {
171                 // System.out.println("calling body method with ["+body+ "]");
172                 callBodyAction(applicableActionList, body);
173             }
174         }
175     }
176 
177     public void endElement(EndEvent endEvent) {
178         setDocumentLocator(endEvent.locator);
179         endElement(endEvent.namespaceURI, endEvent.localName, endEvent.qName);
180     }
181 
182     private void endElement(String namespaceURI, String localName, String qName) {
183         // given that an action list is always pushed for every startElement, we
184         // need
185         // to always pop for every endElement
186         List<Action> applicableActionList = (List<Action>) actionListStack.pop();
187 
188         if (skip != null) {
189             if (skip.equals(elementPath)) {
190                 skip = null;
191             }
192         } else if (applicableActionList != EMPTY_LIST) {
193             callEndAction(applicableActionList, getTagName(localName, qName));
194         }
195 
196         // given that we always push, we must also pop the pattern
197         elementPath.pop();
198     }
199 
200     public Locator getLocator() {
201         return locator;
202     }
203 
204     public void setDocumentLocator(Locator l) {
205         locator = l;
206     }
207 
208     String getTagName(String localName, String qName) {
209         String tagName = localName;
210 
211         if ((tagName == null) || (tagName.length() < 1)) {
212             tagName = qName;
213         }
214 
215         return tagName;
216     }
217 
218     public void addImplicitAction(ImplicitAction ia) {
219         implicitActions.add(ia);
220     }
221 
222     /**
223      * Check if any implicit actions are applicable. As soon as an applicable
224      * action is found, it is returned. Thus, the returned list will have at most
225      * one element.
226      */
227     List<Action> lookupImplicitAction(ElementPath elementPath, Attributes attributes, InterpretationContext ec) {
228         int len = implicitActions.size();
229 
230         for (int i = 0; i < len; i++) {
231             ImplicitAction ia = (ImplicitAction) implicitActions.get(i);
232 
233             if (ia.isApplicable(elementPath, attributes, ec)) {
234                 List<Action> actionList = new ArrayList<Action>(1);
235                 actionList.add(ia);
236 
237                 return actionList;
238             }
239         }
240 
241         return null;
242     }
243 
244     /**
245      * Return the list of applicable patterns for this
246      */
247     List<Action> getApplicableActionList(ElementPath elementPath, Attributes attributes) {
248         List<Action> applicableActionList = ruleStore.matchActions(elementPath);
249 
250         // logger.debug("set of applicable patterns: " + applicableActionList);
251         if (applicableActionList == null) {
252             applicableActionList = lookupImplicitAction(elementPath, attributes, interpretationContext);
253         }
254 
255         return applicableActionList;
256     }
257 
258     void callBeginAction(List<Action> applicableActionList, String tagName, Attributes atts) {
259         if (applicableActionList == null) {
260             return;
261         }
262 
263         Iterator<Action> i = applicableActionList.iterator();
264         while (i.hasNext()) {
265             Action action = (Action) i.next();
266             // now let us invoke the action. We catch and report any eventual
267             // exceptions
268             try {
269                 action.begin(interpretationContext, tagName, atts);
270             } catch (ActionException e) {
271                 skip = elementPath.duplicate();
272                 cai.addError("ActionException in Action for tag [" + tagName + "]", e);
273             } catch (RuntimeException e) {
274                 skip = elementPath.duplicate();
275                 cai.addError("RuntimeException in Action for tag [" + tagName + "]", e);
276             }
277         }
278     }
279 
280     private void callBodyAction(List<Action> applicableActionList, String body) {
281         if (applicableActionList == null) {
282             return;
283         }
284         Iterator<Action> i = applicableActionList.iterator();
285 
286         while (i.hasNext()) {
287             Action action = i.next();
288             try {
289                 action.body(interpretationContext, body);
290             } catch (ActionException ae) {
291                 cai.addError("Exception in end() methd for action [" + action + "]", ae);
292             }
293         }
294     }
295 
296     private void callEndAction(List<Action> applicableActionList, String tagName) {
297         if (applicableActionList == null) {
298             return;
299         }
300 
301         // logger.debug("About to call end actions on node: [" + localName + "]");
302         Iterator<Action> i = applicableActionList.iterator();
303 
304         while (i.hasNext()) {
305             Action action = i.next();
306             // now let us invoke the end method of the action. We catch and report
307             // any eventual exceptions
308             try {
309                 action.end(interpretationContext, tagName);
310             } catch (ActionException ae) {
311                 // at this point endAction, there is no point in skipping children as
312                 // they have been already processed
313                 cai.addError("ActionException in Action for tag [" + tagName + "]", ae);
314             } catch (RuntimeException e) {
315                 // no point in setting skip
316                 cai.addError("RuntimeException in Action for tag [" + tagName + "]", e);
317             }
318         }
319     }
320 
321     public RuleStore getRuleStore() {
322         return ruleStore;
323     }
324 }
325 
326 /**
327  * When {@link Interpreter} class is used as the origin of an
328  * {@link ContextAwareImpl} instance, then XML locator information is lost. This
329  * class preserves locator information (as a string).
330  * 
331  * @author ceki
332  */
333 class CAI_WithLocatorSupport extends ContextAwareImpl {
334 
335     CAI_WithLocatorSupport(Context context, Interpreter interpreter) {
336         super(context, interpreter);
337     }
338 
339     @Override
340     protected Object getOrigin() {
341         Interpreter i = (Interpreter) super.getOrigin();
342         Locator locator = i.locator;
343         if (locator != null) {
344             return Interpreter.class.getName() + "@" + locator.getLineNumber() + ":" + locator.getColumnNumber();
345         } else {
346             return Interpreter.class.getName() + "@NA:NA";
347         }
348     }
349 }