1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.apache.logging.log4j.core.appender.db.jdbc;
18
19 import java.io.Serializable;
20 import java.io.StringReader;
21 import java.sql.Clob;
22 import java.sql.Connection;
23 import java.sql.DatabaseMetaData;
24 import java.sql.NClob;
25 import java.sql.PreparedStatement;
26 import java.sql.ResultSetMetaData;
27 import java.sql.SQLException;
28 import java.sql.Timestamp;
29 import java.sql.Types;
30 import java.util.ArrayList;
31 import java.util.Arrays;
32 import java.util.Date;
33 import java.util.HashMap;
34 import java.util.List;
35 import java.util.Map;
36 import java.util.Objects;
37 import java.util.concurrent.CountDownLatch;
38
39 import org.apache.logging.log4j.core.Layout;
40 import org.apache.logging.log4j.core.LogEvent;
41 import org.apache.logging.log4j.core.StringLayout;
42 import org.apache.logging.log4j.core.appender.AppenderLoggingException;
43 import org.apache.logging.log4j.core.appender.ManagerFactory;
44 import org.apache.logging.log4j.core.appender.db.AbstractDatabaseAppender;
45 import org.apache.logging.log4j.core.appender.db.AbstractDatabaseManager;
46 import org.apache.logging.log4j.core.appender.db.ColumnMapping;
47 import org.apache.logging.log4j.core.appender.db.DbAppenderLoggingException;
48 import org.apache.logging.log4j.core.config.plugins.convert.DateTypeConverter;
49 import org.apache.logging.log4j.core.config.plugins.convert.TypeConverters;
50 import org.apache.logging.log4j.core.util.Closer;
51 import org.apache.logging.log4j.core.util.Log4jThread;
52 import org.apache.logging.log4j.message.MapMessage;
53 import org.apache.logging.log4j.spi.ThreadContextMap;
54 import org.apache.logging.log4j.spi.ThreadContextStack;
55 import org.apache.logging.log4j.util.IndexedReadOnlyStringMap;
56 import org.apache.logging.log4j.util.ReadOnlyStringMap;
57 import org.apache.logging.log4j.util.Strings;
58
59
60
61
62 public final class JdbcDatabaseManager extends AbstractDatabaseManager {
63
64
65
66
67 private static final class FactoryData extends AbstractDatabaseManager.AbstractFactoryData {
68 private final ConnectionSource connectionSource;
69 private final String tableName;
70 private final ColumnConfig[] columnConfigs;
71 private final ColumnMapping[] columnMappings;
72 private final boolean immediateFail;
73 private final boolean retry;
74 private final long reconnectIntervalMillis;
75 private final boolean truncateStrings;
76
77 protected FactoryData(final int bufferSize, final Layout<? extends Serializable> layout,
78 final ConnectionSource connectionSource, final String tableName, final ColumnConfig[] columnConfigs,
79 final ColumnMapping[] columnMappings, final boolean immediateFail, final long reconnectIntervalMillis,
80 final boolean truncateStrings) {
81 super(bufferSize, layout);
82 this.connectionSource = connectionSource;
83 this.tableName = tableName;
84 this.columnConfigs = columnConfigs;
85 this.columnMappings = columnMappings;
86 this.immediateFail = immediateFail;
87 this.retry = reconnectIntervalMillis > 0;
88 this.reconnectIntervalMillis = reconnectIntervalMillis;
89 this.truncateStrings = truncateStrings;
90 }
91
92 @Override
93 public String toString() {
94 return String.format(
95 "FactoryData [connectionSource=%s, tableName=%s, columnConfigs=%s, columnMappings=%s, immediateFail=%s, retry=%s, reconnectIntervalMillis=%s, truncateStrings=%s]",
96 connectionSource, tableName, Arrays.toString(columnConfigs), Arrays.toString(columnMappings),
97 immediateFail, retry, reconnectIntervalMillis, truncateStrings);
98 }
99 }
100
101
102
103
104 private static final class JdbcDatabaseManagerFactory implements ManagerFactory<JdbcDatabaseManager, FactoryData> {
105
106 private static final char PARAMETER_MARKER = '?';
107
108 @Override
109 public JdbcDatabaseManager createManager(final String name, final FactoryData data) {
110 final StringBuilder sb = new StringBuilder("insert into ").append(data.tableName).append(" (");
111
112
113 appendColumnNames("INSERT", data, sb);
114 sb.append(") values (");
115 int i = 1;
116 for (final ColumnMapping mapping : data.columnMappings) {
117 final String mappingName = mapping.getName();
118 if (Strings.isNotEmpty(mapping.getLiteralValue())) {
119 logger().trace("Adding INSERT VALUES literal for ColumnMapping[{}]: {}={} ", i, mappingName,
120 mapping.getLiteralValue());
121 sb.append(mapping.getLiteralValue());
122 } else if (Strings.isNotEmpty(mapping.getParameter())) {
123 logger().trace("Adding INSERT VALUES parameter for ColumnMapping[{}]: {}={} ", i, mappingName,
124 mapping.getParameter());
125 sb.append(mapping.getParameter());
126 } else {
127 logger().trace("Adding INSERT VALUES parameter marker for ColumnMapping[{}]: {}={} ", i,
128 mappingName, PARAMETER_MARKER);
129 sb.append(PARAMETER_MARKER);
130 }
131 sb.append(',');
132 i++;
133 }
134 final List<ColumnConfig> columnConfigs = new ArrayList<>(data.columnConfigs.length);
135 for (final ColumnConfig config : data.columnConfigs) {
136 if (Strings.isNotEmpty(config.getLiteralValue())) {
137 sb.append(config.getLiteralValue());
138 } else {
139 sb.append(PARAMETER_MARKER);
140 columnConfigs.add(config);
141 }
142 sb.append(',');
143 }
144
145 sb.setCharAt(sb.length() - 1, ')');
146 final String sqlStatement = sb.toString();
147
148 return new JdbcDatabaseManager(name, sqlStatement, columnConfigs, data);
149 }
150 }
151
152
153
154
155 private final class Reconnector extends Log4jThread {
156
157 private final CountDownLatch latch = new CountDownLatch(1);
158 private volatile boolean shutdown = false;
159
160 private Reconnector() {
161 super("JdbcDatabaseManager-Reconnector");
162 }
163
164 public void latch() {
165 try {
166 latch.await();
167 } catch (final InterruptedException ex) {
168
169 }
170 }
171
172 void reconnect() throws SQLException {
173 closeResources(false);
174 connectAndPrepare();
175 reconnector = null;
176 shutdown = true;
177 logger().debug("Connection reestablished to {}", factoryData);
178 }
179
180 @Override
181 public void run() {
182 while (!shutdown) {
183 try {
184 sleep(factoryData.reconnectIntervalMillis);
185 reconnect();
186 } catch (final InterruptedException | SQLException e) {
187 logger().debug("Cannot reestablish JDBC connection to {}: {}", factoryData, e.getLocalizedMessage(),
188 e);
189 } finally {
190 latch.countDown();
191 }
192 }
193 }
194
195 public void shutdown() {
196 shutdown = true;
197 }
198
199 }
200
201 private static final class ResultSetColumnMetaData {
202
203 private final String schemaName;
204 private final String catalogName;
205 private final String tableName;
206 private final String name;
207 private final String nameKey;
208 private final String label;
209 private final int displaySize;
210 private final int type;
211 private final String typeName;
212 private final String className;
213 private final int precision;
214 private final int scale;
215 private final boolean isStringType;
216
217 public ResultSetColumnMetaData(final ResultSetMetaData rsMetaData, final int j) throws SQLException {
218
219 this(rsMetaData.getSchemaName(j),
220 rsMetaData.getCatalogName(j),
221 rsMetaData.getTableName(j),
222 rsMetaData.getColumnName(j),
223 rsMetaData.getColumnLabel(j),
224 rsMetaData.getColumnDisplaySize(j),
225 rsMetaData.getColumnType(j),
226 rsMetaData.getColumnTypeName(j),
227 rsMetaData.getColumnClassName(j),
228 rsMetaData.getPrecision(j),
229 rsMetaData.getScale(j));
230
231 }
232
233 private ResultSetColumnMetaData(final String schemaName, final String catalogName, final String tableName,
234 final String name, final String label, final int displaySize, final int type, final String typeName,
235 final String className, final int precision, final int scale) {
236 super();
237 this.schemaName = schemaName;
238 this.catalogName = catalogName;
239 this.tableName = tableName;
240 this.name = name;
241 this.nameKey = ColumnMapping.toKey(name);
242 this.label = label;
243 this.displaySize = displaySize;
244 this.type = type;
245 this.typeName = typeName;
246 this.className = className;
247 this.precision = precision;
248 this.scale = scale;
249
250
251 this.isStringType =
252 type == Types.CHAR ||
253 type == Types.LONGNVARCHAR ||
254 type == Types.LONGVARCHAR ||
255 type == Types.NVARCHAR ||
256 type == Types.VARCHAR;
257
258 }
259
260 public String getCatalogName() {
261 return catalogName;
262 }
263
264 public String getClassName() {
265 return className;
266 }
267
268 public int getDisplaySize() {
269 return displaySize;
270 }
271
272 public String getLabel() {
273 return label;
274 }
275
276 public String getName() {
277 return name;
278 }
279
280 public String getNameKey() {
281 return nameKey;
282 }
283
284 public int getPrecision() {
285 return precision;
286 }
287
288 public int getScale() {
289 return scale;
290 }
291
292 public String getSchemaName() {
293 return schemaName;
294 }
295
296 public String getTableName() {
297 return tableName;
298 }
299
300 public int getType() {
301 return type;
302 }
303
304 public String getTypeName() {
305 return typeName;
306 }
307
308 public boolean isStringType() {
309 return this.isStringType;
310 }
311
312 @Override
313 public String toString() {
314 return String.format(
315 "ColumnMetaData [schemaName=%s, catalogName=%s, tableName=%s, name=%s, nameKey=%s, label=%s, displaySize=%s, type=%s, typeName=%s, className=%s, precision=%s, scale=%s, isStringType=%s]",
316 schemaName, catalogName, tableName, name, nameKey, label, displaySize, type, typeName, className,
317 precision, scale, isStringType);
318 }
319
320 public String truncate(final String string) {
321 return precision > 0 ? Strings.left(string, precision) : string;
322 }
323 }
324
325 private static final JdbcDatabaseManagerFactory INSTANCE = new JdbcDatabaseManagerFactory();
326
327 private static void appendColumnName(final int i, final String columnName, final StringBuilder sb) {
328 if (i > 1) {
329 sb.append(',');
330 }
331 sb.append(columnName);
332 }
333
334
335
336
337 private static void appendColumnNames(final String sqlVerb, final FactoryData data, final StringBuilder sb) {
338
339
340 int i = 1;
341 final String messagePattern = "Appending {} {}[{}]: {}={} ";
342 for (final ColumnMapping colMapping : data.columnMappings) {
343 final String columnName = colMapping.getName();
344 appendColumnName(i, columnName, sb);
345 logger().trace(messagePattern, sqlVerb, colMapping.getClass().getSimpleName(), i, columnName, colMapping);
346 i++;
347 }
348 for (final ColumnConfig colConfig : data.columnConfigs) {
349 final String columnName = colConfig.getColumnName();
350 appendColumnName(i, columnName, sb);
351 logger().trace(messagePattern, sqlVerb, colConfig.getClass().getSimpleName(), i, columnName, colConfig);
352 i++;
353 }
354 }
355
356 private static JdbcDatabaseManagerFactory getFactory() {
357 return INSTANCE;
358 }
359
360
361
362
363
364
365
366
367
368
369
370
371
372 @Deprecated
373 public static JdbcDatabaseManager getJDBCDatabaseManager(final String name, final int bufferSize,
374 final ConnectionSource connectionSource, final String tableName, final ColumnConfig[] columnConfigs) {
375 return getManager(
376 name, new FactoryData(bufferSize, null, connectionSource, tableName, columnConfigs,
377 new ColumnMapping[0], false, AbstractDatabaseAppender.DEFAULT_RECONNECT_INTERVAL_MILLIS, true),
378 getFactory());
379 }
380
381
382
383
384
385
386
387
388
389
390
391
392
393 @Deprecated
394 public static JdbcDatabaseManager getManager(final String name, final int bufferSize,
395 final Layout<? extends Serializable> layout, final ConnectionSource connectionSource,
396 final String tableName, final ColumnConfig[] columnConfigs, final ColumnMapping[] columnMappings) {
397 return getManager(name, new FactoryData(bufferSize, layout, connectionSource, tableName, columnConfigs,
398 columnMappings, false, AbstractDatabaseAppender.DEFAULT_RECONNECT_INTERVAL_MILLIS, true), getFactory());
399 }
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417 @Deprecated
418 public static JdbcDatabaseManager getManager(final String name, final int bufferSize,
419 final Layout<? extends Serializable> layout, final ConnectionSource connectionSource,
420 final String tableName, final ColumnConfig[] columnConfigs, final ColumnMapping[] columnMappings,
421 final boolean immediateFail, final long reconnectIntervalMillis) {
422 return getManager(name, new FactoryData(bufferSize, null, connectionSource, tableName, columnConfigs,
423 columnMappings, false, AbstractDatabaseAppender.DEFAULT_RECONNECT_INTERVAL_MILLIS, true), getFactory());
424 }
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442 public static JdbcDatabaseManager getManager(final String name, final int bufferSize,
443 final Layout<? extends Serializable> layout, final ConnectionSource connectionSource,
444 final String tableName, final ColumnConfig[] columnConfigs, final ColumnMapping[] columnMappings,
445 final boolean immediateFail, final long reconnectIntervalMillis, final boolean truncateStrings) {
446 return getManager(name, new FactoryData(bufferSize, layout, connectionSource, tableName, columnConfigs,
447 columnMappings, immediateFail, reconnectIntervalMillis, truncateStrings), getFactory());
448 }
449
450
451 private final List<ColumnConfig> columnConfigs;
452 private final String sqlStatement;
453 private final FactoryData factoryData;
454 private volatile Connection connection;
455 private volatile PreparedStatement statement;
456 private volatile Reconnector reconnector;
457 private volatile boolean isBatchSupported;
458 private volatile Map<String, ResultSetColumnMetaData> columnMetaData;
459
460 private JdbcDatabaseManager(final String name, final String sqlStatement, final List<ColumnConfig> columnConfigs,
461 final FactoryData factoryData) {
462 super(name, factoryData.getBufferSize());
463 this.sqlStatement = sqlStatement;
464 this.columnConfigs = columnConfigs;
465 this.factoryData = factoryData;
466 }
467
468 private void checkConnection() {
469 boolean connClosed = true;
470 try {
471 connClosed = this.connection == null || this.connection.isClosed();
472 } catch (final SQLException e) {
473
474 }
475 boolean stmtClosed = true;
476 try {
477 stmtClosed = this.statement == null || this.statement.isClosed();
478 } catch (final SQLException e) {
479
480 }
481 if (!this.isRunning() || connClosed || stmtClosed) {
482
483 closeResources(false);
484
485 if (reconnector != null && !factoryData.immediateFail) {
486 reconnector.latch();
487 if (connection == null) {
488 throw new AppenderLoggingException(
489 "Error writing to JDBC Manager '" + getName() + "': JDBC connection not available.");
490 }
491 if (statement == null) {
492 throw new AppenderLoggingException(
493 "Error writing to JDBC Manager '" + getName() + "': JDBC statement not available.");
494 }
495 }
496 }
497 }
498
499 protected void closeResources(final boolean logExceptions) {
500 try {
501
502
503 Closer.close(this.statement);
504 } catch (final Exception e) {
505 if (logExceptions) {
506 logWarn("Failed to close SQL statement logging event or flushing buffer", e);
507 }
508 } finally {
509 this.statement = null;
510 }
511
512 try {
513
514
515 Closer.close(this.connection);
516 } catch (final Exception e) {
517 if (logExceptions) {
518 logWarn("Failed to close database connection logging event or flushing buffer", e);
519 }
520 } finally {
521 this.connection = null;
522 }
523 }
524
525 @Override
526 protected boolean commitAndClose() {
527 final boolean closed = true;
528 try {
529 if (this.connection != null && !this.connection.isClosed()) {
530 if (this.isBatchSupported && this.statement != null) {
531 logger().debug("Executing batch PreparedStatement {}", this.statement);
532 final int[] result = this.statement.executeBatch();
533 logger().debug("Batch result: {}", Arrays.toString(result));
534 }
535 logger().debug("Committing Connection {}", this.connection);
536 this.connection.commit();
537 }
538 } catch (final SQLException e) {
539 throw new DbAppenderLoggingException("Failed to commit transaction logging event or flushing buffer.", e);
540 } finally {
541 closeResources(true);
542 }
543 return closed;
544 }
545
546 private boolean commitAndCloseAll() {
547 if (this.connection != null || this.statement != null) {
548 try {
549 this.commitAndClose();
550 return true;
551 } catch (final AppenderLoggingException e) {
552
553 final Throwable cause = e.getCause();
554 final Throwable actual = cause == null ? e : cause;
555 logger().debug("{} committing and closing connection: {}", actual, actual.getClass().getSimpleName(),
556 e.toString(), e);
557 }
558 }
559 if (factoryData.connectionSource != null) {
560 factoryData.connectionSource.stop();
561 }
562 return true;
563 }
564
565 private void connectAndPrepare() throws SQLException {
566 logger().debug("Acquiring JDBC connection from {}", this.getConnectionSource());
567 this.connection = getConnectionSource().getConnection();
568 logger().debug("Acquired JDBC connection {}", this.connection);
569 logger().debug("Getting connection metadata {}", this.connection);
570 final DatabaseMetaData databaseMetaData = this.connection.getMetaData();
571 logger().debug("Connection metadata {}", databaseMetaData);
572 this.isBatchSupported = databaseMetaData.supportsBatchUpdates();
573 logger().debug("Connection supportsBatchUpdates: {}", this.isBatchSupported);
574 this.connection.setAutoCommit(false);
575 logger().debug("Preparing SQL {}", this.sqlStatement);
576 this.statement = this.connection.prepareStatement(this.sqlStatement);
577 logger().debug("Prepared SQL {}", this.statement);
578 if (this.factoryData.truncateStrings) {
579 initColumnMetaData();
580 }
581 }
582
583 @Override
584 protected void connectAndStart() {
585 checkConnection();
586 synchronized (this) {
587 try {
588 connectAndPrepare();
589 } catch (final SQLException e) {
590 reconnectOn(e);
591 }
592 }
593 }
594
595 private Reconnector createReconnector() {
596 final Reconnector recon = new Reconnector();
597 recon.setDaemon(true);
598 recon.setPriority(Thread.MIN_PRIORITY);
599 return recon;
600 }
601
602 private String createSqlSelect() {
603 final StringBuilder sb = new StringBuilder("select ");
604 appendColumnNames("SELECT", this.factoryData, sb);
605 sb.append(" from ");
606 sb.append(this.factoryData.tableName);
607 sb.append(" where 1=0");
608 return sb.toString();
609 }
610
611 public ConnectionSource getConnectionSource() {
612 return factoryData.connectionSource;
613 }
614
615 public String getSqlStatement() {
616 return sqlStatement;
617 }
618
619 public String getTableName() {
620 return factoryData.tableName;
621 }
622
623 private void initColumnMetaData() throws SQLException {
624
625
626
627 final String sqlSelect = createSqlSelect();
628 logger().debug("Getting SQL metadata for table {}: {}", this.factoryData.tableName, sqlSelect);
629 try (final PreparedStatement mdStatement = this.connection.prepareStatement(sqlSelect)) {
630 final ResultSetMetaData rsMetaData = mdStatement.getMetaData();
631 logger().debug("SQL metadata: {}", rsMetaData);
632 if (rsMetaData != null) {
633 final int columnCount = rsMetaData.getColumnCount();
634 columnMetaData = new HashMap<>(columnCount);
635 for (int i = 0, j = 1; i < columnCount; i++, j++) {
636 final ResultSetColumnMetaData value = new ResultSetColumnMetaData(rsMetaData, j);
637 columnMetaData.put(value.getNameKey(), value);
638 }
639 } else {
640 logger().warn(
641 "{}: truncateStrings is true and ResultSetMetaData is null for statement: {}; manager will not perform truncation.",
642 getClass().getSimpleName(), mdStatement);
643 }
644 }
645 }
646
647 private void reconnectOn(final Exception exception) {
648 if (!factoryData.retry) {
649 throw new AppenderLoggingException("Cannot connect and prepare", exception);
650 }
651 if (reconnector == null) {
652 reconnector = createReconnector();
653 try {
654 reconnector.reconnect();
655 } catch (final SQLException reconnectEx) {
656 logger().debug("Cannot reestablish JDBC connection to {}: {}; starting reconnector thread {}",
657 factoryData, reconnectEx, reconnector.getName(), reconnectEx);
658 reconnector.start();
659 reconnector.latch();
660 if (connection == null || statement == null) {
661 throw new AppenderLoggingException(
662 String.format("Error sending to %s for %s", getName(), factoryData), exception);
663 }
664 }
665 }
666 }
667
668 private void setFields(final MapMessage<?, ?> mapMessage) throws SQLException {
669 final IndexedReadOnlyStringMap map = mapMessage.getIndexedReadOnlyStringMap();
670 final String simpleName = statement.getClass().getName();
671 int j = 1;
672 for (final ColumnMapping mapping : this.factoryData.columnMappings) {
673 if (mapping.getLiteralValue() == null) {
674 final String source = mapping.getSource();
675 final String key = Strings.isEmpty(source) ? mapping.getName() : source;
676 final Object value = map.getValue(key);
677 if (logger().isTraceEnabled()) {
678 final String valueStr = value instanceof String ? "\"" + value + "\""
679 : Objects.toString(value, null);
680 logger().trace("{} setObject({}, {}) for key '{}' and mapping '{}'", simpleName, j, valueStr, key,
681 mapping.getName());
682 }
683 setStatementObject(j, mapping.getNameKey(), value);
684 j++;
685 }
686 }
687 }
688
689
690
691
692 private void setStatementObject(final int j, final String nameKey, final Object value) throws SQLException {
693 statement.setObject(j, truncate(nameKey, value));
694 }
695
696 @Override
697 protected boolean shutdownInternal() {
698 if (reconnector != null) {
699 reconnector.shutdown();
700 reconnector.interrupt();
701 reconnector = null;
702 }
703 return commitAndCloseAll();
704 }
705
706 @Override
707 protected void startupInternal() throws Exception {
708
709 }
710
711
712
713
714 private Object truncate(final String nameKey, Object value) {
715 if (value != null && this.factoryData.truncateStrings && columnMetaData != null) {
716 final ResultSetColumnMetaData resultSetColumnMetaData = columnMetaData.get(nameKey);
717 if (resultSetColumnMetaData != null) {
718 if (resultSetColumnMetaData.isStringType()) {
719 value = resultSetColumnMetaData.truncate(value.toString());
720 }
721 } else {
722 logger().error("Missing ResultSetColumnMetaData for {}", nameKey);
723 }
724 }
725 return value;
726 }
727
728 @Override
729 protected void writeInternal(final LogEvent event, final Serializable serializable) {
730 StringReader reader = null;
731 try {
732 if (!this.isRunning() || this.connection == null || this.connection.isClosed() || this.statement == null
733 || this.statement.isClosed()) {
734 throw new AppenderLoggingException(
735 "Cannot write logging event; JDBC manager not connected to the database.");
736 }
737
738 statement.clearParameters();
739 if (serializable instanceof MapMessage) {
740 setFields((MapMessage<?, ?>) serializable);
741 }
742 int j = 1;
743 for (final ColumnMapping mapping : this.factoryData.columnMappings) {
744 if (ThreadContextMap.class.isAssignableFrom(mapping.getType())
745 || ReadOnlyStringMap.class.isAssignableFrom(mapping.getType())) {
746 this.statement.setObject(j++, event.getContextData().toMap());
747 } else if (ThreadContextStack.class.isAssignableFrom(mapping.getType())) {
748 this.statement.setObject(j++, event.getContextStack().asList());
749 } else if (Date.class.isAssignableFrom(mapping.getType())) {
750 this.statement.setObject(j++, DateTypeConverter.fromMillis(event.getTimeMillis(),
751 mapping.getType().asSubclass(Date.class)));
752 } else {
753 final StringLayout layout = mapping.getLayout();
754 if (layout != null) {
755 if (Clob.class.isAssignableFrom(mapping.getType())) {
756 this.statement.setClob(j++, new StringReader(layout.toSerializable(event)));
757 } else if (NClob.class.isAssignableFrom(mapping.getType())) {
758 this.statement.setNClob(j++, new StringReader(layout.toSerializable(event)));
759 } else {
760 final Object value = TypeConverters.convert(layout.toSerializable(event), mapping.getType(),
761 null);
762 if (value == null) {
763
764 this.statement.setNull(j++, Types.NULL);
765 } else {
766 setStatementObject(j++, mapping.getNameKey(), value);
767 }
768 }
769 }
770 }
771 }
772 for (final ColumnConfig column : this.columnConfigs) {
773 if (column.isEventTimestamp()) {
774 this.statement.setTimestamp(j++, new Timestamp(event.getTimeMillis()));
775 } else if (column.isClob()) {
776 reader = new StringReader(column.getLayout().toSerializable(event));
777 if (column.isUnicode()) {
778 this.statement.setNClob(j++, reader);
779 } else {
780 this.statement.setClob(j++, reader);
781 }
782 } else if (column.isUnicode()) {
783 this.statement.setNString(j++, Objects.toString(
784 truncate(column.getColumnNameKey(), column.getLayout().toSerializable(event)), null));
785 } else {
786 this.statement.setString(j++, Objects.toString(
787 truncate(column.getColumnNameKey(), column.getLayout().toSerializable(event)), null));
788 }
789 }
790
791 if (this.isBatchSupported) {
792 this.statement.addBatch();
793 } else if (this.statement.executeUpdate() == 0) {
794 throw new AppenderLoggingException(
795 "No records inserted in database table for log event in JDBC manager.");
796 }
797 } catch (final SQLException e) {
798 throw new DbAppenderLoggingException(
799 "Failed to insert record for log event in JDBC manager: " + e.getMessage(), e);
800 } finally {
801
802 try {
803 statement.clearParameters();
804 } catch (final SQLException e) {
805
806 }
807 Closer.closeSilently(reader);
808 }
809 }
810
811 @Override
812 protected void writeThrough(final LogEvent event, final Serializable serializable) {
813 this.connectAndStart();
814 try {
815 try {
816 this.writeInternal(event, serializable);
817 } finally {
818 this.commitAndClose();
819 }
820 } catch (final DbAppenderLoggingException e) {
821 reconnectOn(e);
822 try {
823 this.writeInternal(event, serializable);
824 } finally {
825 this.commitAndClose();
826 }
827 }
828 }
829
830 }