/** * 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.flume.channel.jdbc.impl; import java.sql.Connection; import java.sql.SQLException; import java.sql.SQLWarning; import javax.sql.DataSource; import org.apache.flume.Transaction; import org.apache.flume.channel.jdbc.JdbcChannelException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class JdbcTransactionImpl implements Transaction { private static final Logger LOGGER = LoggerFactory.getLogger(JdbcTransactionImpl.class); private final DataSource dataSource; private final JdbcChannelProviderImpl providerImpl; private Connection connection; private JdbcTransactionFactory txFactory; private boolean active = true; /** Reference count used to do the eventual commit.*/ private int count = 0; /** Number of events successfully removed from the channel. */ private int removedEventCount = 0; /** Number of events persisted to the channel. */ private int persistedEventCount = 0; /** Flag that indicates if the transaction must be rolled back. */ private boolean rollback = false; protected JdbcTransactionImpl(DataSource dataSource, JdbcTransactionFactory factory, JdbcChannelProviderImpl provider) { this.dataSource = dataSource; txFactory = factory; providerImpl = provider; } @Override public void begin() { if (!active) { throw new JdbcChannelException("Inactive transaction"); } if (count == 0) { // Lease a connection now try { connection = dataSource.getConnection(); } catch (SQLException ex) { throw new JdbcChannelException("Unable to lease connection", ex); } // Clear any prior warnings on the connection try { connection.clearWarnings(); } catch (SQLException ex) { LOGGER.error("Error while clearing warnings: " + ex.getErrorCode(), ex); } } count++; LOGGER.trace("Tx count-begin: " + count + ", rollback: " + rollback); } @Override public void commit() { if (!active) { throw new JdbcChannelException("Inactive transaction"); } if (rollback) { throw new JdbcChannelException( "Cannot commit transaction marked for rollback"); } LOGGER.trace("Tx count-commit: " + count + ", rollback: " + rollback); } @Override public void rollback() { if (!active) { throw new JdbcChannelException("Inactive transaction"); } LOGGER.warn("Marking transaction for rollback"); rollback = true; LOGGER.trace("Tx count-rollback: " + count + ", rollback: " + rollback); } @Override public void close() { if (!active) { throw new JdbcChannelException("Inactive transaction"); } count--; LOGGER.debug("Tx count-close: " + count + ", rollback: " + rollback); if (count == 0) { active = false; try { if (rollback) { LOGGER.info("Attempting transaction roll-back"); connection.rollback(); } else { LOGGER.debug("Attempting transaction commit"); connection.commit(); // Commit successful. Update provider channel size providerImpl.updateCurrentChannelSize(this.persistedEventCount - this.removedEventCount); this.persistedEventCount = 0; this.removedEventCount = 0; } } catch (SQLException ex) { throw new JdbcChannelException("Unable to finalize transaction", ex); } finally { if (connection != null) { // Log Warnings try { SQLWarning warning = connection.getWarnings(); if (warning != null) { StringBuilder sb = new StringBuilder("Connection warnigns: "); boolean first = true; while (warning != null) { if (first) { first = false; } else { sb.append("; "); } sb.append("[").append(warning.getErrorCode()).append("] "); sb.append(warning.getMessage()); } LOGGER.warn(sb.toString()); } } catch (SQLException ex) { LOGGER.error("Error while retrieving warnigns: " + ex.getErrorCode(), ex); } // Close Connection try { connection.close(); } catch (SQLException ex) { LOGGER.error( "Unable to close connection: " + ex.getErrorCode(), ex); } } // Clean up thread local txFactory.remove(); // Destroy local state connection = null; txFactory = null; } } } protected Connection getConnection() { if (!active) { throw new JdbcChannelException("Inactive transaction"); } return connection; } protected void incrementRemovedEventCount() { removedEventCount++; } protected void incrementPersistedEventCount() { persistedEventCount++; } }