001/**
002 * Logback: the reliable, generic, fast and flexible logging framework.
003 * Copyright (C) 1999-2015, QOS.ch. All rights reserved.
004 *
005 * This program and the accompanying materials are dual-licensed under
006 * either the terms of the Eclipse Public License v1.0 as published by
007 * the Eclipse Foundation
008 *
009 *   or (per the licensee's choosing)
010 *
011 * under the terms of the GNU Lesser General Public License version 2.1
012 * as published by the Free Software Foundation.
013 */
014package ch.qos.logback.core.db;
015
016import java.lang.reflect.InvocationTargetException;
017import java.lang.reflect.Method;
018import java.sql.Connection;
019import java.sql.PreparedStatement;
020import java.sql.ResultSet;
021import java.sql.SQLException;
022import java.sql.Statement;
023
024import ch.qos.logback.core.UnsynchronizedAppenderBase;
025import ch.qos.logback.core.db.dialect.DBUtil;
026import ch.qos.logback.core.db.dialect.SQLDialect;
027import ch.qos.logback.core.db.dialect.SQLDialectCode;
028
029/**
030 * @author Ceki Gülcü
031 * @author Ray DeCampo
032 * @author Sébastien Pennec
033 */
034public abstract class DBAppenderBase<E> extends UnsynchronizedAppenderBase<E> {
035
036    protected ConnectionSource connectionSource;
037    protected boolean cnxSupportsGetGeneratedKeys = false;
038    protected boolean cnxSupportsBatchUpdates = false;
039    protected SQLDialect sqlDialect;
040
041    protected abstract Method getGeneratedKeysMethod();
042
043    protected abstract String getInsertSQL();
044
045    @Override
046    public void start() {
047
048        if (connectionSource == null) {
049            throw new IllegalStateException("DBAppender cannot function without a connection source");
050        }
051
052        sqlDialect = DBUtil.getDialectFromCode(connectionSource.getSQLDialectCode());
053        if (getGeneratedKeysMethod() != null) {
054            cnxSupportsGetGeneratedKeys = connectionSource.supportsGetGeneratedKeys();
055        } else {
056            cnxSupportsGetGeneratedKeys = false;
057        }
058        cnxSupportsBatchUpdates = connectionSource.supportsBatchUpdates();
059        if (!cnxSupportsGetGeneratedKeys && (sqlDialect == null)) {
060            throw new IllegalStateException(
061                            "DBAppender cannot function if the JDBC driver does not support getGeneratedKeys method *and* without a specific SQL dialect");
062        }
063
064        // all nice and dandy on the eastern front
065        super.start();
066    }
067
068    /**
069     * @return Returns the connectionSource.
070     */
071    public ConnectionSource getConnectionSource() {
072        return connectionSource;
073    }
074
075    /**
076     * @param connectionSource
077     *          The connectionSource to set.
078     */
079    public void setConnectionSource(ConnectionSource connectionSource) {
080        this.connectionSource = connectionSource;
081    }
082
083    @Override
084    public void append(E eventObject) {
085        Connection connection = null;
086        PreparedStatement insertStatement = null;
087        try {
088            connection = connectionSource.getConnection();
089            connection.setAutoCommit(false);
090
091            if (cnxSupportsGetGeneratedKeys) {
092                String EVENT_ID_COL_NAME = "EVENT_ID";
093                // see
094                if (connectionSource.getSQLDialectCode() == SQLDialectCode.POSTGRES_DIALECT) {
095                    EVENT_ID_COL_NAME = EVENT_ID_COL_NAME.toLowerCase();
096                }
097                insertStatement = connection.prepareStatement(getInsertSQL(), new String[] { EVENT_ID_COL_NAME });
098            } else {
099                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}