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 <a> element is the string "a". 044 * 045 * <p> 046 * The pattern corresponding to an element <b> embedded within a top level 047 * <a> element is the string {@code "a/b"}. 048 * 049 * <p> 050 * The pattern corresponding to an <b> and any level of nesting is 051 * "*/b. Thus, the * 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ülcü 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}