SaxEventInterpreter.java
/**
* Logback: the reliable, generic, fast and flexible logging framework.
* Copyright (C) 1999-2015, QOS.ch. All rights reserved.
*
* This program and the accompanying materials are dual-licensed under
* either the terms of the Eclipse Public License v1.0 as published by
* the Eclipse Foundation
*
* or (per the licensee's choosing)
*
* under the terms of the GNU Lesser General Public License version 2.1
* as published by the Free Software Foundation.
*/
package ch.qos.logback.core.joran.spi;
import java.util.List;
import java.util.Stack;
import java.util.function.Supplier;
import org.xml.sax.Attributes;
import org.xml.sax.Locator;
import ch.qos.logback.core.Context;
import ch.qos.logback.core.joran.action.Action;
import ch.qos.logback.core.joran.action.NOPAction;
import ch.qos.logback.core.joran.event.BodyEvent;
import ch.qos.logback.core.joran.event.EndEvent;
import ch.qos.logback.core.joran.event.SaxEvent;
import ch.qos.logback.core.joran.event.StartEvent;
import ch.qos.logback.core.spi.ContextAwareImpl;
/**
* {@code SaxEventInterpreter} is Joran's driving class for handling "low-level"
* SAX events. It extends SAX {@link org.xml.sax.helpers.DefaultHandler
* DefaultHandler} which invokes various {@link Action actions} according to
* predefined patterns.
*
* <p>
* Patterns are kept in a {@link RuleStore} which is programmed to store and
* then later produce the applicable actions for a given pattern.
*
* <p>
* The pattern corresponding to a top level <a> element is the string "a".
*
* <p>
* The pattern corresponding to an element <b> embedded within a top level
* <a> element is the string {@code "a/b"}.
*
* <p>
* The pattern corresponding to an <b> and any level of nesting is
* "*/b. Thus, the * character placed at the beginning of a pattern
* serves as a wildcard for the level of nesting.
*
* Conceptually, this is very similar to the API of commons-digester. Joran
* offers several small advantages. First and foremost, it offers support for
* implicit actions which result in a significant leap in flexibility. Second,
* in our opinion better error reporting capability. Third, it is self-reliant.
* It does not depend on other APIs, in particular commons-logging which is too
* unreliable. Last but not least, Joran is quite tiny and is expected to remain
* so.
*
* @author Ceki Gülcü
*
*/
public class SaxEventInterpreter {
private static Action NOP_ACTION_SINGLETON = new NOPAction();
final private RuleStore ruleStore;
final private SaxEventInterpretationContext interpretationContext;
private Supplier<Action> implicitActionSupplier;
final private CAI_WithLocatorSupport cai;
private ElementPath elementPath;
Locator locator;
EventPlayer eventPlayer;
Context context;
/**
* The <id>actionStack</id> contain the action that is executing
* for the given XML element.
*
* An action is pushed by the {link #startElement} and popped by
* {@link #endElement}.
*
*/
Stack<Action> actionStack;
/**
* If the skip nested is set, then we skip all its nested elements until it is
* set back to null at when the element's end is reached.
*/
ElementPath skip = null;
public SaxEventInterpreter(Context context, RuleStore rs, ElementPath initialElementPath, List<SaxEvent> saxEvents) {
this.context = context;
this.cai = new CAI_WithLocatorSupport(context, this);
ruleStore = rs;
interpretationContext = new SaxEventInterpretationContext(context, this);
this.elementPath = initialElementPath;
actionStack = new Stack<>();
eventPlayer = new EventPlayer(this, saxEvents);
}
public EventPlayer getEventPlayer() {
return eventPlayer;
}
public ElementPath getCopyOfElementPath() {
return elementPath.duplicate();
}
public SaxEventInterpretationContext getSaxEventInterpretationContext() {
return interpretationContext;
}
public void startDocument() {
}
public void startElement(StartEvent se) {
setDocumentLocator(se.getLocator());
startElement(se.namespaceURI, se.localName, se.qName, se.attributes);
}
private void startElement(String namespaceURI, String localName, String qName, Attributes atts) {
String tagName = getTagName(localName, qName);
elementPath.push(tagName);
if (skip != null) {
// every startElement pushes an action list
pushEmptyActionOntoActionStack();
return;
}
Action applicableAction = getApplicableAction(elementPath, atts);
if (applicableAction != null) {
actionStack.add(applicableAction);
callBeginAction(applicableAction, tagName, atts);
} else {
// every startElement pushes an action list
pushEmptyActionOntoActionStack();
String errMsg = "no applicable action for [" + tagName + "], current ElementPath is [" + elementPath + "]";
cai.addError(errMsg);
}
}
/**
* This method is used to
*/
private void pushEmptyActionOntoActionStack() {
actionStack.push(NOP_ACTION_SINGLETON);
}
public void characters(BodyEvent be) {
setDocumentLocator(be.locator);
String body = be.getText();
Action applicableAction = actionStack.peek();
if (body != null) {
body = body.trim();
if (body.length() > 0) {
callBodyAction(applicableAction, body);
}
}
}
public void endElement(EndEvent endEvent) {
setDocumentLocator(endEvent.locator);
endElement(endEvent.namespaceURI, endEvent.localName, endEvent.qName);
}
private void endElement(String namespaceURI, String localName, String qName) {
// given that an action is always pushed for every startElement, we
// need to always pop for every endElement
Action applicableAction = actionStack.pop();
if (skip != null) {
if (skip.equals(elementPath)) {
skip = null;
}
} else if (applicableAction != NOP_ACTION_SINGLETON) {
callEndAction(applicableAction, getTagName(localName, qName));
}
// given that we always push, we must also pop the pattern
elementPath.pop();
}
public Locator getLocator() {
return locator;
}
// having the locator set as parsing progresses is quite ugly
public void setDocumentLocator(Locator l) {
locator = l;
}
String getTagName(String localName, String qName) {
String tagName = localName;
if ((tagName == null) || (tagName.length() < 1)) {
tagName = qName;
}
return tagName;
}
public void setImplicitActionSupplier(Supplier<Action> actionSupplier) {
this.implicitActionSupplier = actionSupplier;
}
/**
* Return the list of applicable patterns for this
*/
Action getApplicableAction(ElementPath elementPath, Attributes attributes) {
Supplier<Action> applicableActionSupplier = ruleStore.matchActions(elementPath);
if (applicableActionSupplier != null) {
Action applicableAction = applicableActionSupplier.get();
applicableAction.setContext(context);
return applicableAction;
} else {
Action implicitAction = implicitActionSupplier.get();
implicitAction.setContext(context);
return implicitAction;
}
}
void callBeginAction(Action applicableAction, String tagName, Attributes atts) {
if (applicableAction == null) {
return;
}
// now let us invoke the action. We catch and report any eventual
// exceptions
try {
applicableAction.begin(interpretationContext, tagName, atts);
} catch (ActionException e) {
skip = elementPath.duplicate();
cai.addError("ActionException in Action for tag [" + tagName + "]", e);
} catch (RuntimeException e) {
skip = elementPath.duplicate();
cai.addError("RuntimeException in Action for tag [" + tagName + "]", e);
}
}
private void callBodyAction(Action applicableAction, String body) {
if (applicableAction == null) {
return;
}
try {
applicableAction.body(interpretationContext, body);
} catch (ActionException ae) {
cai.addError("Exception in body() method for action [" + applicableAction + "]", ae);
}
}
private void callEndAction(Action applicableAction, String tagName) {
if (applicableAction == null) {
return;
}
try {
applicableAction.end(interpretationContext, tagName);
} catch (ActionException ae) {
// at this point endAction, there is no point in skipping children as
// they have been already processed
cai.addError("ActionException in Action for tag [" + tagName + "]", ae);
} catch (RuntimeException e) {
// no point in setting skip
cai.addError("RuntimeException in Action for tag [" + tagName + "]", e);
}
}
public RuleStore getRuleStore() {
return ruleStore;
}
}
/**
* When {@link SaxEventInterpreter} class is used as the origin of an
* {@link ContextAwareImpl} instance, then XML locator information is lost. This
* class preserves locator information (as a string).
*
* @author ceki
*/
class CAI_WithLocatorSupport extends ContextAwareImpl {
CAI_WithLocatorSupport(Context context, SaxEventInterpreter interpreter) {
super(context, interpreter);
}
@Override
protected Object getOrigin() {
SaxEventInterpreter i = (SaxEventInterpreter) super.getOrigin();
Locator locator = i.locator;
if (locator != null) {
return SaxEventInterpreter.class.getName() + "@" + locator.getLineNumber() + ":"
+ locator.getColumnNumber();
} else {
return SaxEventInterpreter.class.getName() + "@NA:NA";
}
}
}