View Javadoc
1   /**
2    * Logback: the reliable, generic, fast and flexible logging framework.
3    * Copyright (C) 1999-2015, QOS.ch. All rights reserved.
4    *
5    * This program and the accompanying materials are dual-licensed under
6    * either the terms of the Eclipse Public License v1.0 as published by
7    * the Eclipse Foundation
8    *
9    *   or (per the licensee's choosing)
10   *
11   * under the terms of the GNU Lesser General Public License version 2.1
12   * as published by the Free Software Foundation.
13   */
14  package ch.qos.logback.core.db;
15  
16  import java.lang.reflect.InvocationTargetException;
17  import java.lang.reflect.Method;
18  import java.sql.Connection;
19  import java.sql.PreparedStatement;
20  import java.sql.ResultSet;
21  import java.sql.SQLException;
22  import java.sql.Statement;
23  
24  import ch.qos.logback.core.UnsynchronizedAppenderBase;
25  import ch.qos.logback.core.db.dialect.DBUtil;
26  import ch.qos.logback.core.db.dialect.SQLDialect;
27  import ch.qos.logback.core.db.dialect.SQLDialectCode;
28  
29  /**
30   * @author Ceki Gülcü
31   * @author Ray DeCampo
32   * @author Sébastien Pennec
33   */
34  public abstract class DBAppenderBase<E> extends UnsynchronizedAppenderBase<E> {
35  
36      protected ConnectionSource connectionSource;
37      protected boolean cnxSupportsGetGeneratedKeys = false;
38      protected boolean cnxSupportsBatchUpdates = false;
39      protected SQLDialect sqlDialect;
40  
41      protected abstract Method getGeneratedKeysMethod();
42  
43      protected abstract String getInsertSQL();
44  
45      @Override
46      public void start() {
47  
48          if (connectionSource == null) {
49              throw new IllegalStateException("DBAppender cannot function without a connection source");
50          }
51  
52          sqlDialect = DBUtil.getDialectFromCode(connectionSource.getSQLDialectCode());
53          if (getGeneratedKeysMethod() != null) {
54              cnxSupportsGetGeneratedKeys = connectionSource.supportsGetGeneratedKeys();
55          } else {
56              cnxSupportsGetGeneratedKeys = false;
57          }
58          cnxSupportsBatchUpdates = connectionSource.supportsBatchUpdates();
59          if (!cnxSupportsGetGeneratedKeys && (sqlDialect == null)) {
60              throw new IllegalStateException(
61                              "DBAppender cannot function if the JDBC driver does not support getGeneratedKeys method *and* without a specific SQL dialect");
62          }
63  
64          // all nice and dandy on the eastern front
65          super.start();
66      }
67  
68      /**
69       * @return Returns the connectionSource.
70       */
71      public ConnectionSource getConnectionSource() {
72          return connectionSource;
73      }
74  
75      /**
76       * @param connectionSource
77       *          The connectionSource to set.
78       */
79      public void setConnectionSource(ConnectionSource connectionSource) {
80          this.connectionSource = connectionSource;
81      }
82  
83      @Override
84      public void append(E eventObject) {
85          Connection connection = null;
86          PreparedStatement insertStatement = null;
87          try {
88              connection = connectionSource.getConnection();
89              connection.setAutoCommit(false);
90  
91              if (cnxSupportsGetGeneratedKeys) {
92                  String EVENT_ID_COL_NAME = "EVENT_ID";
93                  // see
94                  if (connectionSource.getSQLDialectCode() == SQLDialectCode.POSTGRES_DIALECT) {
95                      EVENT_ID_COL_NAME = EVENT_ID_COL_NAME.toLowerCase();
96                  }
97                  insertStatement = connection.prepareStatement(getInsertSQL(), new String[] { EVENT_ID_COL_NAME });
98              } else {
99                  insertStatement = connection.prepareStatement(getInsertSQL());
100             }
101 
102             long eventId;
103             // inserting an event and getting the result must be exclusive
104             synchronized (this) {
105                 subAppend(eventObject, connection, insertStatement);
106                 eventId = selectEventId(insertStatement, connection);
107             }
108             secondarySubAppend(eventObject, connection, eventId);
109 
110             connection.commit();
111         } catch (Throwable sqle) {
112             addError("problem appending event", sqle);
113         } finally {
114             DBHelper.closeStatement(insertStatement);
115             DBHelper.closeConnection(connection);
116         }
117     }
118 
119     protected abstract void subAppend(E eventObject, Connection connection, PreparedStatement statement) throws Throwable;
120 
121     protected abstract void secondarySubAppend(E eventObject, Connection connection, long eventId) throws Throwable;
122 
123     @SuppressWarnings("resource")
124     protected long selectEventId(PreparedStatement insertStatement, Connection connection) throws SQLException, InvocationTargetException {
125         ResultSet rs = null;
126         Statement idStatement = null;
127         try {
128             boolean gotGeneratedKeys = false;
129             if (cnxSupportsGetGeneratedKeys) {
130                 try {
131                     rs = (ResultSet) getGeneratedKeysMethod().invoke(insertStatement, (Object[]) null);
132                     gotGeneratedKeys = true;
133                 } catch (InvocationTargetException ex) {
134                     Throwable target = ex.getTargetException();
135                     if (target instanceof SQLException) {
136                         throw (SQLException) target;
137                     }
138                     throw ex;
139                 } catch (IllegalAccessException ex) {
140                     addWarn("IllegalAccessException invoking PreparedStatement.getGeneratedKeys", ex);
141                 }
142             }
143 
144             if (!gotGeneratedKeys) {
145                 idStatement = connection.createStatement();
146                 idStatement.setMaxRows(1);
147                 String selectInsertIdStr = sqlDialect.getSelectInsertId();
148                 rs = idStatement.executeQuery(selectInsertIdStr);
149             }
150 
151             // A ResultSet cursor is initially positioned before the first row;
152             // the first call to the method next makes the first row the current row
153             rs.next();
154             long eventId = rs.getLong(1);
155             return eventId;
156         } finally {
157             if (rs != null) {
158                 try {
159                     rs.close();
160                 } catch (SQLException e) {
161                 }
162             }
163             DBHelper.closeStatement(idStatement);
164         }
165     }
166 
167     @Override
168     public void stop() {
169         super.stop();
170     }
171 }