View Javadoc

1   /**
2    * Logback: the reliable, generic, fast and flexible logging framework.
3    * Copyright (C) 1999-2011, 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   * <id>Interpreter</id> 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   * <id>"a"</id>.
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 <id>"a/b"</id>.
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;lcu&uuml;
65   * 
66   */
67  public class Interpreter {
68    private static List EMPTY_LIST = new Vector(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 Pattern pattern;
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> 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    Pattern skip = null;
93  
94    public Interpreter(Context context, RuleStore rs, Pattern initialPattern) {
95      this.cai = new CAI_WithLocatorSupport(this);
96      this.cai.setContext(context);
97      ruleStore = rs;
98      interpretationContext = new InterpretationContext(context, this);
99      implicitActions = new ArrayList<ImplicitAction>(3);
100     this.pattern = initialPattern;
101     actionListStack = new Stack<List>();
102     eventPlayer = new EventPlayer(this);
103   }
104 
105   public EventPlayer getEventPlayer() {
106     return eventPlayer;
107   }
108 
109   public void setInterpretationContextPropertiesMap(
110       Map<String, String> propertiesMap) {
111     interpretationContext.setPropertiesMap(propertiesMap);
112   }
113 
114   /**
115    * @deprecated replaced by {@link #getInterpretationContext()}
116    */
117   public InterpretationContext getExecutionContext() {
118     return getInterpretationContext();
119   }
120 
121   public InterpretationContext getInterpretationContext() {
122     return interpretationContext;
123   }
124 
125   public void startDocument() {
126   }
127 
128   public void startElement(StartEvent se) {
129     setDocumentLocator(se.getLocator());
130     startElement(se.namespaceURI, se.localName, se.qName, se.attributes);
131   }
132 
133   private void startElement(String namespaceURI, String localName,
134       String qName, Attributes atts) {
135 
136     String tagName = getTagName(localName, qName);
137     pattern.push(tagName);
138 
139     if (skip != null) {
140       // every startElement pushes an action list
141       pushEmptyActionList();
142       return;
143     }
144 
145     List applicableActionList = getApplicableActionList(pattern, atts);
146     if (applicableActionList != null) {
147       actionListStack.add(applicableActionList);
148       callBeginAction(applicableActionList, tagName, atts);
149     } else {
150       // every startElement pushes an action list
151       pushEmptyActionList();
152       String errMsg = "no applicable action for [" + tagName
153           + "], current pattern is [" + pattern + "]";
154       cai.addError(errMsg);
155     }
156   }
157 
158   /**
159    * This method is used to
160    */
161   private void pushEmptyActionList() {
162     actionListStack.add(EMPTY_LIST);
163   }
164 
165   public void characters(BodyEvent be) {
166 
167     setDocumentLocator(be.locator);
168 
169     String body = be.getText();
170     List applicableActionList = (List) actionListStack.peek();
171 
172     if (body != null) {
173       body = body.trim();
174     }
175     if (body.length() > 0) {
176       // System.out.println("calling body method with ["+body+ "]");
177       callBodyAction(applicableActionList, body);
178     }
179   }
180 
181   public void endElement(EndEvent endEvent) {
182     setDocumentLocator(endEvent.locator);
183     endElement(endEvent.namespaceURI, endEvent.localName, endEvent.qName);
184   }
185 
186   private void endElement(String namespaceURI, String localName, String qName) {
187     // given that an action list is always pushed for every startElement, we
188     // need
189     // to always pop for every endElement
190     List applicableActionList = (List) actionListStack.pop();
191 
192     if (skip != null) {
193       if (skip.equals(pattern)) {
194         skip = null;
195       }
196     } else if (applicableActionList != EMPTY_LIST) {
197       callEndAction(applicableActionList, getTagName(localName, qName));
198     }
199 
200     // given that we always push, we must also pop the pattern
201     pattern.pop();
202   }
203 
204   public Locator getLocator() {
205     return locator;
206   }
207 
208   public void setDocumentLocator(Locator l) {
209     locator = l;
210   }
211 
212   String getTagName(String localName, String qName) {
213     String tagName = localName;
214 
215     if ((tagName == null) || (tagName.length() < 1)) {
216       tagName = qName;
217     }
218 
219     return tagName;
220   }
221 
222   public void addImplicitAction(ImplicitAction ia) {
223     implicitActions.add(ia);
224   }
225 
226   /**
227    * Check if any implicit actions are applicable. As soon as an applicable
228    * action is found, it is returned. Thus, the returned list will have at most
229    * one element.
230    */
231   List lookupImplicitAction(Pattern pattern, Attributes attributes,
232       InterpretationContext ec) {
233     int len = implicitActions.size();
234 
235     for (int i = 0; i < len; i++) {
236       ImplicitAction ia = (ImplicitAction) implicitActions.get(i);
237 
238       if (ia.isApplicable(pattern, attributes, ec)) {
239         List<Action> actionList = new ArrayList<Action>(1);
240         actionList.add(ia);
241 
242         return actionList;
243       }
244     }
245 
246     return null;
247   }
248 
249   /**
250    * Return the list of applicable patterns for this
251    */
252   List getApplicableActionList(Pattern pattern, Attributes attributes) {
253     List applicableActionList = ruleStore.matchActions(pattern);
254 
255     // logger.debug("set of applicable patterns: " + applicableActionList);
256     if (applicableActionList == null) {
257       applicableActionList = lookupImplicitAction(pattern, attributes,
258           interpretationContext);
259     }
260 
261     return applicableActionList;
262   }
263 
264   void callBeginAction(List applicableActionList, String tagName,
265       Attributes atts) {
266     if (applicableActionList == null) {
267       return;
268     }
269 
270     Iterator i = applicableActionList.iterator();
271     while (i.hasNext()) {
272       Action action = (Action) i.next();
273       // now let us invoke the action. We catch and report any eventual
274       // exceptions
275       try {
276         action.begin(interpretationContext, tagName, atts);
277       } catch (ActionException e) {
278         skip = (Pattern) pattern.clone();
279         cai.addError("ActionException in Action for tag [" + tagName + "]", e);
280       } catch (RuntimeException e) {
281         skip = (Pattern) pattern.clone();
282         cai.addError("RuntimeException in Action for tag [" + tagName + "]", e);
283       }
284     }
285   }
286 
287   private void callBodyAction(List applicableActionList, String body) {
288     if (applicableActionList == null) {
289       return;
290     }
291     Iterator i = applicableActionList.iterator();
292 
293     while (i.hasNext()) {
294       Action action = (Action) i.next();
295       try {
296         action.body(interpretationContext, body);
297       } catch (ActionException ae) {
298         cai
299             .addError("Exception in end() methd for action [" + action + "]",
300                 ae);
301       }
302     }
303   }
304 
305   private void callEndAction(List applicableActionList, String tagName) {
306     if (applicableActionList == null) {
307       return;
308     }
309 
310     // logger.debug("About to call end actions on node: [" + localName + "]");
311     Iterator i = applicableActionList.iterator();
312 
313     while (i.hasNext()) {
314       Action action = (Action) i.next();
315       // now let us invoke the end method of the action. We catch and report
316       // any eventual exceptions
317       try {
318         action.end(interpretationContext, tagName);
319       } catch (ActionException ae) {
320         // at this point endAction, there is no point in skipping children as
321         // they have been already processed
322         cai.addError("ActionException in Action for tag [" + tagName + "]", ae);
323       } catch (RuntimeException e) {
324         // no point in setting skip
325         cai.addError("RuntimeException in Action for tag [" + tagName + "]", e);
326       }
327     }
328   }
329 
330   public RuleStore getRuleStore() {
331     return ruleStore;
332   }
333 }
334 
335 /**
336  * When {@link Interpreter} class is used as the origin of an
337  * {@link ContextAwareImpl} instance, then XML locator information is lost. This
338  * class preserves locator information (as a string).
339  * 
340  * @author ceki
341  */
342 class CAI_WithLocatorSupport extends ContextAwareImpl {
343 
344   CAI_WithLocatorSupport(Interpreter interpreter) {
345     super(interpreter);
346   }
347 
348   @Override
349   protected Object getOrigin() {
350     Interpreter i = (Interpreter) super.getOrigin();
351     Locator locator = i.locator;
352     if (locator != null) {
353       return Interpreter.class.getName() + "@" + locator.getLineNumber() + ":"
354           + locator.getColumnNumber();
355     } else {
356       return Interpreter.class.getName() + "@NA:NA";
357     }
358   }
359 }