68bf46
From 6370372f5a04cdec6598fa8199b762ce33fa4d40 Mon Sep 17 00:00:00 2001
68bf46
From: Mikolaj Izdebski <mizdebsk@redhat.com>
68bf46
Date: Wed, 2 Feb 2022 19:37:17 +0100
68bf46
Subject: [PATCH] Fix CVE-2022-23305 JDBCAppender
68bf46
68bf46
---
68bf46
 .../apache/log4j/helpers/PatternParser.java   | 944 +++++++++---------
68bf46
 .../org/apache/log4j/jdbc/JDBCAppender.java   | 715 ++++++-------
68bf46
 .../apache/log4j/jdbc/JdbcPatternParser.java  | 115 +++
68bf46
 3 files changed, 942 insertions(+), 832 deletions(-)
68bf46
 create mode 100644 src/main/java/org/apache/log4j/jdbc/JdbcPatternParser.java
68bf46
68bf46
diff --git a/src/main/java/org/apache/log4j/helpers/PatternParser.java b/src/main/java/org/apache/log4j/helpers/PatternParser.java
68bf46
index 0d3ead67..4b169a2d 100644
68bf46
--- a/src/main/java/org/apache/log4j/helpers/PatternParser.java
68bf46
+++ b/src/main/java/org/apache/log4j/helpers/PatternParser.java
68bf46
@@ -30,541 +30,503 @@ import java.util.Arrays;
68bf46
 //                 Reinhard Deschler <reinhard.deschler@web.de>
68bf46
 
68bf46
 /**
68bf46
-   Most of the work of the {@link org.apache.log4j.PatternLayout} class
68bf46
-   is delegated to the PatternParser class.
68bf46
-
68bf46
-   

It is this class that parses conversion patterns and creates

68bf46
-   a chained list of {@link OptionConverter OptionConverters}.
68bf46
-
68bf46
-   @author James P. Cakalic
68bf46
-   @author Ceki Gülcü
68bf46
-   @author Anders Kristensen
68bf46
-
68bf46
-   @since 0.8.2
68bf46
-*/
68bf46
+ * Most of the work of the {@link org.apache.log4j.PatternLayout} class is
68bf46
+ * delegated to the PatternParser class.
68bf46
+ * 
68bf46
+ * 

68bf46
+ * It is this class that parses conversion patterns and creates a chained list
68bf46
+ * of {@link OptionConverter OptionConverters}.
68bf46
+ * 
68bf46
+ * @author James P. Cakalic
68bf46
+ * @author Ceki Gülcü
68bf46
+ * @author Anders Kristensen
68bf46
+ * 
68bf46
+ * @since 0.8.2
68bf46
+ */
68bf46
 public class PatternParser {
68bf46
 
68bf46
-  private static final char ESCAPE_CHAR = '%';
68bf46
-
68bf46
-  private static final int LITERAL_STATE = 0;
68bf46
-  private static final int CONVERTER_STATE = 1;
68bf46
-  private static final int DOT_STATE = 3;
68bf46
-  private static final int MIN_STATE = 4;
68bf46
-  private static final int MAX_STATE = 5;
68bf46
-
68bf46
-  static final int FULL_LOCATION_CONVERTER = 1000;
68bf46
-  static final int METHOD_LOCATION_CONVERTER = 1001;
68bf46
-  static final int CLASS_LOCATION_CONVERTER = 1002;
68bf46
-  static final int LINE_LOCATION_CONVERTER = 1003;
68bf46
-  static final int FILE_LOCATION_CONVERTER = 1004;
68bf46
-
68bf46
-  static final int RELATIVE_TIME_CONVERTER = 2000;
68bf46
-  static final int THREAD_CONVERTER = 2001;
68bf46
-  static final int LEVEL_CONVERTER = 2002;
68bf46
-  static final int NDC_CONVERTER = 2003;
68bf46
-  static final int MESSAGE_CONVERTER = 2004;
68bf46
-
68bf46
-  int state;
68bf46
-  protected StringBuffer currentLiteral = new StringBuffer(32);
68bf46
-  protected int patternLength;
68bf46
-  protected int i;
68bf46
-  PatternConverter head;
68bf46
-  PatternConverter tail;
68bf46
-  protected FormattingInfo formattingInfo = new FormattingInfo();
68bf46
-  protected String pattern;
68bf46
-
68bf46
-  public
68bf46
-  PatternParser(String pattern) {
68bf46
-    this.pattern = pattern;
68bf46
-    patternLength =  pattern.length();
68bf46
-    state = LITERAL_STATE;
68bf46
-  }
68bf46
-
68bf46
-  private
68bf46
-  void  addToList(PatternConverter pc) {
68bf46
-    if(head == null) {
68bf46
-      head = tail = pc;
68bf46
-    } else {
68bf46
-      tail.next = pc;
68bf46
-      tail = pc;
68bf46
+    private static final char LEFT_BRACKET = '{';
68bf46
+    private static final char RIGHT_BRACKET = '}';
68bf46
+    private static final char N_CHAR = 'n';
68bf46
+    private static final char DOT_CHAR = '.';
68bf46
+    private static final char DASH_CHAR = '-';
68bf46
+    private static final char ESCAPE_CHAR = '%';
68bf46
+
68bf46
+    private static final int LITERAL_STATE = 0;
68bf46
+    private static final int CONVERTER_STATE = 1;
68bf46
+    private static final int DOT_STATE = 3;
68bf46
+    private static final int MIN_STATE = 4;
68bf46
+    private static final int MAX_STATE = 5;
68bf46
+
68bf46
+    static final int FULL_LOCATION_CONVERTER = 1000;
68bf46
+    static final int METHOD_LOCATION_CONVERTER = 1001;
68bf46
+    static final int CLASS_LOCATION_CONVERTER = 1002;
68bf46
+    static final int LINE_LOCATION_CONVERTER = 1003;
68bf46
+    static final int FILE_LOCATION_CONVERTER = 1004;
68bf46
+
68bf46
+    static final int RELATIVE_TIME_CONVERTER = 2000;
68bf46
+    static final int THREAD_CONVERTER = 2001;
68bf46
+    static final int LEVEL_CONVERTER = 2002;
68bf46
+    static final int NDC_CONVERTER = 2003;
68bf46
+    static final int MESSAGE_CONVERTER = 2004;
68bf46
+
68bf46
+    int state;
68bf46
+    protected StringBuffer currentLiteral = new StringBuffer(32);
68bf46
+    protected int patternLength;
68bf46
+    protected int i;
68bf46
+    PatternConverter head;
68bf46
+    PatternConverter tail;
68bf46
+    protected FormattingInfo formattingInfo = new FormattingInfo();
68bf46
+    protected String pattern;
68bf46
+
68bf46
+    public PatternParser(String pattern) {
68bf46
+	this.pattern = pattern;
68bf46
+	patternLength = pattern.length();
68bf46
+	state = LITERAL_STATE;
68bf46
     }
68bf46
-  }
68bf46
-
68bf46
-  protected
68bf46
-  String extractOption() {
68bf46
-    if((i < patternLength) && (pattern.charAt(i) == '{')) {
68bf46
-      int end = pattern.indexOf('}', i);
68bf46
-      if (end > i) {
68bf46
-	String r = pattern.substring(i + 1, end);
68bf46
-	i = end+1;
68bf46
-	return r;
68bf46
-      }
68bf46
-    }
68bf46
-    return null;
68bf46
-  }
68bf46
-
68bf46
-
68bf46
-  /**
68bf46
-     The option is expected to be in decimal and positive. In case of
68bf46
-     error, zero is returned.  */
68bf46
-  protected
68bf46
-  int extractPrecisionOption() {
68bf46
-    String opt = extractOption();
68bf46
-    int r = 0;
68bf46
-    if(opt != null) {
68bf46
-      try {
68bf46
-	r = Integer.parseInt(opt);
68bf46
-	if(r <= 0) {
68bf46
-	    LogLog.error(
68bf46
-	        "Precision option (" + opt + ") isn't a positive integer.");
68bf46
-	    r = 0;
68bf46
+
68bf46
+    private void addToList(PatternConverter pc) {
68bf46
+	if (head == null) {
68bf46
+	    head = tail = pc;
68bf46
+	} else {
68bf46
+	    tail.next = pc;
68bf46
+	    tail = pc;
68bf46
 	}
68bf46
-      }
68bf46
-      catch (NumberFormatException e) {
68bf46
-	LogLog.error("Category option \""+opt+"\" not a decimal integer.", e);
68bf46
-      }
68bf46
     }
68bf46
-    return r;
68bf46
-  }
68bf46
-
68bf46
-  public
68bf46
-  PatternConverter parse() {
68bf46
-    char c;
68bf46
-    i = 0;
68bf46
-    while(i < patternLength) {
68bf46
-      c = pattern.charAt(i++);
68bf46
-      switch(state) {
68bf46
-      case LITERAL_STATE:
68bf46
-        // In literal state, the last char is always a literal.
68bf46
-        if(i == patternLength) {
68bf46
-          currentLiteral.append(c);
68bf46
-          continue;
68bf46
-        }
68bf46
-        if(c == ESCAPE_CHAR) {
68bf46
-          // peek at the next char.
68bf46
-          switch(pattern.charAt(i)) {
68bf46
-          case ESCAPE_CHAR:
68bf46
-            currentLiteral.append(c);
68bf46
-            i++; // move pointer
68bf46
-            break;
68bf46
-          case 'n':
68bf46
-            currentLiteral.append(Layout.LINE_SEP);
68bf46
-            i++; // move pointer
68bf46
-            break;
68bf46
-          default:
68bf46
-            if(currentLiteral.length() != 0) {
68bf46
-              addToList(new LiteralPatternConverter(
68bf46
-                                                  currentLiteral.toString()));
68bf46
-              //LogLog.debug("Parsed LITERAL converter: \""
68bf46
-              //           +currentLiteral+"\".");
68bf46
-            }
68bf46
-            currentLiteral.setLength(0);
68bf46
-            currentLiteral.append(c); // append %
68bf46
-            state = CONVERTER_STATE;
68bf46
-            formattingInfo.reset();
68bf46
-          }
68bf46
-        }
68bf46
-        else {
68bf46
-          currentLiteral.append(c);
68bf46
-        }
68bf46
-        break;
68bf46
-      case CONVERTER_STATE:
68bf46
-	currentLiteral.append(c);
68bf46
-	switch(c) {
68bf46
-	case '-':
68bf46
-	  formattingInfo.leftAlign = true;
68bf46
-	  break;
68bf46
-	case '.':
68bf46
-	  state = DOT_STATE;
68bf46
-	  break;
68bf46
-	default:
68bf46
-	  if(c >= '0' && c <= '9') {
68bf46
-	    formattingInfo.min = c - '0';
68bf46
-	    state = MIN_STATE;
68bf46
-	  }
68bf46
-	  else
68bf46
-	    finalizeConverter(c);
68bf46
-	} // switch
68bf46
-	break;
68bf46
-      case MIN_STATE:
68bf46
-	currentLiteral.append(c);
68bf46
-	if(c >= '0' && c <= '9')
68bf46
-	  formattingInfo.min = formattingInfo.min*10 + (c - '0');
68bf46
-	else if(c == '.')
68bf46
-	  state = DOT_STATE;
68bf46
-	else {
68bf46
-	  finalizeConverter(c);
68bf46
-	}
68bf46
-	break;
68bf46
-      case DOT_STATE:
68bf46
-	currentLiteral.append(c);
68bf46
-	if(c >= '0' && c <= '9') {
68bf46
-	  formattingInfo.max = c - '0';
68bf46
-	   state = MAX_STATE;
68bf46
-	}
68bf46
-	else {
68bf46
-	  LogLog.error("Error occured in position "+i
68bf46
-		     +".\n Was expecting digit, instead got char \""+c+"\".");
68bf46
-	  state = LITERAL_STATE;
68bf46
-	}
68bf46
-	break;
68bf46
-      case MAX_STATE:
68bf46
-	currentLiteral.append(c);
68bf46
-	if(c >= '0' && c <= '9')
68bf46
-	  formattingInfo.max = formattingInfo.max*10 + (c - '0');
68bf46
-	else {
68bf46
-	  finalizeConverter(c);
68bf46
-	  state = LITERAL_STATE;
68bf46
+
68bf46
+    protected String extractOption() {
68bf46
+	if ((i < patternLength) && (pattern.charAt(i) == LEFT_BRACKET)) {
68bf46
+	    int end = pattern.indexOf(RIGHT_BRACKET, i);
68bf46
+	    if (end > i) {
68bf46
+		String r = pattern.substring(i + 1, end);
68bf46
+		i = end + 1;
68bf46
+		return r;
68bf46
+	    }
68bf46
 	}
68bf46
-	break;
68bf46
-      } // switch
68bf46
-    } // while
68bf46
-    if(currentLiteral.length() != 0) {
68bf46
-      addToList(new LiteralPatternConverter(currentLiteral.toString()));
68bf46
-      //LogLog.debug("Parsed LITERAL converter: \""+currentLiteral+"\".");
68bf46
+	return null;
68bf46
     }
68bf46
-    return head;
68bf46
-  }
68bf46
-
68bf46
-  protected
68bf46
-  void finalizeConverter(char c) {
68bf46
-    PatternConverter pc = null;
68bf46
-    switch(c) {
68bf46
-    case 'c':
68bf46
-      pc = new CategoryPatternConverter(formattingInfo,
68bf46
-					extractPrecisionOption());
68bf46
-      //LogLog.debug("CATEGORY converter.");
68bf46
-      //formattingInfo.dump();
68bf46
-      currentLiteral.setLength(0);
68bf46
-      break;
68bf46
-    case 'C':
68bf46
-      pc = new ClassNamePatternConverter(formattingInfo,
68bf46
-					 extractPrecisionOption());
68bf46
-      //LogLog.debug("CLASS_NAME converter.");
68bf46
-      //formattingInfo.dump();
68bf46
-      currentLiteral.setLength(0);
68bf46
-      break;
68bf46
-    case 'd':
68bf46
-      String dateFormatStr = AbsoluteTimeDateFormat.ISO8601_DATE_FORMAT;
68bf46
-      DateFormat df;
68bf46
-      String dOpt = extractOption();
68bf46
-      if(dOpt != null)
68bf46
-	dateFormatStr = dOpt;
68bf46
-
68bf46
-      if(dateFormatStr.equalsIgnoreCase(
68bf46
-                                    AbsoluteTimeDateFormat.ISO8601_DATE_FORMAT))
68bf46
-	df = new  ISO8601DateFormat();
68bf46
-      else if(dateFormatStr.equalsIgnoreCase(
68bf46
-                                   AbsoluteTimeDateFormat.ABS_TIME_DATE_FORMAT))
68bf46
-	df = new AbsoluteTimeDateFormat();
68bf46
-      else if(dateFormatStr.equalsIgnoreCase(
68bf46
-                              AbsoluteTimeDateFormat.DATE_AND_TIME_DATE_FORMAT))
68bf46
-	df = new DateTimeDateFormat();
68bf46
-      else {
68bf46
-	try {
68bf46
-	  df = new SimpleDateFormat(dateFormatStr);
68bf46
-	}
68bf46
-	catch (IllegalArgumentException e) {
68bf46
-	  LogLog.error("Could not instantiate SimpleDateFormat with " +
68bf46
-		       dateFormatStr, e);
68bf46
-	  df = (DateFormat) OptionConverter.instantiateByClassName(
68bf46
-			           "org.apache.log4j.helpers.ISO8601DateFormat",
68bf46
-				   DateFormat.class, null);
68bf46
+
68bf46
+    /**
68bf46
+     * The option is expected to be in decimal and positive. In case of error, zero
68bf46
+     * is returned.
68bf46
+     */
68bf46
+    protected int extractPrecisionOption() {
68bf46
+	String opt = extractOption();
68bf46
+	int r = 0;
68bf46
+	if (opt != null) {
68bf46
+	    try {
68bf46
+		r = Integer.parseInt(opt);
68bf46
+		if (r <= 0) {
68bf46
+		    LogLog.error("Precision option (" + opt + ") isn't a positive integer.");
68bf46
+		    r = 0;
68bf46
+		}
68bf46
+	    } catch (NumberFormatException e) {
68bf46
+		LogLog.error("Category option \"" + opt + "\" not a decimal integer.", e);
68bf46
+	    }
68bf46
 	}
68bf46
-      }
68bf46
-      pc = new DatePatternConverter(formattingInfo, df);
68bf46
-      //LogLog.debug("DATE converter {"+dateFormatStr+"}.");
68bf46
-      //formattingInfo.dump();
68bf46
-      currentLiteral.setLength(0);
68bf46
-      break;
68bf46
-    case 'F':
68bf46
-      pc = new LocationPatternConverter(formattingInfo,
68bf46
-					FILE_LOCATION_CONVERTER);
68bf46
-      //LogLog.debug("File name converter.");
68bf46
-      //formattingInfo.dump();
68bf46
-      currentLiteral.setLength(0);
68bf46
-      break;
68bf46
-    case 'l':
68bf46
-      pc = new LocationPatternConverter(formattingInfo,
68bf46
-					FULL_LOCATION_CONVERTER);
68bf46
-      //LogLog.debug("Location converter.");
68bf46
-      //formattingInfo.dump();
68bf46
-      currentLiteral.setLength(0);
68bf46
-      break;
68bf46
-    case 'L':
68bf46
-      pc = new LocationPatternConverter(formattingInfo,
68bf46
-					LINE_LOCATION_CONVERTER);
68bf46
-      //LogLog.debug("LINE NUMBER converter.");
68bf46
-      //formattingInfo.dump();
68bf46
-      currentLiteral.setLength(0);
68bf46
-      break;
68bf46
-    case 'm':
68bf46
-      pc = new BasicPatternConverter(formattingInfo, MESSAGE_CONVERTER);
68bf46
-      //LogLog.debug("MESSAGE converter.");
68bf46
-      //formattingInfo.dump();
68bf46
-      currentLiteral.setLength(0);
68bf46
-      break;
68bf46
-    case 'M':
68bf46
-      pc = new LocationPatternConverter(formattingInfo,
68bf46
-					METHOD_LOCATION_CONVERTER);
68bf46
-      //LogLog.debug("METHOD converter.");
68bf46
-      //formattingInfo.dump();
68bf46
-      currentLiteral.setLength(0);
68bf46
-      break;
68bf46
-    case 'p':
68bf46
-      pc = new BasicPatternConverter(formattingInfo, LEVEL_CONVERTER);
68bf46
-      //LogLog.debug("LEVEL converter.");
68bf46
-      //formattingInfo.dump();
68bf46
-      currentLiteral.setLength(0);
68bf46
-      break;
68bf46
-    case 'r':
68bf46
-      pc = new BasicPatternConverter(formattingInfo,
68bf46
-					 RELATIVE_TIME_CONVERTER);
68bf46
-      //LogLog.debug("RELATIVE time converter.");
68bf46
-      //formattingInfo.dump();
68bf46
-      currentLiteral.setLength(0);
68bf46
-      break;
68bf46
-    case 't':
68bf46
-      pc = new BasicPatternConverter(formattingInfo, THREAD_CONVERTER);
68bf46
-      //LogLog.debug("THREAD converter.");
68bf46
-      //formattingInfo.dump();
68bf46
-      currentLiteral.setLength(0);
68bf46
-      break;
68bf46
-      /*case 'u':
68bf46
-      if(i < patternLength) {
68bf46
-	char cNext = pattern.charAt(i);
68bf46
-	if(cNext >= '0' && cNext <= '9') {
68bf46
-	  pc = new UserFieldPatternConverter(formattingInfo, cNext - '0');
68bf46
-	  LogLog.debug("USER converter ["+cNext+"].");
68bf46
-	  formattingInfo.dump();
68bf46
-	  currentLiteral.setLength(0);
68bf46
-	  i++;
68bf46
+	return r;
68bf46
+    }
68bf46
+
68bf46
+    public PatternConverter parse() {
68bf46
+	char c;
68bf46
+	i = 0;
68bf46
+	while (i < patternLength) {
68bf46
+	    c = pattern.charAt(i++);
68bf46
+	    switch (state) {
68bf46
+	    case LITERAL_STATE:
68bf46
+		// In literal state, the last char is always a literal.
68bf46
+		if (i == patternLength) {
68bf46
+		    currentLiteral.append(c);
68bf46
+		    continue;
68bf46
+		}
68bf46
+		if (c == ESCAPE_CHAR) {
68bf46
+		    // peek at the next char.
68bf46
+		    switch (pattern.charAt(i)) {
68bf46
+		    case ESCAPE_CHAR:
68bf46
+			currentLiteral.append(c);
68bf46
+			i++; // move pointer
68bf46
+			break;
68bf46
+		    case N_CHAR:
68bf46
+			currentLiteral.append(Layout.LINE_SEP);
68bf46
+			i++; // move pointer
68bf46
+			break;
68bf46
+		    default:
68bf46
+			if (currentLiteral.length() != 0) {
68bf46
+			    addToList(new LiteralPatternConverter(currentLiteral.toString()));
68bf46
+			    // LogLog.debug("Parsed LITERAL converter: \""
68bf46
+			    // +currentLiteral+"\".");
68bf46
+			}
68bf46
+			currentLiteral.setLength(0);
68bf46
+			currentLiteral.append(c); // append %
68bf46
+			state = CONVERTER_STATE;
68bf46
+			formattingInfo.reset();
68bf46
+		    }
68bf46
+		} else {
68bf46
+		    currentLiteral.append(c);
68bf46
+		}
68bf46
+		break;
68bf46
+	    case CONVERTER_STATE:
68bf46
+		currentLiteral.append(c);
68bf46
+		switch (c) {
68bf46
+		case DASH_CHAR:
68bf46
+		    formattingInfo.leftAlign = true;
68bf46
+		    break;
68bf46
+		case DOT_CHAR:
68bf46
+		    state = DOT_STATE;
68bf46
+		    break;
68bf46
+		default:
68bf46
+		    if (c >= '0' && c <= '9') {
68bf46
+			formattingInfo.min = c - '0';
68bf46
+			state = MIN_STATE;
68bf46
+		    } else
68bf46
+			finalizeConverter(c);
68bf46
+		} // switch
68bf46
+		break;
68bf46
+	    case MIN_STATE:
68bf46
+		currentLiteral.append(c);
68bf46
+		if (c >= '0' && c <= '9')
68bf46
+		    formattingInfo.min = formattingInfo.min * 10 + (c - '0');
68bf46
+		else if (c == '.')
68bf46
+		    state = DOT_STATE;
68bf46
+		else {
68bf46
+		    finalizeConverter(c);
68bf46
+		}
68bf46
+		break;
68bf46
+	    case DOT_STATE:
68bf46
+		currentLiteral.append(c);
68bf46
+		if (c >= '0' && c <= '9') {
68bf46
+		    formattingInfo.max = c - '0';
68bf46
+		    state = MAX_STATE;
68bf46
+		} else {
68bf46
+		    LogLog.error("Error occured in position " + i + ".\n Was expecting digit, instead got char \"" + c
68bf46
+			    + "\".");
68bf46
+		    state = LITERAL_STATE;
68bf46
+		}
68bf46
+		break;
68bf46
+	    case MAX_STATE:
68bf46
+		currentLiteral.append(c);
68bf46
+		if (c >= '0' && c <= '9')
68bf46
+		    formattingInfo.max = formattingInfo.max * 10 + (c - '0');
68bf46
+		else {
68bf46
+		    finalizeConverter(c);
68bf46
+		    state = LITERAL_STATE;
68bf46
+		}
68bf46
+		break;
68bf46
+	    } // switch
68bf46
+	} // while
68bf46
+	if (currentLiteral.length() != 0) {
68bf46
+	    addToList(new LiteralPatternConverter(currentLiteral.toString()));
68bf46
+	    // LogLog.debug("Parsed LITERAL converter: \""+currentLiteral+"\".");
68bf46
 	}
68bf46
-	else
68bf46
-	  LogLog.error("Unexpected char" +cNext+" at position "+i);
68bf46
-      }
68bf46
-      break;*/
68bf46
-    case 'x':
68bf46
-      pc = new BasicPatternConverter(formattingInfo, NDC_CONVERTER);
68bf46
-      //LogLog.debug("NDC converter.");
68bf46
-      currentLiteral.setLength(0);
68bf46
-      break;
68bf46
-    case 'X':
68bf46
-      String xOpt = extractOption();
68bf46
-      pc = new MDCPatternConverter(formattingInfo, xOpt);
68bf46
-      currentLiteral.setLength(0);
68bf46
-      break;
68bf46
-    default:
68bf46
-      LogLog.error("Unexpected char [" +c+"] at position "+i
68bf46
-		   +" in conversion patterrn.");
68bf46
-      pc = new LiteralPatternConverter(currentLiteral.toString());
68bf46
-      currentLiteral.setLength(0);
68bf46
+	return head;
68bf46
     }
68bf46
 
68bf46
-    addConverter(pc);
68bf46
-  }
68bf46
-
68bf46
-  protected
68bf46
-  void addConverter(PatternConverter pc) {
68bf46
-    currentLiteral.setLength(0);
68bf46
-    // Add the pattern converter to the list.
68bf46
-    addToList(pc);
68bf46
-    // Next pattern is assumed to be a literal.
68bf46
-    state = LITERAL_STATE;
68bf46
-    // Reset formatting info
68bf46
-    formattingInfo.reset();
68bf46
-  }
68bf46
-
68bf46
-  // ---------------------------------------------------------------------
68bf46
-  //                      PatternConverters
68bf46
-  // ---------------------------------------------------------------------
68bf46
-
68bf46
-  private static class BasicPatternConverter extends PatternConverter {
68bf46
-    int type;
68bf46
-
68bf46
-    BasicPatternConverter(FormattingInfo formattingInfo, int type) {
68bf46
-      super(formattingInfo);
68bf46
-      this.type = type;
68bf46
+    protected void finalizeConverter(char c) {
68bf46
+	PatternConverter pc = null;
68bf46
+	switch (c) {
68bf46
+	case 'c':
68bf46
+	    pc = new CategoryPatternConverter(formattingInfo, extractPrecisionOption());
68bf46
+	    // LogLog.debug("CATEGORY converter.");
68bf46
+	    // formattingInfo.dump();
68bf46
+	    currentLiteral.setLength(0);
68bf46
+	    break;
68bf46
+	case 'C':
68bf46
+	    pc = new ClassNamePatternConverter(formattingInfo, extractPrecisionOption());
68bf46
+	    // LogLog.debug("CLASS_NAME converter.");
68bf46
+	    // formattingInfo.dump();
68bf46
+	    currentLiteral.setLength(0);
68bf46
+	    break;
68bf46
+	case 'd':
68bf46
+	    String dateFormatStr = AbsoluteTimeDateFormat.ISO8601_DATE_FORMAT;
68bf46
+	    DateFormat df;
68bf46
+	    String dOpt = extractOption();
68bf46
+	    if (dOpt != null)
68bf46
+		dateFormatStr = dOpt;
68bf46
+
68bf46
+	    if (dateFormatStr.equalsIgnoreCase(AbsoluteTimeDateFormat.ISO8601_DATE_FORMAT))
68bf46
+		df = new ISO8601DateFormat();
68bf46
+	    else if (dateFormatStr.equalsIgnoreCase(AbsoluteTimeDateFormat.ABS_TIME_DATE_FORMAT))
68bf46
+		df = new AbsoluteTimeDateFormat();
68bf46
+	    else if (dateFormatStr.equalsIgnoreCase(AbsoluteTimeDateFormat.DATE_AND_TIME_DATE_FORMAT))
68bf46
+		df = new DateTimeDateFormat();
68bf46
+	    else {
68bf46
+		try {
68bf46
+		    df = new SimpleDateFormat(dateFormatStr);
68bf46
+		} catch (IllegalArgumentException e) {
68bf46
+		    LogLog.error("Could not instantiate SimpleDateFormat with " + dateFormatStr, e);
68bf46
+		    df = (DateFormat) OptionConverter.instantiateByClassName(
68bf46
+			    "org.apache.log4j.helpers.ISO8601DateFormat", DateFormat.class, null);
68bf46
+		}
68bf46
+	    }
68bf46
+	    pc = new DatePatternConverter(formattingInfo, df);
68bf46
+	    // LogLog.debug("DATE converter {"+dateFormatStr+"}.");
68bf46
+	    // formattingInfo.dump();
68bf46
+	    currentLiteral.setLength(0);
68bf46
+	    break;
68bf46
+	case 'F':
68bf46
+	    pc = new LocationPatternConverter(formattingInfo, FILE_LOCATION_CONVERTER);
68bf46
+	    // LogLog.debug("File name converter.");
68bf46
+	    // formattingInfo.dump();
68bf46
+	    currentLiteral.setLength(0);
68bf46
+	    break;
68bf46
+	case 'l':
68bf46
+	    pc = new LocationPatternConverter(formattingInfo, FULL_LOCATION_CONVERTER);
68bf46
+	    // LogLog.debug("Location converter.");
68bf46
+	    // formattingInfo.dump();
68bf46
+	    currentLiteral.setLength(0);
68bf46
+	    break;
68bf46
+	case 'L':
68bf46
+	    pc = new LocationPatternConverter(formattingInfo, LINE_LOCATION_CONVERTER);
68bf46
+	    // LogLog.debug("LINE NUMBER converter.");
68bf46
+	    // formattingInfo.dump();
68bf46
+	    currentLiteral.setLength(0);
68bf46
+	    break;
68bf46
+	case 'm':
68bf46
+	    pc = new BasicPatternConverter(formattingInfo, MESSAGE_CONVERTER);
68bf46
+	    // LogLog.debug("MESSAGE converter.");
68bf46
+	    // formattingInfo.dump();
68bf46
+	    currentLiteral.setLength(0);
68bf46
+	    break;
68bf46
+	case 'M':
68bf46
+	    pc = new LocationPatternConverter(formattingInfo, METHOD_LOCATION_CONVERTER);
68bf46
+	    // LogLog.debug("METHOD converter.");
68bf46
+	    // formattingInfo.dump();
68bf46
+	    currentLiteral.setLength(0);
68bf46
+	    break;
68bf46
+	case 'p':
68bf46
+	    pc = new BasicPatternConverter(formattingInfo, LEVEL_CONVERTER);
68bf46
+	    // LogLog.debug("LEVEL converter.");
68bf46
+	    // formattingInfo.dump();
68bf46
+	    currentLiteral.setLength(0);
68bf46
+	    break;
68bf46
+	case 'r':
68bf46
+	    pc = new BasicPatternConverter(formattingInfo, RELATIVE_TIME_CONVERTER);
68bf46
+	    // LogLog.debug("RELATIVE time converter.");
68bf46
+	    // formattingInfo.dump();
68bf46
+	    currentLiteral.setLength(0);
68bf46
+	    break;
68bf46
+	case 't':
68bf46
+	    pc = new BasicPatternConverter(formattingInfo, THREAD_CONVERTER);
68bf46
+	    // LogLog.debug("THREAD converter.");
68bf46
+	    // formattingInfo.dump();
68bf46
+	    currentLiteral.setLength(0);
68bf46
+	    break;
68bf46
+	/*
68bf46
+	 * case 'u': if(i < patternLength) { char cNext = pattern.charAt(i); if(cNext >=
68bf46
+	 * '0' && cNext <= '9') { pc = new UserFieldPatternConverter(formattingInfo,
68bf46
+	 * cNext - '0'); LogLog.debug("USER converter ["+cNext+"].");
68bf46
+	 * formattingInfo.dump(); currentLiteral.setLength(0); i++; } else
68bf46
+	 * LogLog.error("Unexpected char" +cNext+" at position "+i); } break;
68bf46
+	 */
68bf46
+	case 'x':
68bf46
+	    pc = new BasicPatternConverter(formattingInfo, NDC_CONVERTER);
68bf46
+	    // LogLog.debug("NDC converter.");
68bf46
+	    currentLiteral.setLength(0);
68bf46
+	    break;
68bf46
+	case 'X':
68bf46
+	    String xOpt = extractOption();
68bf46
+	    pc = new MDCPatternConverter(formattingInfo, xOpt);
68bf46
+	    currentLiteral.setLength(0);
68bf46
+	    break;
68bf46
+	default:
68bf46
+	    LogLog.error("Unexpected char [" + c + "] at position " + i + " in conversion patterrn.");
68bf46
+	    pc = new LiteralPatternConverter(currentLiteral.toString());
68bf46
+	    currentLiteral.setLength(0);
68bf46
+	}
68bf46
+
68bf46
+	addConverter(pc);
68bf46
     }
68bf46
 
68bf46
-    public
68bf46
-    String convert(LoggingEvent event) {
68bf46
-      switch(type) {
68bf46
-      case RELATIVE_TIME_CONVERTER:
68bf46
-	return (Long.toString(event.timeStamp - LoggingEvent.getStartTime()));
68bf46
-      case THREAD_CONVERTER:
68bf46
-	return event.getThreadName();
68bf46
-      case LEVEL_CONVERTER:
68bf46
-	return event.getLevel().toString();
68bf46
-      case NDC_CONVERTER:
68bf46
-	return event.getNDC();
68bf46
-      case MESSAGE_CONVERTER: {
68bf46
-	return event.getRenderedMessage();
68bf46
-      }
68bf46
-      default: return null;
68bf46
-      }
68bf46
+    protected void addConverter(PatternConverter pc) {
68bf46
+	currentLiteral.setLength(0);
68bf46
+	// Add the pattern converter to the list.
68bf46
+	addToList(pc);
68bf46
+	// Next pattern is assumed to be a literal.
68bf46
+	state = LITERAL_STATE;
68bf46
+	// Reset formatting info
68bf46
+	formattingInfo.reset();
68bf46
     }
68bf46
-  }
68bf46
 
68bf46
-  private static class LiteralPatternConverter extends PatternConverter {
68bf46
-    private String literal;
68bf46
+    // ---------------------------------------------------------------------
68bf46
+    // PatternConverters
68bf46
+    // ---------------------------------------------------------------------
68bf46
 
68bf46
-    LiteralPatternConverter(String value) {
68bf46
-      literal = value;
68bf46
-    }
68bf46
+    private static class BasicPatternConverter extends PatternConverter {
68bf46
+	int type;
68bf46
 
68bf46
-    public
68bf46
-    final
68bf46
-    void format(StringBuffer sbuf, LoggingEvent event) {
68bf46
-      sbuf.append(literal);
68bf46
-    }
68bf46
+	BasicPatternConverter(FormattingInfo formattingInfo, int type) {
68bf46
+	    super(formattingInfo);
68bf46
+	    this.type = type;
68bf46
+	}
68bf46
 
68bf46
-    public
68bf46
-    String convert(LoggingEvent event) {
68bf46
-      return literal;
68bf46
+	public String convert(LoggingEvent event) {
68bf46
+	    switch (type) {
68bf46
+	    case RELATIVE_TIME_CONVERTER:
68bf46
+		return (Long.toString(event.timeStamp - LoggingEvent.getStartTime()));
68bf46
+	    case THREAD_CONVERTER:
68bf46
+		return event.getThreadName();
68bf46
+	    case LEVEL_CONVERTER:
68bf46
+		return event.getLevel().toString();
68bf46
+	    case NDC_CONVERTER:
68bf46
+		return event.getNDC();
68bf46
+	    case MESSAGE_CONVERTER: {
68bf46
+		return event.getRenderedMessage();
68bf46
+	    }
68bf46
+	    default:
68bf46
+		return null;
68bf46
+	    }
68bf46
+	}
68bf46
     }
68bf46
-  }
68bf46
 
68bf46
-  private static class DatePatternConverter extends PatternConverter {
68bf46
-    private DateFormat df;
68bf46
-    private Date date;
68bf46
+    private static class LiteralPatternConverter extends PatternConverter {
68bf46
+	private String literal;
68bf46
 
68bf46
-    DatePatternConverter(FormattingInfo formattingInfo, DateFormat df) {
68bf46
-      super(formattingInfo);
68bf46
-      date = new Date();
68bf46
-      this.df = df;
68bf46
-    }
68bf46
+	LiteralPatternConverter(String value) {
68bf46
+	    literal = value;
68bf46
+	}
68bf46
 
68bf46
-    public
68bf46
-    String convert(LoggingEvent event) {
68bf46
-      date.setTime(event.timeStamp);
68bf46
-      String converted = null;
68bf46
-      try {
68bf46
-        converted = df.format(date);
68bf46
-      }
68bf46
-      catch (Exception ex) {
68bf46
-        LogLog.error("Error occured while converting date.", ex);
68bf46
-      }
68bf46
-      return converted;
68bf46
+	public final void format(StringBuffer sbuf, LoggingEvent event) {
68bf46
+	    sbuf.append(literal);
68bf46
+	}
68bf46
+
68bf46
+	public String convert(LoggingEvent event) {
68bf46
+	    return literal;
68bf46
+	}
68bf46
     }
68bf46
-  }
68bf46
 
68bf46
-  private static class MDCPatternConverter extends PatternConverter {
68bf46
-    private String key;
68bf46
+    private static class DatePatternConverter extends PatternConverter {
68bf46
+	private DateFormat df;
68bf46
+	private Date date;
68bf46
 
68bf46
-    MDCPatternConverter(FormattingInfo formattingInfo, String key) {
68bf46
-      super(formattingInfo);
68bf46
-      this.key = key;
68bf46
-    }
68bf46
+	DatePatternConverter(FormattingInfo formattingInfo, DateFormat df) {
68bf46
+	    super(formattingInfo);
68bf46
+	    date = new Date();
68bf46
+	    this.df = df;
68bf46
+	}
68bf46
 
68bf46
-    public
68bf46
-    String convert(LoggingEvent event) {
68bf46
-      if (key == null) {
68bf46
-          StringBuffer buf = new StringBuffer("{");
68bf46
-          Map properties = event.getProperties();
68bf46
-          if (properties.size() > 0) {
68bf46
-            Object[] keys = properties.keySet().toArray();
68bf46
-            Arrays.sort(keys);
68bf46
-            for (int i = 0; i < keys.length; i++) {
68bf46
-                buf.append('{');
68bf46
-                buf.append(keys[i]);
68bf46
-                buf.append(',');
68bf46
-                buf.append(properties.get(keys[i]));
68bf46
-                buf.append('}');
68bf46
-            }
68bf46
-          }
68bf46
-          buf.append('}');
68bf46
-          return buf.toString();
68bf46
-      } else {
68bf46
-        Object val = event.getMDC(key);
68bf46
-        if(val == null) {
68bf46
-	        return null;
68bf46
-        } else {
68bf46
-	        return val.toString();
68bf46
-        }
68bf46
-      }
68bf46
+	public String convert(LoggingEvent event) {
68bf46
+	    date.setTime(event.timeStamp);
68bf46
+	    String converted = null;
68bf46
+	    try {
68bf46
+		converted = df.format(date);
68bf46
+	    } catch (Exception ex) {
68bf46
+		LogLog.error("Error occured while converting date.", ex);
68bf46
+	    }
68bf46
+	    return converted;
68bf46
+	}
68bf46
     }
68bf46
-  }
68bf46
 
68bf46
+    private static class MDCPatternConverter extends PatternConverter {
68bf46
+	private String key;
68bf46
 
68bf46
-  private class LocationPatternConverter extends PatternConverter {
68bf46
-    int type;
68bf46
+	MDCPatternConverter(FormattingInfo formattingInfo, String key) {
68bf46
+	    super(formattingInfo);
68bf46
+	    this.key = key;
68bf46
+	}
68bf46
 
68bf46
-    LocationPatternConverter(FormattingInfo formattingInfo, int type) {
68bf46
-      super(formattingInfo);
68bf46
-      this.type = type;
68bf46
+	public String convert(LoggingEvent event) {
68bf46
+	    if (key == null) {
68bf46
+		StringBuffer buf = new StringBuffer("{");
68bf46
+		Map properties = event.getProperties();
68bf46
+		if (properties.size() > 0) {
68bf46
+		    Object[] keys = properties.keySet().toArray();
68bf46
+		    Arrays.sort(keys);
68bf46
+		    for (int i = 0; i < keys.length; i++) {
68bf46
+			buf.append('{');
68bf46
+			buf.append(keys[i]);
68bf46
+			buf.append(',');
68bf46
+			buf.append(properties.get(keys[i]));
68bf46
+			buf.append('}');
68bf46
+		    }
68bf46
+		}
68bf46
+		buf.append('}');
68bf46
+		return buf.toString();
68bf46
+	    } else {
68bf46
+		Object val = event.getMDC(key);
68bf46
+		if (val == null) {
68bf46
+		    return null;
68bf46
+		} else {
68bf46
+		    return val.toString();
68bf46
+		}
68bf46
+	    }
68bf46
+	}
68bf46
     }
68bf46
 
68bf46
-    public
68bf46
-    String convert(LoggingEvent event) {
68bf46
-      LocationInfo locationInfo = event.getLocationInformation();
68bf46
-      switch(type) {
68bf46
-      case FULL_LOCATION_CONVERTER:
68bf46
-	return locationInfo.fullInfo;
68bf46
-      case METHOD_LOCATION_CONVERTER:
68bf46
-	return locationInfo.getMethodName();
68bf46
-      case LINE_LOCATION_CONVERTER:
68bf46
-	return locationInfo.getLineNumber();
68bf46
-      case FILE_LOCATION_CONVERTER:
68bf46
-	return locationInfo.getFileName();
68bf46
-      default: return null;
68bf46
-      }
68bf46
-    }
68bf46
-  }
68bf46
+    private class LocationPatternConverter extends PatternConverter {
68bf46
+	int type;
68bf46
 
68bf46
-  private static abstract class NamedPatternConverter extends PatternConverter {
68bf46
-    int precision;
68bf46
+	LocationPatternConverter(FormattingInfo formattingInfo, int type) {
68bf46
+	    super(formattingInfo);
68bf46
+	    this.type = type;
68bf46
+	}
68bf46
 
68bf46
-    NamedPatternConverter(FormattingInfo formattingInfo, int precision) {
68bf46
-      super(formattingInfo);
68bf46
-      this.precision =  precision;
68bf46
+	public String convert(LoggingEvent event) {
68bf46
+	    LocationInfo locationInfo = event.getLocationInformation();
68bf46
+	    switch (type) {
68bf46
+	    case FULL_LOCATION_CONVERTER:
68bf46
+		return locationInfo.fullInfo;
68bf46
+	    case METHOD_LOCATION_CONVERTER:
68bf46
+		return locationInfo.getMethodName();
68bf46
+	    case LINE_LOCATION_CONVERTER:
68bf46
+		return locationInfo.getLineNumber();
68bf46
+	    case FILE_LOCATION_CONVERTER:
68bf46
+		return locationInfo.getFileName();
68bf46
+	    default:
68bf46
+		return null;
68bf46
+	    }
68bf46
+	}
68bf46
     }
68bf46
 
68bf46
-    abstract
68bf46
-    String getFullyQualifiedName(LoggingEvent event);
68bf46
-
68bf46
-    public
68bf46
-    String convert(LoggingEvent event) {
68bf46
-      String n = getFullyQualifiedName(event);
68bf46
-      if(precision <= 0)
68bf46
-	return n;
68bf46
-      else {
68bf46
-	int len = n.length();
68bf46
-
68bf46
-	// We substract 1 from 'len' when assigning to 'end' to avoid out of
68bf46
-	// bounds exception in return r.substring(end+1, len). This can happen if
68bf46
-	// precision is 1 and the category name ends with a dot.
68bf46
-	int end = len -1 ;
68bf46
-	for(int i = precision; i > 0; i--) {
68bf46
-	  end = n.lastIndexOf('.', end-1);
68bf46
-	  if(end == -1)
68bf46
-	    return n;
68bf46
+    private static abstract class NamedPatternConverter extends PatternConverter {
68bf46
+	int precision;
68bf46
+
68bf46
+	NamedPatternConverter(FormattingInfo formattingInfo, int precision) {
68bf46
+	    super(formattingInfo);
68bf46
+	    this.precision = precision;
68bf46
+	}
68bf46
+
68bf46
+	abstract String getFullyQualifiedName(LoggingEvent event);
68bf46
+
68bf46
+	public String convert(LoggingEvent event) {
68bf46
+	    String n = getFullyQualifiedName(event);
68bf46
+	    if (precision <= 0)
68bf46
+		return n;
68bf46
+	    else {
68bf46
+		int len = n.length();
68bf46
+
68bf46
+		// We substract 1 from 'len' when assigning to 'end' to avoid out of
68bf46
+		// bounds exception in return r.substring(end+1, len). This can happen if
68bf46
+		// precision is 1 and the category name ends with a dot.
68bf46
+		int end = len - 1;
68bf46
+		for (int i = precision; i > 0; i--) {
68bf46
+		    end = n.lastIndexOf('.', end - 1);
68bf46
+		    if (end == -1)
68bf46
+			return n;
68bf46
+		}
68bf46
+		return n.substring(end + 1, len);
68bf46
+	    }
68bf46
 	}
68bf46
-	return n.substring(end+1, len);
68bf46
-      }
68bf46
     }
68bf46
-  }
68bf46
 
68bf46
-  private class ClassNamePatternConverter extends NamedPatternConverter {
68bf46
+    private class ClassNamePatternConverter extends NamedPatternConverter {
68bf46
 
68bf46
-    ClassNamePatternConverter(FormattingInfo formattingInfo, int precision) {
68bf46
-      super(formattingInfo, precision);
68bf46
-    }
68bf46
+	ClassNamePatternConverter(FormattingInfo formattingInfo, int precision) {
68bf46
+	    super(formattingInfo, precision);
68bf46
+	}
68bf46
 
68bf46
-    String getFullyQualifiedName(LoggingEvent event) {
68bf46
-      return event.getLocationInformation().getClassName();
68bf46
+	String getFullyQualifiedName(LoggingEvent event) {
68bf46
+	    return event.getLocationInformation().getClassName();
68bf46
+	}
68bf46
     }
68bf46
-  }
68bf46
 
68bf46
-  private class CategoryPatternConverter extends NamedPatternConverter {
68bf46
+    private class CategoryPatternConverter extends NamedPatternConverter {
68bf46
 
68bf46
-    CategoryPatternConverter(FormattingInfo formattingInfo, int precision) {
68bf46
-      super(formattingInfo, precision);
68bf46
-    }
68bf46
+	CategoryPatternConverter(FormattingInfo formattingInfo, int precision) {
68bf46
+	    super(formattingInfo, precision);
68bf46
+	}
68bf46
 
68bf46
-    String getFullyQualifiedName(LoggingEvent event) {
68bf46
-      return event.getLoggerName();
68bf46
+	String getFullyQualifiedName(LoggingEvent event) {
68bf46
+	    return event.getLoggerName();
68bf46
+	}
68bf46
     }
68bf46
-  }
68bf46
 }
68bf46
-
68bf46
diff --git a/src/main/java/org/apache/log4j/jdbc/JDBCAppender.java b/src/main/java/org/apache/log4j/jdbc/JDBCAppender.java
68bf46
index ad35f657..6ba9b66c 100644
68bf46
--- a/src/main/java/org/apache/log4j/jdbc/JDBCAppender.java
68bf46
+++ b/src/main/java/org/apache/log4j/jdbc/JDBCAppender.java
68bf46
@@ -18,381 +18,414 @@ package org.apache.log4j.jdbc;
68bf46
 
68bf46
 import java.sql.Connection;
68bf46
 import java.sql.DriverManager;
68bf46
+import java.sql.PreparedStatement;
68bf46
 import java.sql.SQLException;
68bf46
 import java.sql.Statement;
68bf46
 import java.util.ArrayList;
68bf46
-import java.util.Iterator;
68bf46
 
68bf46
 import org.apache.log4j.PatternLayout;
68bf46
+import org.apache.log4j.helpers.LogLog;
68bf46
 import org.apache.log4j.spi.ErrorCode;
68bf46
 import org.apache.log4j.spi.LoggingEvent;
68bf46
 
68bf46
-
68bf46
 /**
68bf46
-  The JDBCAppender provides for sending log events to a database.
68bf46
-  
68bf46
- 

<font color="#FF2222">WARNING: This version of JDBCAppender

68bf46
- is very likely to be completely replaced in the future. Moreoever,
68bf46
- it does not log exceptions</font>.
68bf46
-
68bf46
-  

Each append call adds to an ArrayList buffer. When

68bf46
-  the buffer is filled each log event is placed in a sql statement
68bf46
-  (configurable) and executed.
68bf46
-
68bf46
-  BufferSize, db URL, User, & Password are
68bf46
-  configurable options in the standard log4j ways.
68bf46
-
68bf46
-  

The setSql(String sql) sets the SQL statement to be

68bf46
-  used for logging -- this statement is sent to a
68bf46
-  PatternLayout (either created automaticly by the
68bf46
-  appender or added by the user).  Therefore by default all the
68bf46
-  conversion patterns in PatternLayout can be used
68bf46
-  inside of the statement.  (see the test cases for examples)
68bf46
-
68bf46
-  

Overriding the {@link #getLogStatement} method allows more

68bf46
-  explicit control of the statement used for logging.
68bf46
-
68bf46
-  

For use as a base class:

68bf46
-
68bf46
-    
    68bf46
    -
    68bf46
    -    
  • Override getConnection() to pass any connection
  • 68bf46
    -    you want.  Typically this is used to enable application wide
    68bf46
    -    connection pooling.
    68bf46
    -
    68bf46
    -     
  • Override closeConnection(Connection con) -- if
  • 68bf46
    -     you override getConnection make sure to implement
    68bf46
    -     closeConnection to handle the connection you
    68bf46
    -     generated.  Typically this would return the connection to the
    68bf46
    -     pool it came from.
    68bf46
    -
    68bf46
    -     
  • Override getLogStatement(LoggingEvent event) to
  • 68bf46
    -     produce specialized or dynamic statements. The default uses the
    68bf46
    -     sql option value.
    68bf46
    -
    68bf46
    -    
    68bf46
    -
    68bf46
    -    @author Kevin Steppe (ksteppe@pacbell.net)
    68bf46
    -
    68bf46
    -*/
    68bf46
    -public class JDBCAppender extends org.apache.log4j.AppenderSkeleton
    68bf46
    -    implements org.apache.log4j.Appender {
    68bf46
    -
    68bf46
    -  /**
    68bf46
    -   * URL of the DB for default connection handling
    68bf46
    -   */
    68bf46
    -  protected String databaseURL = "jdbc:odbc:myDB";
    68bf46
    -
    68bf46
    -  /**
    68bf46
    -   * User to connect as for default connection handling
    68bf46
    -   */
    68bf46
    -  protected String databaseUser = "me";
    68bf46
    -
    68bf46
    -  /**
    68bf46
    -   * User to use for default connection handling
    68bf46
    -   */
    68bf46
    -  protected String databasePassword = "mypassword";
    68bf46
    -
    68bf46
    -  /**
    68bf46
    -   * Connection used by default.  The connection is opened the first time it
    68bf46
    -   * is needed and then held open until the appender is closed (usually at
    68bf46
    -   * garbage collection).  This behavior is best modified by creating a
    68bf46
    -   * sub-class and overriding the getConnection and
    68bf46
    -   * closeConnection methods.
    68bf46
    -   */
    68bf46
    -  protected Connection connection = null;
    68bf46
    -
    68bf46
    -  /**
    68bf46
    -   * Stores the string given to the pattern layout for conversion into a SQL
    68bf46
    -   * statement, eg: insert into LogTable (Thread, Class, Message) values
    68bf46
    -   * ("%t", "%c", "%m").
    68bf46
    -   *
    68bf46
    -   * Be careful of quotes in your messages!
    68bf46
    -   *
    68bf46
    -   * Also see PatternLayout.
    68bf46
    -   */
    68bf46
    -  protected String sqlStatement = "";
    68bf46
    -
    68bf46
    -  /**
    68bf46
    -   * size of LoggingEvent buffer before writting to the database.
    68bf46
    -   * Default is 1.
    68bf46
    -   */
    68bf46
    -  protected int bufferSize = 1;
    68bf46
    -
    68bf46
    -  /**
    68bf46
    -   * ArrayList holding the buffer of Logging Events.
    68bf46
    -   */
    68bf46
    -  protected ArrayList buffer;
    68bf46
    -
    68bf46
    -  /**
    68bf46
    -   * Helper object for clearing out the buffer
    68bf46
    -   */
    68bf46
    -  protected ArrayList removes;
    68bf46
    -  
    68bf46
    -  private boolean locationInfo = false;
    68bf46
    -
    68bf46
    -  public JDBCAppender() {
    68bf46
    -    super();
    68bf46
    -    buffer = new ArrayList(bufferSize);
    68bf46
    -    removes = new ArrayList(bufferSize);
    68bf46
    -  }
    68bf46
    -
    68bf46
    -  /**
    68bf46
    -   * Gets whether the location of the logging request call
    68bf46
    -   * should be captured.
    68bf46
    -   *
    68bf46
    -   * @since 1.2.16
    68bf46
    -   * @return the current value of the LocationInfo option.
    68bf46
    -   */
    68bf46
    -  public boolean getLocationInfo() {
    68bf46
    -    return locationInfo;
    68bf46
    -  }
    68bf46
    -  
    68bf46
    -  /**
    68bf46
    -   * The LocationInfo option takes a boolean value. By default, it is
    68bf46
    -   * set to false which means there will be no effort to extract the location
    68bf46
    -   * information related to the event. As a result, the event that will be
    68bf46
    -   * ultimately logged will likely to contain the wrong location information
    68bf46
    -   * (if present in the log format).
    68bf46
    -   * 

    68bf46
    -   * 

    68bf46
    -   * Location information extraction is comparatively very slow and should be
    68bf46
    -   * avoided unless performance is not a concern.
    68bf46
    -   * 

    68bf46
    -   * @since 1.2.16
    68bf46
    -   * @param flag true if location information should be extracted.
    68bf46
    -   */
    68bf46
    -  public void setLocationInfo(final boolean flag) {
    68bf46
    -    locationInfo = flag;
    68bf46
    -  }
    68bf46
    -  
    68bf46
    -
    68bf46
    -  /**
    68bf46
    -   * Adds the event to the buffer.  When full the buffer is flushed.
    68bf46
    -   */
    68bf46
    -  public void append(LoggingEvent event) {
    68bf46
    -    event.getNDC();
    68bf46
    -    event.getThreadName();
    68bf46
    -    // Get a copy of this thread's MDC.
    68bf46
    -    event.getMDCCopy();
    68bf46
    -    if (locationInfo) {
    68bf46
    -      event.getLocationInformation();
    68bf46
    -    }
    68bf46
    -    event.getRenderedMessage();
    68bf46
    -    event.getThrowableStrRep();
    68bf46
    -    buffer.add(event);
    68bf46
    -
    68bf46
    -    if (buffer.size() >= bufferSize)
    68bf46
    -      flushBuffer();
    68bf46
    -  }
    68bf46
    -
    68bf46
    -  /**
    68bf46
    -   * By default getLogStatement sends the event to the required Layout object.
    68bf46
    -   * The layout will format the given pattern into a workable SQL string.
    68bf46
    -   *
    68bf46
    -   * Overriding this provides direct access to the LoggingEvent
    68bf46
    -   * when constructing the logging statement.
    68bf46
    -   *
    68bf46
    -   */
    68bf46
    -  protected String getLogStatement(LoggingEvent event) {
    68bf46
    -    return getLayout().format(event);
    68bf46
    -  }
    68bf46
    -
    68bf46
    -  /**
    68bf46
    -   *
    68bf46
    -   * Override this to provide an alertnate method of getting
    68bf46
    -   * connections (such as caching).  One method to fix this is to open
    68bf46
    -   * connections at the start of flushBuffer() and close them at the
    68bf46
    -   * end.  I use a connection pool outside of JDBCAppender which is
    68bf46
    -   * accessed in an override of this method.
    68bf46
    -   * */
    68bf46
    -  protected void execute(String sql) throws SQLException {
    68bf46
    -
    68bf46
    -    Connection con = null;
    68bf46
    -    Statement stmt = null;
    68bf46
    -
    68bf46
    -    try {
    68bf46
    -        con = getConnection();
    68bf46
    -
    68bf46
    -        stmt = con.createStatement();
    68bf46
    -        stmt.executeUpdate(sql);
    68bf46
    -    } finally {
    68bf46
    -        if(stmt != null) {
    68bf46
    -            stmt.close();
    68bf46
    -        }
    68bf46
    -        closeConnection(con);
    68bf46
    + * The JDBCAppender provides for sending log events to a database.
    68bf46
    + * 
    68bf46
    + * 

    68bf46
    + * <font color="#FF2222">WARNING: This version of JDBCAppender is very likely
    68bf46
    + * to be completely replaced in the future. Moreoever, it does not log
    68bf46
    + * exceptions</font>.
    68bf46
    + * 
    68bf46
    + * 

    68bf46
    + * Each append call adds to an ArrayList buffer. When the buffer is
    68bf46
    + * filled each log event is placed in a sql statement (configurable) and
    68bf46
    + * executed.
    68bf46
    + * 
    68bf46
    + * BufferSize, db URL, User, & Password are
    68bf46
    + * configurable options in the standard log4j ways.
    68bf46
    + * 
    68bf46
    + * 

    68bf46
    + * The setSql(String sql) sets the SQL statement to be used for
    68bf46
    + * logging -- this statement is sent to a PatternLayout (either
    68bf46
    + * created automatically by the appender or added by the user). Therefore by
    68bf46
    + * default all the conversion patterns in PatternLayout can be used
    68bf46
    + * inside of the statement. (see the test cases for examples)
    68bf46
    + * 
    68bf46
    + * 

    68bf46
    + * Overriding the {@link #getLogStatement} method allows more explicit control
    68bf46
    + * of the statement used for logging.
    68bf46
    + * 
    68bf46
    + * 

    68bf46
    + * For use as a base class:
    68bf46
    + * 
    68bf46
    + * 
      68bf46
      + * 
      68bf46
      + * 
    • Override getConnection() to pass any connection you want.
    • 68bf46
      + * Typically this is used to enable application wide connection pooling.
      68bf46
      + * 
      68bf46
      + * 
    • Override closeConnection(Connection con) -- if you override
    • 68bf46
      + * getConnection make sure to implement closeConnection to handle
      68bf46
      + * the connection you generated. Typically this would return the connection to
      68bf46
      + * the pool it came from.
      68bf46
      + * 
      68bf46
      + * 
    • Override getLogStatement(LoggingEvent event) to produce
    • 68bf46
      + * specialized or dynamic statements. The default uses the sql option value.
      68bf46
      + * 
      68bf46
      + * 
      68bf46
      + * 
      68bf46
      + * @author Kevin Steppe
      68bf46
      + *         (ksteppe@pacbell.net)
      68bf46
      + * 
      68bf46
      + */
      68bf46
      +public class JDBCAppender extends org.apache.log4j.AppenderSkeleton implements org.apache.log4j.Appender {
      68bf46
      +
      68bf46
      +//    private final Boolean secureSqlReplacement = Boolean
      68bf46
      +//	    .parseBoolean(System.getProperty("org.apache.log4j.jdbc.JDBCAppender.secure_jdbc_replacement", "true"));
      68bf46
      +
      68bf46
      +    private static final IllegalArgumentException ILLEGAL_PATTERN_FOR_SECURE_EXEC = new IllegalArgumentException(
      68bf46
      +	    "Only org.apache.log4j.PatternLayout is supported for now due to CVE-2022-23305");
      68bf46
      +
      68bf46
      +    /**
      68bf46
      +     * URL of the DB for default connection handling
      68bf46
      +     */
      68bf46
      +    protected String databaseURL = "jdbc:odbc:myDB";
      68bf46
      +
      68bf46
      +    /**
      68bf46
      +     * User to connect as for default connection handling
      68bf46
      +     */
      68bf46
      +    protected String databaseUser = "me";
      68bf46
      +
      68bf46
      +    /**
      68bf46
      +     * User to use for default connection handling
      68bf46
      +     */
      68bf46
      +    protected String databasePassword = "mypassword";
      68bf46
      +
      68bf46
      +    /**
      68bf46
      +     * Connection used by default. The connection is opened the first time it is
      68bf46
      +     * needed and then held open until the appender is closed (usually at garbage
      68bf46
      +     * collection). This behavior is best modified by creating a sub-class and
      68bf46
      +     * overriding the getConnection and closeConnection
      68bf46
      +     * methods.
      68bf46
      +     */
      68bf46
      +    protected Connection connection = null;
      68bf46
      +
      68bf46
      +    /**
      68bf46
      +     * Stores the string given to the pattern layout for conversion into a SQL
      68bf46
      +     * statement, eg: insert into LogTable (Thread, Class, Message) values ("%t",
      68bf46
      +     * "%c", "%m").
      68bf46
      +     *
      68bf46
      +     * Be careful of quotes in your messages!
      68bf46
      +     *
      68bf46
      +     * Also see PatternLayout.
      68bf46
      +     */
      68bf46
      +    protected String sqlStatement = "";
      68bf46
      +
      68bf46
      +    private JdbcPatternParser preparedStatementParser;
      68bf46
      +    /**
      68bf46
      +     * size of LoggingEvent buffer before writting to the database. Default is 1.
      68bf46
      +     */
      68bf46
      +    protected int bufferSize = 1;
      68bf46
      +
      68bf46
      +    /**
      68bf46
      +     * ArrayList holding the buffer of Logging Events.
      68bf46
      +     */
      68bf46
      +    protected ArrayList<LoggingEvent> buffer;
      68bf46
      +
      68bf46
      +    private boolean locationInfo = false;
      68bf46
      +
      68bf46
      +    private boolean isActive = false;
      68bf46
      +
      68bf46
      +    public JDBCAppender() {
      68bf46
      +	super();
      68bf46
      +	buffer = new ArrayList<LoggingEvent>(bufferSize);
      68bf46
           }
      68bf46
       
      68bf46
      -    //System.out.println("Execute: " + sql);
      68bf46
      -  }
      68bf46
      -
      68bf46
      -
      68bf46
      -  /**
      68bf46
      -   * Override this to return the connection to a pool, or to clean up the
      68bf46
      -   * resource.
      68bf46
      -   *
      68bf46
      -   * The default behavior holds a single connection open until the appender
      68bf46
      -   * is closed (typically when garbage collected).
      68bf46
      -   */
      68bf46
      -  protected void closeConnection(Connection con) {
      68bf46
      -  }
      68bf46
      -
      68bf46
      -  /**
      68bf46
      -   * Override this to link with your connection pooling system.
      68bf46
      -   *
      68bf46
      -   * By default this creates a single connection which is held open
      68bf46
      -   * until the object is garbage collected.
      68bf46
      -   */
      68bf46
      -  protected Connection getConnection() throws SQLException {
      68bf46
      -      if (!DriverManager.getDrivers().hasMoreElements())
      68bf46
      -	     setDriver("sun.jdbc.odbc.JdbcOdbcDriver");
      68bf46
      -
      68bf46
      -      if (connection == null) {
      68bf46
      -        connection = DriverManager.getConnection(databaseURL, databaseUser,
      68bf46
      -					databasePassword);
      68bf46
      -      }
      68bf46
      -
      68bf46
      -      return connection;
      68bf46
      -  }
      68bf46
      -
      68bf46
      -  /**
      68bf46
      -   * Closes the appender, flushing the buffer first then closing the default
      68bf46
      -   * connection if it is open.
      68bf46
      -   */
      68bf46
      -  public void close()
      68bf46
      -  {
      68bf46
      -    flushBuffer();
      68bf46
      -
      68bf46
      -    try {
      68bf46
      -      if (connection != null && !connection.isClosed())
      68bf46
      -          connection.close();
      68bf46
      -    } catch (SQLException e) {
      68bf46
      -        errorHandler.error("Error closing connection", e, ErrorCode.GENERIC_FAILURE);
      68bf46
      +    @Override
      68bf46
      +    public void activateOptions() {
      68bf46
      +
      68bf46
      +	if (getSql() == null || getSql().trim().length() == 0) {
      68bf46
      +	    LogLog.error("JDBCAppender.sql parameter is mandatory. Skipping all futher processing");
      68bf46
      +	    isActive = false;
      68bf46
      +	    return;
      68bf46
      +	}
      68bf46
      +
      68bf46
      +	LogLog.debug("JDBCAppender constructing internal pattern parser");
      68bf46
      +	preparedStatementParser = new JdbcPatternParser(getSql());
      68bf46
      +	isActive = true;
      68bf46
           }
      68bf46
      -    this.closed = true;
      68bf46
      -  }
      68bf46
      -
      68bf46
      -  /**
      68bf46
      -   * loops through the buffer of LoggingEvents, gets a
      68bf46
      -   * sql string from getLogStatement() and sends it to execute().
      68bf46
      -   * Errors are sent to the errorHandler.
      68bf46
      -   *
      68bf46
      -   * If a statement fails the LoggingEvent stays in the buffer!
      68bf46
      -   */
      68bf46
      -  public void flushBuffer() {
      68bf46
      -    //Do the actual logging
      68bf46
      -    removes.ensureCapacity(buffer.size());
      68bf46
      -    for (Iterator i = buffer.iterator(); i.hasNext();) {
      68bf46
      -      LoggingEvent logEvent = (LoggingEvent)i.next();
      68bf46
      -      try {
      68bf46
      -	    String sql = getLogStatement(logEvent);
      68bf46
      -	    execute(sql);
      68bf46
      -      }
      68bf46
      -      catch (SQLException e) {
      68bf46
      -	    errorHandler.error("Failed to excute sql", e,
      68bf46
      -			   ErrorCode.FLUSH_FAILURE);
      68bf46
      -      } finally {
      68bf46
      -        removes.add(logEvent);
      68bf46
      -      }
      68bf46
      +
      68bf46
      +    /**
      68bf46
      +     * Gets whether the location of the logging request call should be captured.
      68bf46
      +     *
      68bf46
      +     * @since 1.2.16
      68bf46
      +     * @return the current value of the LocationInfo option.
      68bf46
      +     */
      68bf46
      +    public boolean getLocationInfo() {
      68bf46
      +	return locationInfo;
      68bf46
           }
      68bf46
      -    
      68bf46
      -    // remove from the buffer any events that were reported
      68bf46
      -    buffer.removeAll(removes);
      68bf46
      -    
      68bf46
      -    // clear the buffer of reported events
      68bf46
      -    removes.clear();
      68bf46
      -  }
      68bf46
      -
      68bf46
      -
      68bf46
      -  /** closes the appender before disposal */
      68bf46
      -  public void finalize() {
      68bf46
      -    close();
      68bf46
      -  }
      68bf46
      -
      68bf46
      -
      68bf46
      -  /**
      68bf46
      -   * JDBCAppender requires a layout.
      68bf46
      -   * */
      68bf46
      -  public boolean requiresLayout() {
      68bf46
      -    return true;
      68bf46
      -  }
      68bf46
      -
      68bf46
      -
      68bf46
      -  /**
      68bf46
      -   *
      68bf46
      -   */
      68bf46
      -  public void setSql(String s) {
      68bf46
      -    sqlStatement = s;
      68bf46
      -    if (getLayout() == null) {
      68bf46
      -        this.setLayout(new PatternLayout(s));
      68bf46
      +
      68bf46
      +    /**
      68bf46
      +     * The LocationInfo option takes a boolean value. By default, it is set
      68bf46
      +     * to false which means there will be no effort to extract the location
      68bf46
      +     * information related to the event. As a result, the event that will be
      68bf46
      +     * ultimately logged will likely to contain the wrong location information (if
      68bf46
      +     * present in the log format).
      68bf46
      +     * 

      68bf46
      +     * 

      68bf46
      +     * Location information extraction is comparatively very slow and should be
      68bf46
      +     * avoided unless performance is not a concern.
      68bf46
      +     * 

      68bf46
      +     * 
      68bf46
      +     * @since 1.2.16
      68bf46
      +     * @param flag true if location information should be extracted.
      68bf46
      +     */
      68bf46
      +    public void setLocationInfo(final boolean flag) {
      68bf46
      +	locationInfo = flag;
      68bf46
           }
      68bf46
      -    else {
      68bf46
      -        ((PatternLayout)getLayout()).setConversionPattern(s);
      68bf46
      +
      68bf46
      +    /**
      68bf46
      +     * Adds the event to the buffer. When full the buffer is flushed.
      68bf46
      +     */
      68bf46
      +    public void append(LoggingEvent event) {
      68bf46
      +	if (!isActive) {
      68bf46
      +	    return;
      68bf46
      +	}
      68bf46
      +	event.getNDC();
      68bf46
      +	event.getThreadName();
      68bf46
      +	// Get a copy of this thread's MDC.
      68bf46
      +	event.getMDCCopy();
      68bf46
      +	if (locationInfo) {
      68bf46
      +	    event.getLocationInformation();
      68bf46
      +	}
      68bf46
      +	event.getRenderedMessage();
      68bf46
      +	event.getThrowableStrRep();
      68bf46
      +	buffer.add(event);
      68bf46
      +
      68bf46
      +	if (buffer.size() >= bufferSize)
      68bf46
      +	    flushBuffer();
      68bf46
           }
      68bf46
      -  }
      68bf46
       
      68bf46
      +    /**
      68bf46
      +     * By default getLogStatement sends the event to the required Layout object. The
      68bf46
      +     * layout will format the given pattern into a workable SQL string.
      68bf46
      +     *
      68bf46
      +     * Overriding this provides direct access to the LoggingEvent when constructing
      68bf46
      +     * the logging statement.
      68bf46
      +     *
      68bf46
      +     */
      68bf46
      +    protected String getLogStatement(LoggingEvent event) {
      68bf46
      +	return getLayout().format(event);
      68bf46
      +    }
      68bf46
       
      68bf46
      -  /**
      68bf46
      -   * Returns pre-formated statement eg: insert into LogTable (msg) values ("%m")
      68bf46
      -   */
      68bf46
      -  public String getSql() {
      68bf46
      -    return sqlStatement;
      68bf46
      -  }
      68bf46
      +    /**
      68bf46
      +     *
      68bf46
      +     * Override this to provide an alertnate method of getting connections (such as
      68bf46
      +     * caching). One method to fix this is to open connections at the start of
      68bf46
      +     * flushBuffer() and close them at the end. I use a connection pool outside of
      68bf46
      +     * JDBCAppender which is accessed in an override of this method.
      68bf46
      +     */
      68bf46
      +    protected void execute(String sql) throws SQLException {
      68bf46
      +
      68bf46
      +	Connection con = null;
      68bf46
      +	Statement stmt = null;
      68bf46
      +
      68bf46
      +	try {
      68bf46
      +	    con = getConnection();
      68bf46
      +
      68bf46
      +	    stmt = con.createStatement();
      68bf46
      +	    stmt.executeUpdate(sql);
      68bf46
      +	} finally {
      68bf46
      +	    if (stmt != null) {
      68bf46
      +		stmt.close();
      68bf46
      +	    }
      68bf46
      +	    closeConnection(con);
      68bf46
      +	}
      68bf46
      +
      68bf46
      +	// System.out.println("Execute: " + sql);
      68bf46
      +    }
      68bf46
       
      68bf46
      +    /**
      68bf46
      +     * Override this to return the connection to a pool, or to clean up the
      68bf46
      +     * resource.
      68bf46
      +     *
      68bf46
      +     * The default behavior holds a single connection open until the appender is
      68bf46
      +     * closed (typically when garbage collected).
      68bf46
      +     */
      68bf46
      +    protected void closeConnection(Connection con) {
      68bf46
      +    }
      68bf46
       
      68bf46
      -  public void setUser(String user) {
      68bf46
      -    databaseUser = user;
      68bf46
      -  }
      68bf46
      +    /**
      68bf46
      +     * Override this to link with your connection pooling system.
      68bf46
      +     *
      68bf46
      +     * By default this creates a single connection which is held open until the
      68bf46
      +     * object is garbage collected.
      68bf46
      +     */
      68bf46
      +    protected Connection getConnection() throws SQLException {
      68bf46
      +	if (!DriverManager.getDrivers().hasMoreElements())
      68bf46
      +	    setDriver("sun.jdbc.odbc.JdbcOdbcDriver");
      68bf46
      +
      68bf46
      +	if (connection == null) {
      68bf46
      +	    connection = DriverManager.getConnection(databaseURL, databaseUser, databasePassword);
      68bf46
      +	}
      68bf46
      +
      68bf46
      +	return connection;
      68bf46
      +    }
      68bf46
       
      68bf46
      +    /**
      68bf46
      +     * Closes the appender, flushing the buffer first then closing the default
      68bf46
      +     * connection if it is open.
      68bf46
      +     */
      68bf46
      +    public void close() {
      68bf46
      +	flushBuffer();
      68bf46
      +
      68bf46
      +	try {
      68bf46
      +	    if (connection != null && !connection.isClosed())
      68bf46
      +		connection.close();
      68bf46
      +	} catch (SQLException e) {
      68bf46
      +	    errorHandler.error("Error closing connection", e, ErrorCode.GENERIC_FAILURE);
      68bf46
      +	}
      68bf46
      +	this.closed = true;
      68bf46
      +    }
      68bf46
       
      68bf46
      -  public void setURL(String url) {
      68bf46
      -    databaseURL = url;
      68bf46
      -  }
      68bf46
      +    /**
      68bf46
      +     * loops through the buffer of LoggingEvents, gets a sql string from
      68bf46
      +     * getLogStatement() and sends it to execute(). Errors are sent to the
      68bf46
      +     * errorHandler.
      68bf46
      +     *
      68bf46
      +     * If a statement fails the LoggingEvent stays in the buffer!
      68bf46
      +     */
      68bf46
      +    public void flushBuffer() {
      68bf46
      +	if (buffer.isEmpty()) {
      68bf46
      +	    return;
      68bf46
      +	}
      68bf46
      +	flushBufferSecure();
      68bf46
      +    }
      68bf46
       
      68bf46
      +    private void flushBufferSecure() {
      68bf46
      +	// Prepare events that we will store to the DB
      68bf46
      +	ArrayList<LoggingEvent> removes = new ArrayList<LoggingEvent>(buffer);
      68bf46
      +	buffer.removeAll(removes);
      68bf46
      +	  
      68bf46
      +	if (layout.getClass() != PatternLayout.class) {
      68bf46
      +	    errorHandler.error("Failed to convert pattern " + layout + " to SQL", ILLEGAL_PATTERN_FOR_SECURE_EXEC,
      68bf46
      +		    ErrorCode.MISSING_LAYOUT);
      68bf46
      +	    return;
      68bf46
      +	}
      68bf46
      +	Connection con = null;
      68bf46
      +	boolean useBatch = removes.size() > 1;
      68bf46
      +	try {
      68bf46
      +	    con = getConnection();
      68bf46
      +	    PreparedStatement ps = con.prepareStatement(preparedStatementParser.getParameterizedSql());
      68bf46
      +	    safelyInsertIntoDB(removes, useBatch, ps);
      68bf46
      +	} catch (SQLException e) {
      68bf46
      +	    errorHandler.error("Failed to append messages sql", e, ErrorCode.FLUSH_FAILURE);
      68bf46
      +	} finally {
      68bf46
      +	    if (con != null) {
      68bf46
      +		closeConnection(con);
      68bf46
      +	    }
      68bf46
      +	}
      68bf46
      +
      68bf46
      +   }
      68bf46
      +
      68bf46
      +    private void safelyInsertIntoDB(ArrayList<LoggingEvent> removes, boolean useBatch, PreparedStatement ps)
      68bf46
      +	    throws SQLException {
      68bf46
      +	try {
      68bf46
      +	    for (LoggingEvent logEvent : removes) {
      68bf46
      +		try {
      68bf46
      +		    preparedStatementParser.setParameters(ps, logEvent);
      68bf46
      +		    if (useBatch) {
      68bf46
      +			ps.addBatch();
      68bf46
      +		    }
      68bf46
      +		} catch (SQLException e) {
      68bf46
      +		    errorHandler.error("Failed to append parameters", e, ErrorCode.FLUSH_FAILURE);
      68bf46
      +		}
      68bf46
      +	    }
      68bf46
      +	    if (useBatch) {
      68bf46
      +		ps.executeBatch();
      68bf46
      +	    } else {
      68bf46
      +		ps.execute();
      68bf46
      +	    }
      68bf46
      +	} finally {
      68bf46
      +	    try {
      68bf46
      +		ps.close();
      68bf46
      +	    } catch (SQLException ignored) {
      68bf46
      +	    }
      68bf46
      +	}
      68bf46
      +    }
      68bf46
       
      68bf46
      -  public void setPassword(String password) {
      68bf46
      -    databasePassword = password;
      68bf46
      -  }
      68bf46
      +    /** closes the appender before disposal */
      68bf46
      +    public void finalize() {
      68bf46
      +	close();
      68bf46
      +    }
      68bf46
       
      68bf46
      +    /**
      68bf46
      +     * JDBCAppender requires a layout.
      68bf46
      +     */
      68bf46
      +    public boolean requiresLayout() {
      68bf46
      +	return true;
      68bf46
      +    }
      68bf46
       
      68bf46
      -  public void setBufferSize(int newBufferSize) {
      68bf46
      -    bufferSize = newBufferSize;
      68bf46
      -    buffer.ensureCapacity(bufferSize);
      68bf46
      -    removes.ensureCapacity(bufferSize);
      68bf46
      -  }
      68bf46
      +    /**
      68bf46
      +     *
      68bf46
      +     */
      68bf46
      +    public void setSql(String s) {
      68bf46
      +	sqlStatement = s;
      68bf46
      +	if (getLayout() == null) {
      68bf46
      +	    this.setLayout(new PatternLayout(s));
      68bf46
      +	} else {
      68bf46
      +	    ((PatternLayout) getLayout()).setConversionPattern(s);
      68bf46
      +	}
      68bf46
      +    }
      68bf46
       
      68bf46
      +    /**
      68bf46
      +     * Returns pre-formated statement eg: insert into LogTable (msg) values ("%m")
      68bf46
      +     */
      68bf46
      +    public String getSql() {
      68bf46
      +	return sqlStatement;
      68bf46
      +    }
      68bf46
       
      68bf46
      -  public String getUser() {
      68bf46
      -    return databaseUser;
      68bf46
      -  }
      68bf46
      +    public void setUser(String user) {
      68bf46
      +	databaseUser = user;
      68bf46
      +    }
      68bf46
       
      68bf46
      +    public void setURL(String url) {
      68bf46
      +	databaseURL = url;
      68bf46
      +    }
      68bf46
       
      68bf46
      -  public String getURL() {
      68bf46
      -    return databaseURL;
      68bf46
      -  }
      68bf46
      +    public void setPassword(String password) {
      68bf46
      +	databasePassword = password;
      68bf46
      +    }
      68bf46
       
      68bf46
      +    public void setBufferSize(int newBufferSize) {
      68bf46
      +	bufferSize = newBufferSize;
      68bf46
      +	buffer.ensureCapacity(bufferSize);
      68bf46
      +    }
      68bf46
       
      68bf46
      -  public String getPassword() {
      68bf46
      -    return databasePassword;
      68bf46
      -  }
      68bf46
      +    public String getUser() {
      68bf46
      +	return databaseUser;
      68bf46
      +    }
      68bf46
       
      68bf46
      +    public String getURL() {
      68bf46
      +	return databaseURL;
      68bf46
      +    }
      68bf46
       
      68bf46
      -  public int getBufferSize() {
      68bf46
      -    return bufferSize;
      68bf46
      -  }
      68bf46
      +    public String getPassword() {
      68bf46
      +	return databasePassword;
      68bf46
      +    }
      68bf46
       
      68bf46
      +    public int getBufferSize() {
      68bf46
      +	return bufferSize;
      68bf46
      +    }
      68bf46
       
      68bf46
      -  /**
      68bf46
      -   * Ensures that the given driver class has been loaded for sql connection
      68bf46
      -   * creation.
      68bf46
      -   */
      68bf46
      -  public void setDriver(String driverClass) {
      68bf46
      -    try {
      68bf46
      -      Class.forName(driverClass);
      68bf46
      -    } catch (Exception e) {
      68bf46
      -      errorHandler.error("Failed to load driver", e,
      68bf46
      -			 ErrorCode.GENERIC_FAILURE);
      68bf46
      +    /**
      68bf46
      +     * Ensures that the given driver class has been loaded for sql connection
      68bf46
      +     * creation.
      68bf46
      +     */
      68bf46
      +    public void setDriver(String driverClass) {
      68bf46
      +	try {
      68bf46
      +	    Class.forName(driverClass);
      68bf46
      +	} catch (Exception e) {
      68bf46
      +	    errorHandler.error("Failed to load driver", e, ErrorCode.GENERIC_FAILURE);
      68bf46
      +	}
      68bf46
           }
      68bf46
      -  }
      68bf46
       }
      68bf46
      -
      68bf46
      diff --git a/src/main/java/org/apache/log4j/jdbc/JdbcPatternParser.java b/src/main/java/org/apache/log4j/jdbc/JdbcPatternParser.java
      68bf46
      new file mode 100644
      68bf46
      index 00000000..d6807dee
      68bf46
      --- /dev/null
      68bf46
      +++ b/src/main/java/org/apache/log4j/jdbc/JdbcPatternParser.java
      68bf46
      @@ -0,0 +1,115 @@
      68bf46
      +/*
      68bf46
      + * Licensed to the Apache Software Foundation (ASF) under one or more
      68bf46
      + * contributor license agreements.  See the NOTICE file distributed with
      68bf46
      + * this work for additional information regarding copyright ownership.
      68bf46
      + * The ASF licenses this file to You under the Apache License, Version 2.0
      68bf46
      + * (the "License"); you may not use this file except in compliance with
      68bf46
      + * the License.  You may obtain a copy of the License at
      68bf46
      + *
      68bf46
      + *      http://www.apache.org/licenses/LICENSE-2.0
      68bf46
      + *
      68bf46
      + * Unless required by applicable law or agreed to in writing, software
      68bf46
      + * distributed under the License is distributed on an "AS IS" BASIS,
      68bf46
      + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
      68bf46
      + * See the License for the specific language governing permissions and
      68bf46
      + * limitations under the License.
      68bf46
      + */
      68bf46
      +package org.apache.log4j.jdbc;
      68bf46
      +
      68bf46
      +import org.apache.log4j.helpers.PatternConverter;
      68bf46
      +import org.apache.log4j.helpers.PatternParser;
      68bf46
      +import org.apache.log4j.spi.LoggingEvent;
      68bf46
      +
      68bf46
      +import java.sql.PreparedStatement;
      68bf46
      +import java.sql.SQLException;
      68bf46
      +import java.util.ArrayList;
      68bf46
      +import java.util.Collections;
      68bf46
      +import java.util.List;
      68bf46
      +import java.util.regex.Matcher;
      68bf46
      +import java.util.regex.Pattern;
      68bf46
      +
      68bf46
      +class JdbcPatternParser {
      68bf46
      +    private static final String QUESTION_MARK = "?";
      68bf46
      +
      68bf46
      +    private static final char PERCENT_CHAR = '%';
      68bf46
      +
      68bf46
      +    private final static Pattern STRING_LITERAL_PATTERN = Pattern.compile("'((?>[^']|'')+)'");
      68bf46
      +    // NOTE: capturing group work seem to work just as well.
      68bf46
      +    //private final static Pattern STRING_LITERAL_PATTERN = Pattern.compile("'(([^']|'')+)'");
      68bf46
      +
      68bf46
      +    private String parameterizedSql;
      68bf46
      +    final private List<String> patternStringRepresentationList = new ArrayList<String>();
      68bf46
      +    final private List<PatternConverter> args = new ArrayList<PatternConverter>();
      68bf46
      +
      68bf46
      +    JdbcPatternParser(String insertString) {
      68bf46
      +	init(insertString);
      68bf46
      +    }
      68bf46
      +
      68bf46
      +    public String getParameterizedSql() {
      68bf46
      +	return parameterizedSql;
      68bf46
      +    }
      68bf46
      +
      68bf46
      +    public List<String> getUnmodifiablePatternStringRepresentationList() {
      68bf46
      +	return Collections.unmodifiableList(patternStringRepresentationList);
      68bf46
      +    }
      68bf46
      +
      68bf46
      +    @Override
      68bf46
      +    public String toString() {
      68bf46
      +	return "JdbcPatternParser{sql=" + parameterizedSql + ",args=" + patternStringRepresentationList + "}";
      68bf46
      +    }
      68bf46
      +
      68bf46
      +    /**
      68bf46
      +     * Converts '....' literals into bind variables in JDBC.
      68bf46
      +     */
      68bf46
      +    private void init(String insertString) {
      68bf46
      +	if (insertString == null) {
      68bf46
      +	    throw new IllegalArgumentException("Null pattern");
      68bf46
      +	}
      68bf46
      +
      68bf46
      +	Matcher m = STRING_LITERAL_PATTERN.matcher(insertString);
      68bf46
      +	StringBuffer sb = new StringBuffer();
      68bf46
      +	while (m.find()) {
      68bf46
      +	    String matchedStr = m.group(1);
      68bf46
      +	    if (matchedStr.indexOf(PERCENT_CHAR) == -1) {
      68bf46
      +		replaceWithMatchedStr(m, sb);
      68bf46
      +	    } else {
      68bf46
      +		// Replace with bind
      68bf46
      +		replaceWithBind(m, sb, matchedStr);
      68bf46
      +	    }
      68bf46
      +	}
      68bf46
      +	m.appendTail(sb);
      68bf46
      +	this.parameterizedSql = sb.toString();
      68bf46
      +    }
      68bf46
      +
      68bf46
      +    private void replaceWithMatchedStr(Matcher m, StringBuffer sb) {
      68bf46
      +	// Just literal, append it as is
      68bf46
      +	m.appendReplacement(sb, "'$1'");
      68bf46
      +    }
      68bf46
      +
      68bf46
      +    private void replaceWithBind(Matcher m, StringBuffer sb, String matchedStr) {
      68bf46
      +	m.appendReplacement(sb, QUESTION_MARK);
      68bf46
      +	// We will use prepared statements, so we don't need to escape quotes.
      68bf46
      +	// And we assume the users had 'That''s a string with quotes' in their configs.
      68bf46
      +	matchedStr = matchedStr.replaceAll("''", "'");
      68bf46
      +	patternStringRepresentationList.add(matchedStr);
      68bf46
      +	args.add(new PatternParser(matchedStr).parse());
      68bf46
      +    }
      68bf46
      +
      68bf46
      +    public void setParameters(PreparedStatement ps, LoggingEvent logEvent) throws SQLException {
      68bf46
      +	for (int i = 0; i < args.size(); i++) {
      68bf46
      +	    final PatternConverter head = args.get(i);
      68bf46
      +	    String value = buildValueStr(logEvent, head);
      68bf46
      +	    ps.setString(i + 1, value);
      68bf46
      +	}
      68bf46
      +    }
      68bf46
      +
      68bf46
      +    private String buildValueStr(LoggingEvent logEvent, final PatternConverter head) {
      68bf46
      +	StringBuffer buffer = new StringBuffer();
      68bf46
      +	PatternConverter c = head;
      68bf46
      +	while (c != null) {
      68bf46
      +	    c.format(buffer, logEvent);
      68bf46
      +	    c = c.next;
      68bf46
      +	}
      68bf46
      +	return buffer.toString();
      68bf46
      +    }
      68bf46
      +}
      68bf46
      -- 
      68bf46
      2.33.1
      68bf46