diff --git a/SOURCES/0001-Backport-fix-for-CVE-2017-5645.patch b/SOURCES/0001-Backport-fix-for-CVE-2017-5645.patch
index 704ecfc..acb6e3d 100644
--- a/SOURCES/0001-Backport-fix-for-CVE-2017-5645.patch
+++ b/SOURCES/0001-Backport-fix-for-CVE-2017-5645.patch
@@ -1,20 +1,20 @@
-From ea4609eca531916ac347686c048bebdb7b4b6e0d Mon Sep 17 00:00:00 2001
+From 39f12f04d0b138377194b7aa43aa16a2ff401f98 Mon Sep 17 00:00:00 2001
 From: Michael Simacek <msimacek@redhat.com>
 Date: Fri, 2 Jun 2017 14:37:35 +0200
 Subject: [PATCH] Backport fix for CVE-2017-5645
 
 ---
- .../apache/log4j/FilteredObjectInputStream.java    | 65 ++++++++++++++++++++++
- src/main/java/org/apache/log4j/net/SocketNode.java | 17 +++++-
- 2 files changed, 80 insertions(+), 2 deletions(-)
+ .../log4j/FilteredObjectInputStream.java      | 76 +++++++++++++++++++
+ .../java/org/apache/log4j/net/SocketNode.java |  9 ++-
+ 2 files changed, 83 insertions(+), 2 deletions(-)
  create mode 100644 src/main/java/org/apache/log4j/FilteredObjectInputStream.java
 
 diff --git a/src/main/java/org/apache/log4j/FilteredObjectInputStream.java b/src/main/java/org/apache/log4j/FilteredObjectInputStream.java
 new file mode 100644
-index 0000000..b9ef20c
+index 00000000..ea597308
 --- /dev/null
 +++ b/src/main/java/org/apache/log4j/FilteredObjectInputStream.java
-@@ -0,0 +1,65 @@
+@@ -0,0 +1,76 @@
 +/*
 + * Licensed to the Apache Software Foundation (ASF) under one or more
 + * contributor license agreements. See the NOTICE file distributed with
@@ -33,15 +33,16 @@ index 0000000..b9ef20c
 + */
 +package org.apache.log4j;
 +
-+import java.io.FileOutputStream;
 +import java.io.IOException;
 +import java.io.InputStream;
 +import java.io.InvalidObjectException;
 +import java.io.ObjectInputStream;
 +import java.io.ObjectStreamClass;
++import java.util.ArrayList;
 +import java.util.Arrays;
 +import java.util.Collection;
-+import java.util.List;
++import java.util.HashSet;
++import java.util.Set;
 +
 +/**
 + * Extended ObjectInputStream that only allows certain classes to be deserialized.
@@ -50,13 +51,15 @@ index 0000000..b9ef20c
 + */
 +public class FilteredObjectInputStream extends ObjectInputStream {
 +
-+    private static final List REQUIRED_JAVA_CLASSES = Arrays.asList(new String[] {
++    private static final Set REQUIRED_JAVA_CLASSES = new HashSet(Arrays.asList(new String[] {
 +        // Types of non-trainsient fields of LoggingEvent
 +        "java.lang.String",
 +        "java.util.Hashtable",
 +        // ThrowableInformation
 +        "[Ljava.lang.String;"
-+    });
++    }));
++
++    public static final Collection SYSTEM_ALLOWED_CLASSES = getAllowedClasses();
 +
 +    private final Collection allowedClasses;
 +
@@ -73,6 +76,14 @@ index 0000000..b9ef20c
 +        return super.resolveClass(desc);
 +    }
 +
++    private static Collection getAllowedClasses() {
++        Collection allowedClasses = new HashSet();
++        String property = System.getProperty("org.apache.log4j.net.allowedClasses");
++        if (property != null)
++            allowedClasses.addAll(Arrays.asList(property.split(",")));
++        return allowedClasses;
++    }
++
 +    private static boolean isAllowedByDefault(final String name) {
 +        return name.startsWith("org.apache.log4j.") ||
 +            name.startsWith("[Lorg.apache.log4j.") ||
@@ -81,7 +92,7 @@ index 0000000..b9ef20c
 +
 +}
 diff --git a/src/main/java/org/apache/log4j/net/SocketNode.java b/src/main/java/org/apache/log4j/net/SocketNode.java
-index e977f13..f95bb10 100644
+index e977f133..692da289 100644
 --- a/src/main/java/org/apache/log4j/net/SocketNode.java
 +++ b/src/main/java/org/apache/log4j/net/SocketNode.java
 @@ -22,6 +22,10 @@ import java.io.IOException;
@@ -103,25 +114,10 @@ index e977f13..f95bb10 100644
 -                         new BufferedInputStream(socket.getInputStream()));
 +      ois = new FilteredObjectInputStream(
 +                         new BufferedInputStream(socket.getInputStream()),
-+                         getAllowedClasses());
++                         FilteredObjectInputStream.SYSTEM_ALLOWED_CLASSES);
      } catch(InterruptedIOException e) {
        Thread.currentThread().interrupt();
        logger.error("Could not open ObjectInputStream to "+socket, e);
-@@ -65,6 +70,14 @@ public class SocketNode implements Runnable {
-     }
-   }
- 
-+  private Collection getAllowedClasses() {
-+      Collection allowedClasses = new ArrayList();
-+      String property = System.getProperty("org.apache.log4j.net.allowedClasses");
-+      if (property != null)
-+          allowedClasses.addAll(Arrays.asList(property.split(",")));
-+      return allowedClasses;
-+  }
-+
-   //public
-   //void finalize() {
-   //System.err.println("-------------------------Finalize called");
 -- 
-2.9.4
+2.33.1
 
diff --git a/SOURCES/0001-Fix-CVE-2022-23302-JMSSink.patch b/SOURCES/0001-Fix-CVE-2022-23302-JMSSink.patch
new file mode 100644
index 0000000..2ca8290
--- /dev/null
+++ b/SOURCES/0001-Fix-CVE-2022-23302-JMSSink.patch
@@ -0,0 +1,172 @@
+From 70345b5e5a6ad37399911194f0b746094061b399 Mon Sep 17 00:00:00 2001
+From: Mikolaj Izdebski <mizdebsk@redhat.com>
+Date: Wed, 2 Feb 2022 20:07:09 +0100
+Subject: [PATCH] Fix CVE-2022-23302 JMSSink
+
+---
+ .../java/org/apache/log4j/net/JMSSink.java    | 153 ------------------
+ 1 file changed, 153 deletions(-)
+ delete mode 100644 src/main/java/org/apache/log4j/net/JMSSink.java
+
+diff --git a/src/main/java/org/apache/log4j/net/JMSSink.java b/src/main/java/org/apache/log4j/net/JMSSink.java
+deleted file mode 100644
+index 6a02831e..00000000
+--- a/src/main/java/org/apache/log4j/net/JMSSink.java
++++ /dev/null
+@@ -1,153 +0,0 @@
+-/*
+- * Licensed to the Apache Software Foundation (ASF) under one or more
+- * contributor license agreements.  See the NOTICE file distributed with
+- * this work for additional information regarding copyright ownership.
+- * The ASF licenses this file to You under the Apache License, Version 2.0
+- * (the "License"); you may not use this file except in compliance with
+- * the License.  You may obtain a copy of the License at
+- * 
+- *      http://www.apache.org/licenses/LICENSE-2.0
+- * 
+- * Unless required by applicable law or agreed to in writing, software
+- * distributed under the License is distributed on an "AS IS" BASIS,
+- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+- * See the License for the specific language governing permissions and
+- * limitations under the License.
+- */
+-
+-package org.apache.log4j.net;
+-
+-import org.apache.log4j.Logger;
+-import org.apache.log4j.PropertyConfigurator;
+-import org.apache.log4j.spi.LoggingEvent;
+-import org.apache.log4j.xml.DOMConfigurator;
+-
+-import javax.jms.JMSException;
+-import javax.jms.ObjectMessage;
+-import javax.jms.Session;
+-import javax.jms.Topic;
+-import javax.jms.TopicConnection;
+-import javax.jms.TopicConnectionFactory;
+-import javax.jms.TopicSession;
+-import javax.jms.TopicSubscriber;
+-import javax.naming.Context;
+-import javax.naming.InitialContext;
+-import javax.naming.NameNotFoundException;
+-import javax.naming.NamingException;
+-import java.io.BufferedReader;
+-import java.io.InputStreamReader;
+-
+-/**
+- * A simple application that consumes logging events sent by a {@link
+- * JMSAppender}.
+- *
+- *
+- * @author Ceki G&uuml;lc&uuml; 
+- * */
+-public class JMSSink implements javax.jms.MessageListener {
+-
+-  static Logger logger = Logger.getLogger(JMSSink.class);
+-
+-  static public void main(String[] args) throws Exception {
+-    if(args.length != 5) {
+-      usage("Wrong number of arguments.");
+-    }
+-    
+-    String tcfBindingName = args[0];
+-    String topicBindingName = args[1];
+-    String username = args[2];
+-    String password = args[3];
+-    
+-    
+-    String configFile = args[4];
+-
+-    if(configFile.endsWith(".xml")) {
+-      DOMConfigurator.configure(configFile);
+-    } else {
+-      PropertyConfigurator.configure(configFile);
+-    }
+-    
+-    new JMSSink(tcfBindingName, topicBindingName, username, password);
+-
+-    BufferedReader stdin = new BufferedReader(new InputStreamReader(System.in));
+-    // Loop until the word "exit" is typed
+-    System.out.println("Type \"exit\" to quit JMSSink.");
+-    while(true){
+-      String s = stdin.readLine( );
+-      if (s.equalsIgnoreCase("exit")) {
+-	System.out.println("Exiting. Kill the application if it does not exit "
+-			   + "due to daemon threads.");
+-	return; 
+-      }
+-    } 
+-  }
+-
+-  public JMSSink( String tcfBindingName, String topicBindingName, String username,
+-		  String password) {
+-    
+-    try {
+-      Context ctx = new InitialContext();
+-      TopicConnectionFactory topicConnectionFactory;
+-      topicConnectionFactory = (TopicConnectionFactory) lookup(ctx,
+-                                                               tcfBindingName);
+-
+-      TopicConnection topicConnection =
+-	                        topicConnectionFactory.createTopicConnection(username,
+-									     password);
+-      topicConnection.start();
+-
+-      TopicSession topicSession = topicConnection.createTopicSession(false,
+-                                                       Session.AUTO_ACKNOWLEDGE);
+-
+-      Topic topic = (Topic)ctx.lookup(topicBindingName);
+-
+-      TopicSubscriber topicSubscriber = topicSession.createSubscriber(topic);
+-    
+-      topicSubscriber.setMessageListener(this);
+-
+-    } catch(JMSException e) {
+-      logger.error("Could not read JMS message.", e);
+-    } catch(NamingException e) {
+-      logger.error("Could not read JMS message.", e);
+-    } catch(RuntimeException e) {
+-      logger.error("Could not read JMS message.", e);
+-    }
+-  }
+-
+-  public void onMessage(javax.jms.Message message) {
+-    LoggingEvent event;
+-    Logger remoteLogger;
+-
+-    try {
+-      if(message instanceof  ObjectMessage) {
+-	ObjectMessage objectMessage = (ObjectMessage) message;
+-	event = (LoggingEvent) objectMessage.getObject();
+-	remoteLogger = Logger.getLogger(event.getLoggerName());
+-	remoteLogger.callAppenders(event);
+-      } else {
+-	logger.warn("Received message is of type "+message.getJMSType()
+-		    +", was expecting ObjectMessage.");
+-      }      
+-    } catch(JMSException jmse) {
+-      logger.error("Exception thrown while processing incoming message.", 
+-		   jmse);
+-    }
+-  }
+-
+-
+-  protected static Object lookup(Context ctx, String name) throws NamingException {
+-    try {
+-      return ctx.lookup(name);
+-    } catch(NameNotFoundException e) {
+-      logger.error("Could not find name ["+name+"].");
+-      throw e;
+-    }
+-  }
+-
+-  static void usage(String msg) {
+-    System.err.println(msg);
+-    System.err.println("Usage: java " + JMSSink.class.getName()
+-            + " TopicConnectionFactoryBindingName TopicBindingName username password configFile");
+-    System.exit(1);
+-  }
+-}
+-- 
+2.33.1
+
diff --git a/SOURCES/0001-Fix-CVE-2022-23305-JDBCAppender.patch b/SOURCES/0001-Fix-CVE-2022-23305-JDBCAppender.patch
new file mode 100644
index 0000000..67b8c09
--- /dev/null
+++ b/SOURCES/0001-Fix-CVE-2022-23305-JDBCAppender.patch
@@ -0,0 +1,1895 @@
+From 6370372f5a04cdec6598fa8199b762ce33fa4d40 Mon Sep 17 00:00:00 2001
+From: Mikolaj Izdebski <mizdebsk@redhat.com>
+Date: Wed, 2 Feb 2022 19:37:17 +0100
+Subject: [PATCH] Fix CVE-2022-23305 JDBCAppender
+
+---
+ .../apache/log4j/helpers/PatternParser.java   | 944 +++++++++---------
+ .../org/apache/log4j/jdbc/JDBCAppender.java   | 715 ++++++-------
+ .../apache/log4j/jdbc/JdbcPatternParser.java  | 115 +++
+ 3 files changed, 942 insertions(+), 832 deletions(-)
+ create mode 100644 src/main/java/org/apache/log4j/jdbc/JdbcPatternParser.java
+
+diff --git a/src/main/java/org/apache/log4j/helpers/PatternParser.java b/src/main/java/org/apache/log4j/helpers/PatternParser.java
+index 0d3ead67..4b169a2d 100644
+--- a/src/main/java/org/apache/log4j/helpers/PatternParser.java
++++ b/src/main/java/org/apache/log4j/helpers/PatternParser.java
+@@ -30,541 +30,503 @@ import java.util.Arrays;
+ //                 Reinhard Deschler <reinhard.deschler@web.de>
+ 
+ /**
+-   Most of the work of the {@link org.apache.log4j.PatternLayout} class
+-   is delegated to the PatternParser class.
+-
+-   <p>It is this class that parses conversion patterns and creates
+-   a chained list of {@link OptionConverter OptionConverters}.
+-
+-   @author <a href=mailto:"cakalijp@Maritz.com">James P. Cakalic</a>
+-   @author Ceki G&uuml;lc&uuml;
+-   @author Anders Kristensen
+-
+-   @since 0.8.2
+-*/
++ * Most of the work of the {@link org.apache.log4j.PatternLayout} class is
++ * delegated to the PatternParser class.
++ * 
++ * <p>
++ * It is this class that parses conversion patterns and creates a chained list
++ * of {@link OptionConverter OptionConverters}.
++ * 
++ * @author James P. Cakalic
++ * @author Ceki G&uuml;lc&uuml;
++ * @author Anders Kristensen
++ * 
++ * @since 0.8.2
++ */
+ public class PatternParser {
+ 
+-  private static final char ESCAPE_CHAR = '%';
+-
+-  private static final int LITERAL_STATE = 0;
+-  private static final int CONVERTER_STATE = 1;
+-  private static final int DOT_STATE = 3;
+-  private static final int MIN_STATE = 4;
+-  private static final int MAX_STATE = 5;
+-
+-  static final int FULL_LOCATION_CONVERTER = 1000;
+-  static final int METHOD_LOCATION_CONVERTER = 1001;
+-  static final int CLASS_LOCATION_CONVERTER = 1002;
+-  static final int LINE_LOCATION_CONVERTER = 1003;
+-  static final int FILE_LOCATION_CONVERTER = 1004;
+-
+-  static final int RELATIVE_TIME_CONVERTER = 2000;
+-  static final int THREAD_CONVERTER = 2001;
+-  static final int LEVEL_CONVERTER = 2002;
+-  static final int NDC_CONVERTER = 2003;
+-  static final int MESSAGE_CONVERTER = 2004;
+-
+-  int state;
+-  protected StringBuffer currentLiteral = new StringBuffer(32);
+-  protected int patternLength;
+-  protected int i;
+-  PatternConverter head;
+-  PatternConverter tail;
+-  protected FormattingInfo formattingInfo = new FormattingInfo();
+-  protected String pattern;
+-
+-  public
+-  PatternParser(String pattern) {
+-    this.pattern = pattern;
+-    patternLength =  pattern.length();
+-    state = LITERAL_STATE;
+-  }
+-
+-  private
+-  void  addToList(PatternConverter pc) {
+-    if(head == null) {
+-      head = tail = pc;
+-    } else {
+-      tail.next = pc;
+-      tail = pc;
++    private static final char LEFT_BRACKET = '{';
++    private static final char RIGHT_BRACKET = '}';
++    private static final char N_CHAR = 'n';
++    private static final char DOT_CHAR = '.';
++    private static final char DASH_CHAR = '-';
++    private static final char ESCAPE_CHAR = '%';
++
++    private static final int LITERAL_STATE = 0;
++    private static final int CONVERTER_STATE = 1;
++    private static final int DOT_STATE = 3;
++    private static final int MIN_STATE = 4;
++    private static final int MAX_STATE = 5;
++
++    static final int FULL_LOCATION_CONVERTER = 1000;
++    static final int METHOD_LOCATION_CONVERTER = 1001;
++    static final int CLASS_LOCATION_CONVERTER = 1002;
++    static final int LINE_LOCATION_CONVERTER = 1003;
++    static final int FILE_LOCATION_CONVERTER = 1004;
++
++    static final int RELATIVE_TIME_CONVERTER = 2000;
++    static final int THREAD_CONVERTER = 2001;
++    static final int LEVEL_CONVERTER = 2002;
++    static final int NDC_CONVERTER = 2003;
++    static final int MESSAGE_CONVERTER = 2004;
++
++    int state;
++    protected StringBuffer currentLiteral = new StringBuffer(32);
++    protected int patternLength;
++    protected int i;
++    PatternConverter head;
++    PatternConverter tail;
++    protected FormattingInfo formattingInfo = new FormattingInfo();
++    protected String pattern;
++
++    public PatternParser(String pattern) {
++	this.pattern = pattern;
++	patternLength = pattern.length();
++	state = LITERAL_STATE;
+     }
+-  }
+-
+-  protected
+-  String extractOption() {
+-    if((i < patternLength) && (pattern.charAt(i) == '{')) {
+-      int end = pattern.indexOf('}', i);
+-      if (end > i) {
+-	String r = pattern.substring(i + 1, end);
+-	i = end+1;
+-	return r;
+-      }
+-    }
+-    return null;
+-  }
+-
+-
+-  /**
+-     The option is expected to be in decimal and positive. In case of
+-     error, zero is returned.  */
+-  protected
+-  int extractPrecisionOption() {
+-    String opt = extractOption();
+-    int r = 0;
+-    if(opt != null) {
+-      try {
+-	r = Integer.parseInt(opt);
+-	if(r <= 0) {
+-	    LogLog.error(
+-	        "Precision option (" + opt + ") isn't a positive integer.");
+-	    r = 0;
++
++    private void addToList(PatternConverter pc) {
++	if (head == null) {
++	    head = tail = pc;
++	} else {
++	    tail.next = pc;
++	    tail = pc;
+ 	}
+-      }
+-      catch (NumberFormatException e) {
+-	LogLog.error("Category option \""+opt+"\" not a decimal integer.", e);
+-      }
+     }
+-    return r;
+-  }
+-
+-  public
+-  PatternConverter parse() {
+-    char c;
+-    i = 0;
+-    while(i < patternLength) {
+-      c = pattern.charAt(i++);
+-      switch(state) {
+-      case LITERAL_STATE:
+-        // In literal state, the last char is always a literal.
+-        if(i == patternLength) {
+-          currentLiteral.append(c);
+-          continue;
+-        }
+-        if(c == ESCAPE_CHAR) {
+-          // peek at the next char.
+-          switch(pattern.charAt(i)) {
+-          case ESCAPE_CHAR:
+-            currentLiteral.append(c);
+-            i++; // move pointer
+-            break;
+-          case 'n':
+-            currentLiteral.append(Layout.LINE_SEP);
+-            i++; // move pointer
+-            break;
+-          default:
+-            if(currentLiteral.length() != 0) {
+-              addToList(new LiteralPatternConverter(
+-                                                  currentLiteral.toString()));
+-              //LogLog.debug("Parsed LITERAL converter: \""
+-              //           +currentLiteral+"\".");
+-            }
+-            currentLiteral.setLength(0);
+-            currentLiteral.append(c); // append %
+-            state = CONVERTER_STATE;
+-            formattingInfo.reset();
+-          }
+-        }
+-        else {
+-          currentLiteral.append(c);
+-        }
+-        break;
+-      case CONVERTER_STATE:
+-	currentLiteral.append(c);
+-	switch(c) {
+-	case '-':
+-	  formattingInfo.leftAlign = true;
+-	  break;
+-	case '.':
+-	  state = DOT_STATE;
+-	  break;
+-	default:
+-	  if(c >= '0' && c <= '9') {
+-	    formattingInfo.min = c - '0';
+-	    state = MIN_STATE;
+-	  }
+-	  else
+-	    finalizeConverter(c);
+-	} // switch
+-	break;
+-      case MIN_STATE:
+-	currentLiteral.append(c);
+-	if(c >= '0' && c <= '9')
+-	  formattingInfo.min = formattingInfo.min*10 + (c - '0');
+-	else if(c == '.')
+-	  state = DOT_STATE;
+-	else {
+-	  finalizeConverter(c);
+-	}
+-	break;
+-      case DOT_STATE:
+-	currentLiteral.append(c);
+-	if(c >= '0' && c <= '9') {
+-	  formattingInfo.max = c - '0';
+-	   state = MAX_STATE;
+-	}
+-	else {
+-	  LogLog.error("Error occured in position "+i
+-		     +".\n Was expecting digit, instead got char \""+c+"\".");
+-	  state = LITERAL_STATE;
+-	}
+-	break;
+-      case MAX_STATE:
+-	currentLiteral.append(c);
+-	if(c >= '0' && c <= '9')
+-	  formattingInfo.max = formattingInfo.max*10 + (c - '0');
+-	else {
+-	  finalizeConverter(c);
+-	  state = LITERAL_STATE;
++
++    protected String extractOption() {
++	if ((i < patternLength) && (pattern.charAt(i) == LEFT_BRACKET)) {
++	    int end = pattern.indexOf(RIGHT_BRACKET, i);
++	    if (end > i) {
++		String r = pattern.substring(i + 1, end);
++		i = end + 1;
++		return r;
++	    }
+ 	}
+-	break;
+-      } // switch
+-    } // while
+-    if(currentLiteral.length() != 0) {
+-      addToList(new LiteralPatternConverter(currentLiteral.toString()));
+-      //LogLog.debug("Parsed LITERAL converter: \""+currentLiteral+"\".");
++	return null;
+     }
+-    return head;
+-  }
+-
+-  protected
+-  void finalizeConverter(char c) {
+-    PatternConverter pc = null;
+-    switch(c) {
+-    case 'c':
+-      pc = new CategoryPatternConverter(formattingInfo,
+-					extractPrecisionOption());
+-      //LogLog.debug("CATEGORY converter.");
+-      //formattingInfo.dump();
+-      currentLiteral.setLength(0);
+-      break;
+-    case 'C':
+-      pc = new ClassNamePatternConverter(formattingInfo,
+-					 extractPrecisionOption());
+-      //LogLog.debug("CLASS_NAME converter.");
+-      //formattingInfo.dump();
+-      currentLiteral.setLength(0);
+-      break;
+-    case 'd':
+-      String dateFormatStr = AbsoluteTimeDateFormat.ISO8601_DATE_FORMAT;
+-      DateFormat df;
+-      String dOpt = extractOption();
+-      if(dOpt != null)
+-	dateFormatStr = dOpt;
+-
+-      if(dateFormatStr.equalsIgnoreCase(
+-                                    AbsoluteTimeDateFormat.ISO8601_DATE_FORMAT))
+-	df = new  ISO8601DateFormat();
+-      else if(dateFormatStr.equalsIgnoreCase(
+-                                   AbsoluteTimeDateFormat.ABS_TIME_DATE_FORMAT))
+-	df = new AbsoluteTimeDateFormat();
+-      else if(dateFormatStr.equalsIgnoreCase(
+-                              AbsoluteTimeDateFormat.DATE_AND_TIME_DATE_FORMAT))
+-	df = new DateTimeDateFormat();
+-      else {
+-	try {
+-	  df = new SimpleDateFormat(dateFormatStr);
+-	}
+-	catch (IllegalArgumentException e) {
+-	  LogLog.error("Could not instantiate SimpleDateFormat with " +
+-		       dateFormatStr, e);
+-	  df = (DateFormat) OptionConverter.instantiateByClassName(
+-			           "org.apache.log4j.helpers.ISO8601DateFormat",
+-				   DateFormat.class, null);
++
++    /**
++     * The option is expected to be in decimal and positive. In case of error, zero
++     * is returned.
++     */
++    protected int extractPrecisionOption() {
++	String opt = extractOption();
++	int r = 0;
++	if (opt != null) {
++	    try {
++		r = Integer.parseInt(opt);
++		if (r <= 0) {
++		    LogLog.error("Precision option (" + opt + ") isn't a positive integer.");
++		    r = 0;
++		}
++	    } catch (NumberFormatException e) {
++		LogLog.error("Category option \"" + opt + "\" not a decimal integer.", e);
++	    }
+ 	}
+-      }
+-      pc = new DatePatternConverter(formattingInfo, df);
+-      //LogLog.debug("DATE converter {"+dateFormatStr+"}.");
+-      //formattingInfo.dump();
+-      currentLiteral.setLength(0);
+-      break;
+-    case 'F':
+-      pc = new LocationPatternConverter(formattingInfo,
+-					FILE_LOCATION_CONVERTER);
+-      //LogLog.debug("File name converter.");
+-      //formattingInfo.dump();
+-      currentLiteral.setLength(0);
+-      break;
+-    case 'l':
+-      pc = new LocationPatternConverter(formattingInfo,
+-					FULL_LOCATION_CONVERTER);
+-      //LogLog.debug("Location converter.");
+-      //formattingInfo.dump();
+-      currentLiteral.setLength(0);
+-      break;
+-    case 'L':
+-      pc = new LocationPatternConverter(formattingInfo,
+-					LINE_LOCATION_CONVERTER);
+-      //LogLog.debug("LINE NUMBER converter.");
+-      //formattingInfo.dump();
+-      currentLiteral.setLength(0);
+-      break;
+-    case 'm':
+-      pc = new BasicPatternConverter(formattingInfo, MESSAGE_CONVERTER);
+-      //LogLog.debug("MESSAGE converter.");
+-      //formattingInfo.dump();
+-      currentLiteral.setLength(0);
+-      break;
+-    case 'M':
+-      pc = new LocationPatternConverter(formattingInfo,
+-					METHOD_LOCATION_CONVERTER);
+-      //LogLog.debug("METHOD converter.");
+-      //formattingInfo.dump();
+-      currentLiteral.setLength(0);
+-      break;
+-    case 'p':
+-      pc = new BasicPatternConverter(formattingInfo, LEVEL_CONVERTER);
+-      //LogLog.debug("LEVEL converter.");
+-      //formattingInfo.dump();
+-      currentLiteral.setLength(0);
+-      break;
+-    case 'r':
+-      pc = new BasicPatternConverter(formattingInfo,
+-					 RELATIVE_TIME_CONVERTER);
+-      //LogLog.debug("RELATIVE time converter.");
+-      //formattingInfo.dump();
+-      currentLiteral.setLength(0);
+-      break;
+-    case 't':
+-      pc = new BasicPatternConverter(formattingInfo, THREAD_CONVERTER);
+-      //LogLog.debug("THREAD converter.");
+-      //formattingInfo.dump();
+-      currentLiteral.setLength(0);
+-      break;
+-      /*case 'u':
+-      if(i < patternLength) {
+-	char cNext = pattern.charAt(i);
+-	if(cNext >= '0' && cNext <= '9') {
+-	  pc = new UserFieldPatternConverter(formattingInfo, cNext - '0');
+-	  LogLog.debug("USER converter ["+cNext+"].");
+-	  formattingInfo.dump();
+-	  currentLiteral.setLength(0);
+-	  i++;
++	return r;
++    }
++
++    public PatternConverter parse() {
++	char c;
++	i = 0;
++	while (i < patternLength) {
++	    c = pattern.charAt(i++);
++	    switch (state) {
++	    case LITERAL_STATE:
++		// In literal state, the last char is always a literal.
++		if (i == patternLength) {
++		    currentLiteral.append(c);
++		    continue;
++		}
++		if (c == ESCAPE_CHAR) {
++		    // peek at the next char.
++		    switch (pattern.charAt(i)) {
++		    case ESCAPE_CHAR:
++			currentLiteral.append(c);
++			i++; // move pointer
++			break;
++		    case N_CHAR:
++			currentLiteral.append(Layout.LINE_SEP);
++			i++; // move pointer
++			break;
++		    default:
++			if (currentLiteral.length() != 0) {
++			    addToList(new LiteralPatternConverter(currentLiteral.toString()));
++			    // LogLog.debug("Parsed LITERAL converter: \""
++			    // +currentLiteral+"\".");
++			}
++			currentLiteral.setLength(0);
++			currentLiteral.append(c); // append %
++			state = CONVERTER_STATE;
++			formattingInfo.reset();
++		    }
++		} else {
++		    currentLiteral.append(c);
++		}
++		break;
++	    case CONVERTER_STATE:
++		currentLiteral.append(c);
++		switch (c) {
++		case DASH_CHAR:
++		    formattingInfo.leftAlign = true;
++		    break;
++		case DOT_CHAR:
++		    state = DOT_STATE;
++		    break;
++		default:
++		    if (c >= '0' && c <= '9') {
++			formattingInfo.min = c - '0';
++			state = MIN_STATE;
++		    } else
++			finalizeConverter(c);
++		} // switch
++		break;
++	    case MIN_STATE:
++		currentLiteral.append(c);
++		if (c >= '0' && c <= '9')
++		    formattingInfo.min = formattingInfo.min * 10 + (c - '0');
++		else if (c == '.')
++		    state = DOT_STATE;
++		else {
++		    finalizeConverter(c);
++		}
++		break;
++	    case DOT_STATE:
++		currentLiteral.append(c);
++		if (c >= '0' && c <= '9') {
++		    formattingInfo.max = c - '0';
++		    state = MAX_STATE;
++		} else {
++		    LogLog.error("Error occured in position " + i + ".\n Was expecting digit, instead got char \"" + c
++			    + "\".");
++		    state = LITERAL_STATE;
++		}
++		break;
++	    case MAX_STATE:
++		currentLiteral.append(c);
++		if (c >= '0' && c <= '9')
++		    formattingInfo.max = formattingInfo.max * 10 + (c - '0');
++		else {
++		    finalizeConverter(c);
++		    state = LITERAL_STATE;
++		}
++		break;
++	    } // switch
++	} // while
++	if (currentLiteral.length() != 0) {
++	    addToList(new LiteralPatternConverter(currentLiteral.toString()));
++	    // LogLog.debug("Parsed LITERAL converter: \""+currentLiteral+"\".");
+ 	}
+-	else
+-	  LogLog.error("Unexpected char" +cNext+" at position "+i);
+-      }
+-      break;*/
+-    case 'x':
+-      pc = new BasicPatternConverter(formattingInfo, NDC_CONVERTER);
+-      //LogLog.debug("NDC converter.");
+-      currentLiteral.setLength(0);
+-      break;
+-    case 'X':
+-      String xOpt = extractOption();
+-      pc = new MDCPatternConverter(formattingInfo, xOpt);
+-      currentLiteral.setLength(0);
+-      break;
+-    default:
+-      LogLog.error("Unexpected char [" +c+"] at position "+i
+-		   +" in conversion patterrn.");
+-      pc = new LiteralPatternConverter(currentLiteral.toString());
+-      currentLiteral.setLength(0);
++	return head;
+     }
+ 
+-    addConverter(pc);
+-  }
+-
+-  protected
+-  void addConverter(PatternConverter pc) {
+-    currentLiteral.setLength(0);
+-    // Add the pattern converter to the list.
+-    addToList(pc);
+-    // Next pattern is assumed to be a literal.
+-    state = LITERAL_STATE;
+-    // Reset formatting info
+-    formattingInfo.reset();
+-  }
+-
+-  // ---------------------------------------------------------------------
+-  //                      PatternConverters
+-  // ---------------------------------------------------------------------
+-
+-  private static class BasicPatternConverter extends PatternConverter {
+-    int type;
+-
+-    BasicPatternConverter(FormattingInfo formattingInfo, int type) {
+-      super(formattingInfo);
+-      this.type = type;
++    protected void finalizeConverter(char c) {
++	PatternConverter pc = null;
++	switch (c) {
++	case 'c':
++	    pc = new CategoryPatternConverter(formattingInfo, extractPrecisionOption());
++	    // LogLog.debug("CATEGORY converter.");
++	    // formattingInfo.dump();
++	    currentLiteral.setLength(0);
++	    break;
++	case 'C':
++	    pc = new ClassNamePatternConverter(formattingInfo, extractPrecisionOption());
++	    // LogLog.debug("CLASS_NAME converter.");
++	    // formattingInfo.dump();
++	    currentLiteral.setLength(0);
++	    break;
++	case 'd':
++	    String dateFormatStr = AbsoluteTimeDateFormat.ISO8601_DATE_FORMAT;
++	    DateFormat df;
++	    String dOpt = extractOption();
++	    if (dOpt != null)
++		dateFormatStr = dOpt;
++
++	    if (dateFormatStr.equalsIgnoreCase(AbsoluteTimeDateFormat.ISO8601_DATE_FORMAT))
++		df = new ISO8601DateFormat();
++	    else if (dateFormatStr.equalsIgnoreCase(AbsoluteTimeDateFormat.ABS_TIME_DATE_FORMAT))
++		df = new AbsoluteTimeDateFormat();
++	    else if (dateFormatStr.equalsIgnoreCase(AbsoluteTimeDateFormat.DATE_AND_TIME_DATE_FORMAT))
++		df = new DateTimeDateFormat();
++	    else {
++		try {
++		    df = new SimpleDateFormat(dateFormatStr);
++		} catch (IllegalArgumentException e) {
++		    LogLog.error("Could not instantiate SimpleDateFormat with " + dateFormatStr, e);
++		    df = (DateFormat) OptionConverter.instantiateByClassName(
++			    "org.apache.log4j.helpers.ISO8601DateFormat", DateFormat.class, null);
++		}
++	    }
++	    pc = new DatePatternConverter(formattingInfo, df);
++	    // LogLog.debug("DATE converter {"+dateFormatStr+"}.");
++	    // formattingInfo.dump();
++	    currentLiteral.setLength(0);
++	    break;
++	case 'F':
++	    pc = new LocationPatternConverter(formattingInfo, FILE_LOCATION_CONVERTER);
++	    // LogLog.debug("File name converter.");
++	    // formattingInfo.dump();
++	    currentLiteral.setLength(0);
++	    break;
++	case 'l':
++	    pc = new LocationPatternConverter(formattingInfo, FULL_LOCATION_CONVERTER);
++	    // LogLog.debug("Location converter.");
++	    // formattingInfo.dump();
++	    currentLiteral.setLength(0);
++	    break;
++	case 'L':
++	    pc = new LocationPatternConverter(formattingInfo, LINE_LOCATION_CONVERTER);
++	    // LogLog.debug("LINE NUMBER converter.");
++	    // formattingInfo.dump();
++	    currentLiteral.setLength(0);
++	    break;
++	case 'm':
++	    pc = new BasicPatternConverter(formattingInfo, MESSAGE_CONVERTER);
++	    // LogLog.debug("MESSAGE converter.");
++	    // formattingInfo.dump();
++	    currentLiteral.setLength(0);
++	    break;
++	case 'M':
++	    pc = new LocationPatternConverter(formattingInfo, METHOD_LOCATION_CONVERTER);
++	    // LogLog.debug("METHOD converter.");
++	    // formattingInfo.dump();
++	    currentLiteral.setLength(0);
++	    break;
++	case 'p':
++	    pc = new BasicPatternConverter(formattingInfo, LEVEL_CONVERTER);
++	    // LogLog.debug("LEVEL converter.");
++	    // formattingInfo.dump();
++	    currentLiteral.setLength(0);
++	    break;
++	case 'r':
++	    pc = new BasicPatternConverter(formattingInfo, RELATIVE_TIME_CONVERTER);
++	    // LogLog.debug("RELATIVE time converter.");
++	    // formattingInfo.dump();
++	    currentLiteral.setLength(0);
++	    break;
++	case 't':
++	    pc = new BasicPatternConverter(formattingInfo, THREAD_CONVERTER);
++	    // LogLog.debug("THREAD converter.");
++	    // formattingInfo.dump();
++	    currentLiteral.setLength(0);
++	    break;
++	/*
++	 * case 'u': if(i < patternLength) { char cNext = pattern.charAt(i); if(cNext >=
++	 * '0' && cNext <= '9') { pc = new UserFieldPatternConverter(formattingInfo,
++	 * cNext - '0'); LogLog.debug("USER converter ["+cNext+"].");
++	 * formattingInfo.dump(); currentLiteral.setLength(0); i++; } else
++	 * LogLog.error("Unexpected char" +cNext+" at position "+i); } break;
++	 */
++	case 'x':
++	    pc = new BasicPatternConverter(formattingInfo, NDC_CONVERTER);
++	    // LogLog.debug("NDC converter.");
++	    currentLiteral.setLength(0);
++	    break;
++	case 'X':
++	    String xOpt = extractOption();
++	    pc = new MDCPatternConverter(formattingInfo, xOpt);
++	    currentLiteral.setLength(0);
++	    break;
++	default:
++	    LogLog.error("Unexpected char [" + c + "] at position " + i + " in conversion patterrn.");
++	    pc = new LiteralPatternConverter(currentLiteral.toString());
++	    currentLiteral.setLength(0);
++	}
++
++	addConverter(pc);
+     }
+ 
+-    public
+-    String convert(LoggingEvent event) {
+-      switch(type) {
+-      case RELATIVE_TIME_CONVERTER:
+-	return (Long.toString(event.timeStamp - LoggingEvent.getStartTime()));
+-      case THREAD_CONVERTER:
+-	return event.getThreadName();
+-      case LEVEL_CONVERTER:
+-	return event.getLevel().toString();
+-      case NDC_CONVERTER:
+-	return event.getNDC();
+-      case MESSAGE_CONVERTER: {
+-	return event.getRenderedMessage();
+-      }
+-      default: return null;
+-      }
++    protected void addConverter(PatternConverter pc) {
++	currentLiteral.setLength(0);
++	// Add the pattern converter to the list.
++	addToList(pc);
++	// Next pattern is assumed to be a literal.
++	state = LITERAL_STATE;
++	// Reset formatting info
++	formattingInfo.reset();
+     }
+-  }
+ 
+-  private static class LiteralPatternConverter extends PatternConverter {
+-    private String literal;
++    // ---------------------------------------------------------------------
++    // PatternConverters
++    // ---------------------------------------------------------------------
+ 
+-    LiteralPatternConverter(String value) {
+-      literal = value;
+-    }
++    private static class BasicPatternConverter extends PatternConverter {
++	int type;
+ 
+-    public
+-    final
+-    void format(StringBuffer sbuf, LoggingEvent event) {
+-      sbuf.append(literal);
+-    }
++	BasicPatternConverter(FormattingInfo formattingInfo, int type) {
++	    super(formattingInfo);
++	    this.type = type;
++	}
+ 
+-    public
+-    String convert(LoggingEvent event) {
+-      return literal;
++	public String convert(LoggingEvent event) {
++	    switch (type) {
++	    case RELATIVE_TIME_CONVERTER:
++		return (Long.toString(event.timeStamp - LoggingEvent.getStartTime()));
++	    case THREAD_CONVERTER:
++		return event.getThreadName();
++	    case LEVEL_CONVERTER:
++		return event.getLevel().toString();
++	    case NDC_CONVERTER:
++		return event.getNDC();
++	    case MESSAGE_CONVERTER: {
++		return event.getRenderedMessage();
++	    }
++	    default:
++		return null;
++	    }
++	}
+     }
+-  }
+ 
+-  private static class DatePatternConverter extends PatternConverter {
+-    private DateFormat df;
+-    private Date date;
++    private static class LiteralPatternConverter extends PatternConverter {
++	private String literal;
+ 
+-    DatePatternConverter(FormattingInfo formattingInfo, DateFormat df) {
+-      super(formattingInfo);
+-      date = new Date();
+-      this.df = df;
+-    }
++	LiteralPatternConverter(String value) {
++	    literal = value;
++	}
+ 
+-    public
+-    String convert(LoggingEvent event) {
+-      date.setTime(event.timeStamp);
+-      String converted = null;
+-      try {
+-        converted = df.format(date);
+-      }
+-      catch (Exception ex) {
+-        LogLog.error("Error occured while converting date.", ex);
+-      }
+-      return converted;
++	public final void format(StringBuffer sbuf, LoggingEvent event) {
++	    sbuf.append(literal);
++	}
++
++	public String convert(LoggingEvent event) {
++	    return literal;
++	}
+     }
+-  }
+ 
+-  private static class MDCPatternConverter extends PatternConverter {
+-    private String key;
++    private static class DatePatternConverter extends PatternConverter {
++	private DateFormat df;
++	private Date date;
+ 
+-    MDCPatternConverter(FormattingInfo formattingInfo, String key) {
+-      super(formattingInfo);
+-      this.key = key;
+-    }
++	DatePatternConverter(FormattingInfo formattingInfo, DateFormat df) {
++	    super(formattingInfo);
++	    date = new Date();
++	    this.df = df;
++	}
+ 
+-    public
+-    String convert(LoggingEvent event) {
+-      if (key == null) {
+-          StringBuffer buf = new StringBuffer("{");
+-          Map properties = event.getProperties();
+-          if (properties.size() > 0) {
+-            Object[] keys = properties.keySet().toArray();
+-            Arrays.sort(keys);
+-            for (int i = 0; i < keys.length; i++) {
+-                buf.append('{');
+-                buf.append(keys[i]);
+-                buf.append(',');
+-                buf.append(properties.get(keys[i]));
+-                buf.append('}');
+-            }
+-          }
+-          buf.append('}');
+-          return buf.toString();
+-      } else {
+-        Object val = event.getMDC(key);
+-        if(val == null) {
+-	        return null;
+-        } else {
+-	        return val.toString();
+-        }
+-      }
++	public String convert(LoggingEvent event) {
++	    date.setTime(event.timeStamp);
++	    String converted = null;
++	    try {
++		converted = df.format(date);
++	    } catch (Exception ex) {
++		LogLog.error("Error occured while converting date.", ex);
++	    }
++	    return converted;
++	}
+     }
+-  }
+ 
++    private static class MDCPatternConverter extends PatternConverter {
++	private String key;
+ 
+-  private class LocationPatternConverter extends PatternConverter {
+-    int type;
++	MDCPatternConverter(FormattingInfo formattingInfo, String key) {
++	    super(formattingInfo);
++	    this.key = key;
++	}
+ 
+-    LocationPatternConverter(FormattingInfo formattingInfo, int type) {
+-      super(formattingInfo);
+-      this.type = type;
++	public String convert(LoggingEvent event) {
++	    if (key == null) {
++		StringBuffer buf = new StringBuffer("{");
++		Map properties = event.getProperties();
++		if (properties.size() > 0) {
++		    Object[] keys = properties.keySet().toArray();
++		    Arrays.sort(keys);
++		    for (int i = 0; i < keys.length; i++) {
++			buf.append('{');
++			buf.append(keys[i]);
++			buf.append(',');
++			buf.append(properties.get(keys[i]));
++			buf.append('}');
++		    }
++		}
++		buf.append('}');
++		return buf.toString();
++	    } else {
++		Object val = event.getMDC(key);
++		if (val == null) {
++		    return null;
++		} else {
++		    return val.toString();
++		}
++	    }
++	}
+     }
+ 
+-    public
+-    String convert(LoggingEvent event) {
+-      LocationInfo locationInfo = event.getLocationInformation();
+-      switch(type) {
+-      case FULL_LOCATION_CONVERTER:
+-	return locationInfo.fullInfo;
+-      case METHOD_LOCATION_CONVERTER:
+-	return locationInfo.getMethodName();
+-      case LINE_LOCATION_CONVERTER:
+-	return locationInfo.getLineNumber();
+-      case FILE_LOCATION_CONVERTER:
+-	return locationInfo.getFileName();
+-      default: return null;
+-      }
+-    }
+-  }
++    private class LocationPatternConverter extends PatternConverter {
++	int type;
+ 
+-  private static abstract class NamedPatternConverter extends PatternConverter {
+-    int precision;
++	LocationPatternConverter(FormattingInfo formattingInfo, int type) {
++	    super(formattingInfo);
++	    this.type = type;
++	}
+ 
+-    NamedPatternConverter(FormattingInfo formattingInfo, int precision) {
+-      super(formattingInfo);
+-      this.precision =  precision;
++	public String convert(LoggingEvent event) {
++	    LocationInfo locationInfo = event.getLocationInformation();
++	    switch (type) {
++	    case FULL_LOCATION_CONVERTER:
++		return locationInfo.fullInfo;
++	    case METHOD_LOCATION_CONVERTER:
++		return locationInfo.getMethodName();
++	    case LINE_LOCATION_CONVERTER:
++		return locationInfo.getLineNumber();
++	    case FILE_LOCATION_CONVERTER:
++		return locationInfo.getFileName();
++	    default:
++		return null;
++	    }
++	}
+     }
+ 
+-    abstract
+-    String getFullyQualifiedName(LoggingEvent event);
+-
+-    public
+-    String convert(LoggingEvent event) {
+-      String n = getFullyQualifiedName(event);
+-      if(precision <= 0)
+-	return n;
+-      else {
+-	int len = n.length();
+-
+-	// We substract 1 from 'len' when assigning to 'end' to avoid out of
+-	// bounds exception in return r.substring(end+1, len). This can happen if
+-	// precision is 1 and the category name ends with a dot.
+-	int end = len -1 ;
+-	for(int i = precision; i > 0; i--) {
+-	  end = n.lastIndexOf('.', end-1);
+-	  if(end == -1)
+-	    return n;
++    private static abstract class NamedPatternConverter extends PatternConverter {
++	int precision;
++
++	NamedPatternConverter(FormattingInfo formattingInfo, int precision) {
++	    super(formattingInfo);
++	    this.precision = precision;
++	}
++
++	abstract String getFullyQualifiedName(LoggingEvent event);
++
++	public String convert(LoggingEvent event) {
++	    String n = getFullyQualifiedName(event);
++	    if (precision <= 0)
++		return n;
++	    else {
++		int len = n.length();
++
++		// We substract 1 from 'len' when assigning to 'end' to avoid out of
++		// bounds exception in return r.substring(end+1, len). This can happen if
++		// precision is 1 and the category name ends with a dot.
++		int end = len - 1;
++		for (int i = precision; i > 0; i--) {
++		    end = n.lastIndexOf('.', end - 1);
++		    if (end == -1)
++			return n;
++		}
++		return n.substring(end + 1, len);
++	    }
+ 	}
+-	return n.substring(end+1, len);
+-      }
+     }
+-  }
+ 
+-  private class ClassNamePatternConverter extends NamedPatternConverter {
++    private class ClassNamePatternConverter extends NamedPatternConverter {
+ 
+-    ClassNamePatternConverter(FormattingInfo formattingInfo, int precision) {
+-      super(formattingInfo, precision);
+-    }
++	ClassNamePatternConverter(FormattingInfo formattingInfo, int precision) {
++	    super(formattingInfo, precision);
++	}
+ 
+-    String getFullyQualifiedName(LoggingEvent event) {
+-      return event.getLocationInformation().getClassName();
++	String getFullyQualifiedName(LoggingEvent event) {
++	    return event.getLocationInformation().getClassName();
++	}
+     }
+-  }
+ 
+-  private class CategoryPatternConverter extends NamedPatternConverter {
++    private class CategoryPatternConverter extends NamedPatternConverter {
+ 
+-    CategoryPatternConverter(FormattingInfo formattingInfo, int precision) {
+-      super(formattingInfo, precision);
+-    }
++	CategoryPatternConverter(FormattingInfo formattingInfo, int precision) {
++	    super(formattingInfo, precision);
++	}
+ 
+-    String getFullyQualifiedName(LoggingEvent event) {
+-      return event.getLoggerName();
++	String getFullyQualifiedName(LoggingEvent event) {
++	    return event.getLoggerName();
++	}
+     }
+-  }
+ }
+-
+diff --git a/src/main/java/org/apache/log4j/jdbc/JDBCAppender.java b/src/main/java/org/apache/log4j/jdbc/JDBCAppender.java
+index ad35f657..6ba9b66c 100644
+--- a/src/main/java/org/apache/log4j/jdbc/JDBCAppender.java
++++ b/src/main/java/org/apache/log4j/jdbc/JDBCAppender.java
+@@ -18,381 +18,414 @@ package org.apache.log4j.jdbc;
+ 
+ import java.sql.Connection;
+ import java.sql.DriverManager;
++import java.sql.PreparedStatement;
+ import java.sql.SQLException;
+ import java.sql.Statement;
+ import java.util.ArrayList;
+-import java.util.Iterator;
+ 
+ import org.apache.log4j.PatternLayout;
++import org.apache.log4j.helpers.LogLog;
+ import org.apache.log4j.spi.ErrorCode;
+ import org.apache.log4j.spi.LoggingEvent;
+ 
+-
+ /**
+-  The JDBCAppender provides for sending log events to a database.
+-  
+- <p><b><font color="#FF2222">WARNING: This version of JDBCAppender
+- is very likely to be completely replaced in the future. Moreoever,
+- it does not log exceptions</font></b>.
+-
+-  <p>Each append call adds to an <code>ArrayList</code> buffer.  When
+-  the buffer is filled each log event is placed in a sql statement
+-  (configurable) and executed.
+-
+-  <b>BufferSize</b>, <b>db URL</b>, <b>User</b>, & <b>Password</b> are
+-  configurable options in the standard log4j ways.
+-
+-  <p>The <code>setSql(String sql)</code> sets the SQL statement to be
+-  used for logging -- this statement is sent to a
+-  <code>PatternLayout</code> (either created automaticly by the
+-  appender or added by the user).  Therefore by default all the
+-  conversion patterns in <code>PatternLayout</code> can be used
+-  inside of the statement.  (see the test cases for examples)
+-
+-  <p>Overriding the {@link #getLogStatement} method allows more
+-  explicit control of the statement used for logging.
+-
+-  <p>For use as a base class:
+-
+-    <ul>
+-
+-    <li>Override <code>getConnection()</code> to pass any connection
+-    you want.  Typically this is used to enable application wide
+-    connection pooling.
+-
+-     <li>Override <code>closeConnection(Connection con)</code> -- if
+-     you override getConnection make sure to implement
+-     <code>closeConnection</code> to handle the connection you
+-     generated.  Typically this would return the connection to the
+-     pool it came from.
+-
+-     <li>Override <code>getLogStatement(LoggingEvent event)</code> to
+-     produce specialized or dynamic statements. The default uses the
+-     sql option value.
+-
+-    </ul>
+-
+-    @author Kevin Steppe (<A HREF="mailto:ksteppe@pacbell.net">ksteppe@pacbell.net</A>)
+-
+-*/
+-public class JDBCAppender extends org.apache.log4j.AppenderSkeleton
+-    implements org.apache.log4j.Appender {
+-
+-  /**
+-   * URL of the DB for default connection handling
+-   */
+-  protected String databaseURL = "jdbc:odbc:myDB";
+-
+-  /**
+-   * User to connect as for default connection handling
+-   */
+-  protected String databaseUser = "me";
+-
+-  /**
+-   * User to use for default connection handling
+-   */
+-  protected String databasePassword = "mypassword";
+-
+-  /**
+-   * Connection used by default.  The connection is opened the first time it
+-   * is needed and then held open until the appender is closed (usually at
+-   * garbage collection).  This behavior is best modified by creating a
+-   * sub-class and overriding the <code>getConnection</code> and
+-   * <code>closeConnection</code> methods.
+-   */
+-  protected Connection connection = null;
+-
+-  /**
+-   * Stores the string given to the pattern layout for conversion into a SQL
+-   * statement, eg: insert into LogTable (Thread, Class, Message) values
+-   * ("%t", "%c", "%m").
+-   *
+-   * Be careful of quotes in your messages!
+-   *
+-   * Also see PatternLayout.
+-   */
+-  protected String sqlStatement = "";
+-
+-  /**
+-   * size of LoggingEvent buffer before writting to the database.
+-   * Default is 1.
+-   */
+-  protected int bufferSize = 1;
+-
+-  /**
+-   * ArrayList holding the buffer of Logging Events.
+-   */
+-  protected ArrayList buffer;
+-
+-  /**
+-   * Helper object for clearing out the buffer
+-   */
+-  protected ArrayList removes;
+-  
+-  private boolean locationInfo = false;
+-
+-  public JDBCAppender() {
+-    super();
+-    buffer = new ArrayList(bufferSize);
+-    removes = new ArrayList(bufferSize);
+-  }
+-
+-  /**
+-   * Gets whether the location of the logging request call
+-   * should be captured.
+-   *
+-   * @since 1.2.16
+-   * @return the current value of the <b>LocationInfo</b> option.
+-   */
+-  public boolean getLocationInfo() {
+-    return locationInfo;
+-  }
+-  
+-  /**
+-   * The <b>LocationInfo</b> option takes a boolean value. By default, it is
+-   * set to false which means there will be no effort to extract the location
+-   * information related to the event. As a result, the event that will be
+-   * ultimately logged will likely to contain the wrong location information
+-   * (if present in the log format).
+-   * <p/>
+-   * <p/>
+-   * Location information extraction is comparatively very slow and should be
+-   * avoided unless performance is not a concern.
+-   * </p>
+-   * @since 1.2.16
+-   * @param flag true if location information should be extracted.
+-   */
+-  public void setLocationInfo(final boolean flag) {
+-    locationInfo = flag;
+-  }
+-  
+-
+-  /**
+-   * Adds the event to the buffer.  When full the buffer is flushed.
+-   */
+-  public void append(LoggingEvent event) {
+-    event.getNDC();
+-    event.getThreadName();
+-    // Get a copy of this thread's MDC.
+-    event.getMDCCopy();
+-    if (locationInfo) {
+-      event.getLocationInformation();
+-    }
+-    event.getRenderedMessage();
+-    event.getThrowableStrRep();
+-    buffer.add(event);
+-
+-    if (buffer.size() >= bufferSize)
+-      flushBuffer();
+-  }
+-
+-  /**
+-   * By default getLogStatement sends the event to the required Layout object.
+-   * The layout will format the given pattern into a workable SQL string.
+-   *
+-   * Overriding this provides direct access to the LoggingEvent
+-   * when constructing the logging statement.
+-   *
+-   */
+-  protected String getLogStatement(LoggingEvent event) {
+-    return getLayout().format(event);
+-  }
+-
+-  /**
+-   *
+-   * Override this to provide an alertnate method of getting
+-   * connections (such as caching).  One method to fix this is to open
+-   * connections at the start of flushBuffer() and close them at the
+-   * end.  I use a connection pool outside of JDBCAppender which is
+-   * accessed in an override of this method.
+-   * */
+-  protected void execute(String sql) throws SQLException {
+-
+-    Connection con = null;
+-    Statement stmt = null;
+-
+-    try {
+-        con = getConnection();
+-
+-        stmt = con.createStatement();
+-        stmt.executeUpdate(sql);
+-    } finally {
+-        if(stmt != null) {
+-            stmt.close();
+-        }
+-        closeConnection(con);
++ * The JDBCAppender provides for sending log events to a database.
++ * 
++ * <p>
++ * <b><font color="#FF2222">WARNING: This version of JDBCAppender is very likely
++ * to be completely replaced in the future. Moreoever, it does not log
++ * exceptions</font></b>.
++ * 
++ * <p>
++ * Each append call adds to an <code>ArrayList</code> buffer. When the buffer is
++ * filled each log event is placed in a sql statement (configurable) and
++ * executed.
++ * 
++ * <b>BufferSize</b>, <b>db URL</b>, <b>User</b>, &amp; <b>Password</b> are
++ * configurable options in the standard log4j ways.
++ * 
++ * <p>
++ * The <code>setSql(String sql)</code> sets the SQL statement to be used for
++ * logging -- this statement is sent to a <code>PatternLayout</code> (either
++ * created automatically by the appender or added by the user). Therefore by
++ * default all the conversion patterns in <code>PatternLayout</code> can be used
++ * inside of the statement. (see the test cases for examples)
++ * 
++ * <p>
++ * Overriding the {@link #getLogStatement} method allows more explicit control
++ * of the statement used for logging.
++ * 
++ * <p>
++ * For use as a base class:
++ * 
++ * <ul>
++ * 
++ * <li>Override <code>getConnection()</code> to pass any connection you want.
++ * Typically this is used to enable application wide connection pooling.
++ * 
++ * <li>Override <code>closeConnection(Connection con)</code> -- if you override
++ * getConnection make sure to implement <code>closeConnection</code> to handle
++ * the connection you generated. Typically this would return the connection to
++ * the pool it came from.
++ * 
++ * <li>Override <code>getLogStatement(LoggingEvent event)</code> to produce
++ * specialized or dynamic statements. The default uses the sql option value.
++ * 
++ * </ul>
++ * 
++ * @author Kevin Steppe
++ *         (<A HREF="mailto:ksteppe@pacbell.net">ksteppe@pacbell.net</A>)
++ * 
++ */
++public class JDBCAppender extends org.apache.log4j.AppenderSkeleton implements org.apache.log4j.Appender {
++
++//    private final Boolean secureSqlReplacement = Boolean
++//	    .parseBoolean(System.getProperty("org.apache.log4j.jdbc.JDBCAppender.secure_jdbc_replacement", "true"));
++
++    private static final IllegalArgumentException ILLEGAL_PATTERN_FOR_SECURE_EXEC = new IllegalArgumentException(
++	    "Only org.apache.log4j.PatternLayout is supported for now due to CVE-2022-23305");
++
++    /**
++     * URL of the DB for default connection handling
++     */
++    protected String databaseURL = "jdbc:odbc:myDB";
++
++    /**
++     * User to connect as for default connection handling
++     */
++    protected String databaseUser = "me";
++
++    /**
++     * User to use for default connection handling
++     */
++    protected String databasePassword = "mypassword";
++
++    /**
++     * Connection used by default. The connection is opened the first time it is
++     * needed and then held open until the appender is closed (usually at garbage
++     * collection). This behavior is best modified by creating a sub-class and
++     * overriding the <code>getConnection</code> and <code>closeConnection</code>
++     * methods.
++     */
++    protected Connection connection = null;
++
++    /**
++     * Stores the string given to the pattern layout for conversion into a SQL
++     * statement, eg: insert into LogTable (Thread, Class, Message) values ("%t",
++     * "%c", "%m").
++     *
++     * Be careful of quotes in your messages!
++     *
++     * Also see PatternLayout.
++     */
++    protected String sqlStatement = "";
++
++    private JdbcPatternParser preparedStatementParser;
++    /**
++     * size of LoggingEvent buffer before writting to the database. Default is 1.
++     */
++    protected int bufferSize = 1;
++
++    /**
++     * ArrayList holding the buffer of Logging Events.
++     */
++    protected ArrayList<LoggingEvent> buffer;
++
++    private boolean locationInfo = false;
++
++    private boolean isActive = false;
++
++    public JDBCAppender() {
++	super();
++	buffer = new ArrayList<LoggingEvent>(bufferSize);
+     }
+ 
+-    //System.out.println("Execute: " + sql);
+-  }
+-
+-
+-  /**
+-   * Override this to return the connection to a pool, or to clean up the
+-   * resource.
+-   *
+-   * The default behavior holds a single connection open until the appender
+-   * is closed (typically when garbage collected).
+-   */
+-  protected void closeConnection(Connection con) {
+-  }
+-
+-  /**
+-   * Override this to link with your connection pooling system.
+-   *
+-   * By default this creates a single connection which is held open
+-   * until the object is garbage collected.
+-   */
+-  protected Connection getConnection() throws SQLException {
+-      if (!DriverManager.getDrivers().hasMoreElements())
+-	     setDriver("sun.jdbc.odbc.JdbcOdbcDriver");
+-
+-      if (connection == null) {
+-        connection = DriverManager.getConnection(databaseURL, databaseUser,
+-					databasePassword);
+-      }
+-
+-      return connection;
+-  }
+-
+-  /**
+-   * Closes the appender, flushing the buffer first then closing the default
+-   * connection if it is open.
+-   */
+-  public void close()
+-  {
+-    flushBuffer();
+-
+-    try {
+-      if (connection != null && !connection.isClosed())
+-          connection.close();
+-    } catch (SQLException e) {
+-        errorHandler.error("Error closing connection", e, ErrorCode.GENERIC_FAILURE);
++    @Override
++    public void activateOptions() {
++
++	if (getSql() == null || getSql().trim().length() == 0) {
++	    LogLog.error("JDBCAppender.sql parameter is mandatory. Skipping all futher processing");
++	    isActive = false;
++	    return;
++	}
++
++	LogLog.debug("JDBCAppender constructing internal pattern parser");
++	preparedStatementParser = new JdbcPatternParser(getSql());
++	isActive = true;
+     }
+-    this.closed = true;
+-  }
+-
+-  /**
+-   * loops through the buffer of LoggingEvents, gets a
+-   * sql string from getLogStatement() and sends it to execute().
+-   * Errors are sent to the errorHandler.
+-   *
+-   * If a statement fails the LoggingEvent stays in the buffer!
+-   */
+-  public void flushBuffer() {
+-    //Do the actual logging
+-    removes.ensureCapacity(buffer.size());
+-    for (Iterator i = buffer.iterator(); i.hasNext();) {
+-      LoggingEvent logEvent = (LoggingEvent)i.next();
+-      try {
+-	    String sql = getLogStatement(logEvent);
+-	    execute(sql);
+-      }
+-      catch (SQLException e) {
+-	    errorHandler.error("Failed to excute sql", e,
+-			   ErrorCode.FLUSH_FAILURE);
+-      } finally {
+-        removes.add(logEvent);
+-      }
++
++    /**
++     * Gets whether the location of the logging request call should be captured.
++     *
++     * @since 1.2.16
++     * @return the current value of the <b>LocationInfo</b> option.
++     */
++    public boolean getLocationInfo() {
++	return locationInfo;
+     }
+-    
+-    // remove from the buffer any events that were reported
+-    buffer.removeAll(removes);
+-    
+-    // clear the buffer of reported events
+-    removes.clear();
+-  }
+-
+-
+-  /** closes the appender before disposal */
+-  public void finalize() {
+-    close();
+-  }
+-
+-
+-  /**
+-   * JDBCAppender requires a layout.
+-   * */
+-  public boolean requiresLayout() {
+-    return true;
+-  }
+-
+-
+-  /**
+-   *
+-   */
+-  public void setSql(String s) {
+-    sqlStatement = s;
+-    if (getLayout() == null) {
+-        this.setLayout(new PatternLayout(s));
++
++    /**
++     * The <b>LocationInfo</b> option takes a boolean value. By default, it is set
++     * to false which means there will be no effort to extract the location
++     * information related to the event. As a result, the event that will be
++     * ultimately logged will likely to contain the wrong location information (if
++     * present in the log format).
++     * <p/>
++     * <p/>
++     * Location information extraction is comparatively very slow and should be
++     * avoided unless performance is not a concern.
++     * </p>
++     * 
++     * @since 1.2.16
++     * @param flag true if location information should be extracted.
++     */
++    public void setLocationInfo(final boolean flag) {
++	locationInfo = flag;
+     }
+-    else {
+-        ((PatternLayout)getLayout()).setConversionPattern(s);
++
++    /**
++     * Adds the event to the buffer. When full the buffer is flushed.
++     */
++    public void append(LoggingEvent event) {
++	if (!isActive) {
++	    return;
++	}
++	event.getNDC();
++	event.getThreadName();
++	// Get a copy of this thread's MDC.
++	event.getMDCCopy();
++	if (locationInfo) {
++	    event.getLocationInformation();
++	}
++	event.getRenderedMessage();
++	event.getThrowableStrRep();
++	buffer.add(event);
++
++	if (buffer.size() >= bufferSize)
++	    flushBuffer();
+     }
+-  }
+ 
++    /**
++     * By default getLogStatement sends the event to the required Layout object. The
++     * layout will format the given pattern into a workable SQL string.
++     *
++     * Overriding this provides direct access to the LoggingEvent when constructing
++     * the logging statement.
++     *
++     */
++    protected String getLogStatement(LoggingEvent event) {
++	return getLayout().format(event);
++    }
+ 
+-  /**
+-   * Returns pre-formated statement eg: insert into LogTable (msg) values ("%m")
+-   */
+-  public String getSql() {
+-    return sqlStatement;
+-  }
++    /**
++     *
++     * Override this to provide an alertnate method of getting connections (such as
++     * caching). One method to fix this is to open connections at the start of
++     * flushBuffer() and close them at the end. I use a connection pool outside of
++     * JDBCAppender which is accessed in an override of this method.
++     */
++    protected void execute(String sql) throws SQLException {
++
++	Connection con = null;
++	Statement stmt = null;
++
++	try {
++	    con = getConnection();
++
++	    stmt = con.createStatement();
++	    stmt.executeUpdate(sql);
++	} finally {
++	    if (stmt != null) {
++		stmt.close();
++	    }
++	    closeConnection(con);
++	}
++
++	// System.out.println("Execute: " + sql);
++    }
+ 
++    /**
++     * Override this to return the connection to a pool, or to clean up the
++     * resource.
++     *
++     * The default behavior holds a single connection open until the appender is
++     * closed (typically when garbage collected).
++     */
++    protected void closeConnection(Connection con) {
++    }
+ 
+-  public void setUser(String user) {
+-    databaseUser = user;
+-  }
++    /**
++     * Override this to link with your connection pooling system.
++     *
++     * By default this creates a single connection which is held open until the
++     * object is garbage collected.
++     */
++    protected Connection getConnection() throws SQLException {
++	if (!DriverManager.getDrivers().hasMoreElements())
++	    setDriver("sun.jdbc.odbc.JdbcOdbcDriver");
++
++	if (connection == null) {
++	    connection = DriverManager.getConnection(databaseURL, databaseUser, databasePassword);
++	}
++
++	return connection;
++    }
+ 
++    /**
++     * Closes the appender, flushing the buffer first then closing the default
++     * connection if it is open.
++     */
++    public void close() {
++	flushBuffer();
++
++	try {
++	    if (connection != null && !connection.isClosed())
++		connection.close();
++	} catch (SQLException e) {
++	    errorHandler.error("Error closing connection", e, ErrorCode.GENERIC_FAILURE);
++	}
++	this.closed = true;
++    }
+ 
+-  public void setURL(String url) {
+-    databaseURL = url;
+-  }
++    /**
++     * loops through the buffer of LoggingEvents, gets a sql string from
++     * getLogStatement() and sends it to execute(). Errors are sent to the
++     * errorHandler.
++     *
++     * If a statement fails the LoggingEvent stays in the buffer!
++     */
++    public void flushBuffer() {
++	if (buffer.isEmpty()) {
++	    return;
++	}
++	flushBufferSecure();
++    }
+ 
++    private void flushBufferSecure() {
++	// Prepare events that we will store to the DB
++	ArrayList<LoggingEvent> removes = new ArrayList<LoggingEvent>(buffer);
++	buffer.removeAll(removes);
++	  
++	if (layout.getClass() != PatternLayout.class) {
++	    errorHandler.error("Failed to convert pattern " + layout + " to SQL", ILLEGAL_PATTERN_FOR_SECURE_EXEC,
++		    ErrorCode.MISSING_LAYOUT);
++	    return;
++	}
++	Connection con = null;
++	boolean useBatch = removes.size() > 1;
++	try {
++	    con = getConnection();
++	    PreparedStatement ps = con.prepareStatement(preparedStatementParser.getParameterizedSql());
++	    safelyInsertIntoDB(removes, useBatch, ps);
++	} catch (SQLException e) {
++	    errorHandler.error("Failed to append messages sql", e, ErrorCode.FLUSH_FAILURE);
++	} finally {
++	    if (con != null) {
++		closeConnection(con);
++	    }
++	}
++
++   }
++
++    private void safelyInsertIntoDB(ArrayList<LoggingEvent> removes, boolean useBatch, PreparedStatement ps)
++	    throws SQLException {
++	try {
++	    for (LoggingEvent logEvent : removes) {
++		try {
++		    preparedStatementParser.setParameters(ps, logEvent);
++		    if (useBatch) {
++			ps.addBatch();
++		    }
++		} catch (SQLException e) {
++		    errorHandler.error("Failed to append parameters", e, ErrorCode.FLUSH_FAILURE);
++		}
++	    }
++	    if (useBatch) {
++		ps.executeBatch();
++	    } else {
++		ps.execute();
++	    }
++	} finally {
++	    try {
++		ps.close();
++	    } catch (SQLException ignored) {
++	    }
++	}
++    }
+ 
+-  public void setPassword(String password) {
+-    databasePassword = password;
+-  }
++    /** closes the appender before disposal */
++    public void finalize() {
++	close();
++    }
+ 
++    /**
++     * JDBCAppender requires a layout.
++     */
++    public boolean requiresLayout() {
++	return true;
++    }
+ 
+-  public void setBufferSize(int newBufferSize) {
+-    bufferSize = newBufferSize;
+-    buffer.ensureCapacity(bufferSize);
+-    removes.ensureCapacity(bufferSize);
+-  }
++    /**
++     *
++     */
++    public void setSql(String s) {
++	sqlStatement = s;
++	if (getLayout() == null) {
++	    this.setLayout(new PatternLayout(s));
++	} else {
++	    ((PatternLayout) getLayout()).setConversionPattern(s);
++	}
++    }
+ 
++    /**
++     * Returns pre-formated statement eg: insert into LogTable (msg) values ("%m")
++     */
++    public String getSql() {
++	return sqlStatement;
++    }
+ 
+-  public String getUser() {
+-    return databaseUser;
+-  }
++    public void setUser(String user) {
++	databaseUser = user;
++    }
+ 
++    public void setURL(String url) {
++	databaseURL = url;
++    }
+ 
+-  public String getURL() {
+-    return databaseURL;
+-  }
++    public void setPassword(String password) {
++	databasePassword = password;
++    }
+ 
++    public void setBufferSize(int newBufferSize) {
++	bufferSize = newBufferSize;
++	buffer.ensureCapacity(bufferSize);
++    }
+ 
+-  public String getPassword() {
+-    return databasePassword;
+-  }
++    public String getUser() {
++	return databaseUser;
++    }
+ 
++    public String getURL() {
++	return databaseURL;
++    }
+ 
+-  public int getBufferSize() {
+-    return bufferSize;
+-  }
++    public String getPassword() {
++	return databasePassword;
++    }
+ 
++    public int getBufferSize() {
++	return bufferSize;
++    }
+ 
+-  /**
+-   * Ensures that the given driver class has been loaded for sql connection
+-   * creation.
+-   */
+-  public void setDriver(String driverClass) {
+-    try {
+-      Class.forName(driverClass);
+-    } catch (Exception e) {
+-      errorHandler.error("Failed to load driver", e,
+-			 ErrorCode.GENERIC_FAILURE);
++    /**
++     * Ensures that the given driver class has been loaded for sql connection
++     * creation.
++     */
++    public void setDriver(String driverClass) {
++	try {
++	    Class.forName(driverClass);
++	} catch (Exception e) {
++	    errorHandler.error("Failed to load driver", e, ErrorCode.GENERIC_FAILURE);
++	}
+     }
+-  }
+ }
+-
+diff --git a/src/main/java/org/apache/log4j/jdbc/JdbcPatternParser.java b/src/main/java/org/apache/log4j/jdbc/JdbcPatternParser.java
+new file mode 100644
+index 00000000..d6807dee
+--- /dev/null
++++ b/src/main/java/org/apache/log4j/jdbc/JdbcPatternParser.java
+@@ -0,0 +1,115 @@
++/*
++ * Licensed to the Apache Software Foundation (ASF) under one or more
++ * contributor license agreements.  See the NOTICE file distributed with
++ * this work for additional information regarding copyright ownership.
++ * The ASF licenses this file to You under the Apache License, Version 2.0
++ * (the "License"); you may not use this file except in compliance with
++ * the License.  You may obtain a copy of the License at
++ *
++ *      http://www.apache.org/licenses/LICENSE-2.0
++ *
++ * Unless required by applicable law or agreed to in writing, software
++ * distributed under the License is distributed on an "AS IS" BASIS,
++ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
++ * See the License for the specific language governing permissions and
++ * limitations under the License.
++ */
++package org.apache.log4j.jdbc;
++
++import org.apache.log4j.helpers.PatternConverter;
++import org.apache.log4j.helpers.PatternParser;
++import org.apache.log4j.spi.LoggingEvent;
++
++import java.sql.PreparedStatement;
++import java.sql.SQLException;
++import java.util.ArrayList;
++import java.util.Collections;
++import java.util.List;
++import java.util.regex.Matcher;
++import java.util.regex.Pattern;
++
++class JdbcPatternParser {
++    private static final String QUESTION_MARK = "?";
++
++    private static final char PERCENT_CHAR = '%';
++
++    private final static Pattern STRING_LITERAL_PATTERN = Pattern.compile("'((?>[^']|'')+)'");
++    // NOTE: capturing group work seem to work just as well.
++    //private final static Pattern STRING_LITERAL_PATTERN = Pattern.compile("'(([^']|'')+)'");
++
++    private String parameterizedSql;
++    final private List<String> patternStringRepresentationList = new ArrayList<String>();
++    final private List<PatternConverter> args = new ArrayList<PatternConverter>();
++
++    JdbcPatternParser(String insertString) {
++	init(insertString);
++    }
++
++    public String getParameterizedSql() {
++	return parameterizedSql;
++    }
++
++    public List<String> getUnmodifiablePatternStringRepresentationList() {
++	return Collections.unmodifiableList(patternStringRepresentationList);
++    }
++
++    @Override
++    public String toString() {
++	return "JdbcPatternParser{sql=" + parameterizedSql + ",args=" + patternStringRepresentationList + "}";
++    }
++
++    /**
++     * Converts '....' literals into bind variables in JDBC.
++     */
++    private void init(String insertString) {
++	if (insertString == null) {
++	    throw new IllegalArgumentException("Null pattern");
++	}
++
++	Matcher m = STRING_LITERAL_PATTERN.matcher(insertString);
++	StringBuffer sb = new StringBuffer();
++	while (m.find()) {
++	    String matchedStr = m.group(1);
++	    if (matchedStr.indexOf(PERCENT_CHAR) == -1) {
++		replaceWithMatchedStr(m, sb);
++	    } else {
++		// Replace with bind
++		replaceWithBind(m, sb, matchedStr);
++	    }
++	}
++	m.appendTail(sb);
++	this.parameterizedSql = sb.toString();
++    }
++
++    private void replaceWithMatchedStr(Matcher m, StringBuffer sb) {
++	// Just literal, append it as is
++	m.appendReplacement(sb, "'$1'");
++    }
++
++    private void replaceWithBind(Matcher m, StringBuffer sb, String matchedStr) {
++	m.appendReplacement(sb, QUESTION_MARK);
++	// We will use prepared statements, so we don't need to escape quotes.
++	// And we assume the users had 'That''s a string with quotes' in their configs.
++	matchedStr = matchedStr.replaceAll("''", "'");
++	patternStringRepresentationList.add(matchedStr);
++	args.add(new PatternParser(matchedStr).parse());
++    }
++
++    public void setParameters(PreparedStatement ps, LoggingEvent logEvent) throws SQLException {
++	for (int i = 0; i < args.size(); i++) {
++	    final PatternConverter head = args.get(i);
++	    String value = buildValueStr(logEvent, head);
++	    ps.setString(i + 1, value);
++	}
++    }
++
++    private String buildValueStr(LoggingEvent logEvent, final PatternConverter head) {
++	StringBuffer buffer = new StringBuffer();
++	PatternConverter c = head;
++	while (c != null) {
++	    c.format(buffer, logEvent);
++	    c = c.next;
++	}
++	return buffer.toString();
++    }
++}
+-- 
+2.33.1
+
diff --git a/SOURCES/0001-Fix-CVE-2022-23307-Chainsaw.patch b/SOURCES/0001-Fix-CVE-2022-23307-Chainsaw.patch
new file mode 100644
index 0000000..65cb04d
--- /dev/null
+++ b/SOURCES/0001-Fix-CVE-2022-23307-Chainsaw.patch
@@ -0,0 +1,35 @@
+From 637fb986311f8c5a22cfb2ad2a6b928d179ea49c Mon Sep 17 00:00:00 2001
+From: Mikolaj Izdebski <mizdebsk@redhat.com>
+Date: Wed, 2 Feb 2022 19:37:17 +0100
+Subject: [PATCH] Fix CVE-2022-23307 Chainsaw
+
+---
+ src/main/java/org/apache/log4j/chainsaw/LoggingReceiver.java | 5 ++++-
+ 1 file changed, 4 insertions(+), 1 deletion(-)
+
+diff --git a/src/main/java/org/apache/log4j/chainsaw/LoggingReceiver.java b/src/main/java/org/apache/log4j/chainsaw/LoggingReceiver.java
+index ca087adc..7e739df5 100644
+--- a/src/main/java/org/apache/log4j/chainsaw/LoggingReceiver.java
++++ b/src/main/java/org/apache/log4j/chainsaw/LoggingReceiver.java
+@@ -22,6 +22,8 @@ import java.io.ObjectInputStream;
+ import java.net.ServerSocket;
+ import java.net.Socket;
+ import java.net.SocketException;
++
++import org.apache.log4j.FilteredObjectInputStream;
+ import org.apache.log4j.Logger;
+ import org.apache.log4j.spi.LoggingEvent;
+ 
+@@ -59,7 +61,8 @@ class LoggingReceiver extends Thread {
+             LOG.debug("Starting to get data");
+             try {
+                 final ObjectInputStream ois =
+-                    new ObjectInputStream(mClient.getInputStream());
++                    new FilteredObjectInputStream(
++                            mClient.getInputStream(), FilteredObjectInputStream.SYSTEM_ALLOWED_CLASSES);
+                 while (true) {
+                     final LoggingEvent event = (LoggingEvent) ois.readObject();
+                     mModel.addEvent(new EventDetails(event));
+-- 
+2.33.1
+
diff --git a/SPECS/log4j.spec b/SPECS/log4j.spec
index 6a2f06b..6ddb8ce 100644
--- a/SPECS/log4j.spec
+++ b/SPECS/log4j.spec
@@ -3,7 +3,7 @@
 
 Name:           log4j
 Version:        1.2.17
-Release:        17%{?dist}
+Release:        18%{?dist}
 Epoch:          0
 Summary:        Java logging package
 BuildArch:      noarch
@@ -26,6 +26,9 @@ Patch5:         0012-Add-proper-bundle-symbolicname.patch
 Patch6:         0001-Backport-fix-for-CVE-2017-5645.patch
 Patch7:         0001-Add-test-case-for-JNDI-disablement.patch
 Patch8:         0002-Disable-JNDI-by-default.patch
+Patch9:         0001-Fix-CVE-2022-23302-JMSSink.patch
+Patch10:        0001-Fix-CVE-2022-23305-JDBCAppender.patch
+Patch11:        0001-Fix-CVE-2022-23307-Chainsaw.patch
 
 BuildRequires:  %{__perl}
 BuildRequires:  maven-local
@@ -65,6 +68,9 @@ Summary:        API documentation for %{name}
 %patch6 -p1 -b .cve-2017-5645
 %patch7 -p1 -b .log4shell
 %patch8 -p1 -b .log4shell
+%patch9 -p1 -b .jms-cve
+%patch10 -p1 -b .jdbc-cve
+%patch11 -p1 -b .chainsaw-cve
 %pom_remove_plugin :maven-site-plugin
 
 sed -i "s|groupId>ant<|groupId>org.apache.ant<|g" pom.xml
@@ -170,6 +176,12 @@ fi
 
 
 %changelog
+* Wed Feb 02 2022 Mikolaj Izdebski <mizdebsk@redhat.com> - 0:1.2.17-18
+- Fix Unsafe deserialization flaw in Chainsaw log viewer
+- Fix SQL injection when application is configured to use JDBCAppender
+- Fix remote code execution when application is configured to use JMSSink
+- Resolves: CVE-2022-23307, CVE-2022-23305, CVE-2022-23302
+
 * Wed Dec 15 2021 Mikolaj Izdebski <mizdebsk@redhat.com> - 0:1.2.17-17
 - Fix remote code execution vulnerability
 - Resolves: CVE-2021-4104