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