001/**
002 * Logback: the reliable, generic, fast and flexible logging framework.
003 * Copyright (C) 1999-2015, QOS.ch. All rights reserved.
004 *
005 * This program and the accompanying materials are dual-licensed under
006 * either the terms of the Eclipse Public License v1.0 as published by
007 * the Eclipse Foundation
008 *
009 *   or (per the licensee's choosing)
010 *
011 * under the terms of the GNU Lesser General Public License version 2.1
012 * as published by the Free Software Foundation.
013 */
014package ch.qos.logback.core.joran.spi;
015
016import java.util.List;
017import java.util.Stack;
018import java.util.function.Supplier;
019
020import org.xml.sax.Attributes;
021import org.xml.sax.Locator;
022
023import ch.qos.logback.core.Context;
024import ch.qos.logback.core.joran.action.Action;
025import ch.qos.logback.core.joran.action.NOPAction;
026import ch.qos.logback.core.joran.event.BodyEvent;
027import ch.qos.logback.core.joran.event.EndEvent;
028import ch.qos.logback.core.joran.event.SaxEvent;
029import ch.qos.logback.core.joran.event.StartEvent;
030import ch.qos.logback.core.spi.ContextAwareImpl;
031
032/**
033 * {@code SaxEventInterpreter} is Joran's driving class for handling "low-level"
034 * SAX events. It extends SAX {@link org.xml.sax.helpers.DefaultHandler
035 * DefaultHandler} which invokes various {@link Action actions} according to
036 * predefined patterns.
037 * 
038 * <p>
039 * Patterns are kept in a {@link RuleStore} which is programmed to store and
040 * then later produce the applicable actions for a given pattern.
041 * 
042 * <p>
043 * The pattern corresponding to a top level &lt;a&gt; element is the string "a".
044 * 
045 * <p>
046 * The pattern corresponding to an element &lt;b&gt; embedded within a top level
047 * &lt;a&gt; element is the string {@code "a/b"}.
048 * 
049 * <p>
050 * The pattern corresponding to an &lt;b&gt; and any level of nesting is
051 * "&#42;/b. Thus, the &#42; character placed at the beginning of a pattern
052 * serves as a wildcard for the level of nesting.
053 * 
054 * Conceptually, this is very similar to the API of commons-digester. Joran
055 * offers several small advantages. First and foremost, it offers support for
056 * implicit actions which result in a significant leap in flexibility. Second,
057 * in our opinion better error reporting capability. Third, it is self-reliant.
058 * It does not depend on other APIs, in particular commons-logging which is too
059 * unreliable. Last but not least, Joran is quite tiny and is expected to remain
060 * so.
061 * 
062 * @author Ceki G&uuml;lc&uuml;
063 * 
064 */
065public class SaxEventInterpreter {
066    private static Action NOP_ACTION_SINGLETON = new NOPAction();
067
068    final private RuleStore ruleStore;
069    final private SaxEventInterpretationContext interpretationContext;
070    private Supplier<Action> implicitActionSupplier;
071    final private CAI_WithLocatorSupport cai;
072    private ElementPath elementPath;
073    Locator locator;
074    EventPlayer eventPlayer;
075    Context context;
076    
077    /**
078     * The <id>actionStack</id> contain the action that is executing
079     * for the given XML element.
080     * 
081     * An action is pushed by the {link #startElement} and popped by
082     * {@link #endElement}.
083     * 
084     */
085    Stack<Action> actionStack;
086
087    /**
088     * If the skip nested is set, then we skip all its nested elements until it is
089     * set back to null at when the element's end is reached.
090     */
091    ElementPath skip = null;
092
093    public SaxEventInterpreter(Context context, RuleStore rs, ElementPath initialElementPath, List<SaxEvent> saxEvents) {
094        this.context = context;
095        this.cai = new CAI_WithLocatorSupport(context, this);
096        ruleStore = rs;
097        interpretationContext = new SaxEventInterpretationContext(context, this);
098        this.elementPath = initialElementPath;
099        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 */
292class 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}