/*
* JBoss, Home of Professional Open Source
* Copyright 2009 Red Hat Inc. and/or its affiliates and other
* contributors as indicated by the @author tags. All rights reserved.
* See the copyright.txt in the distribution for a full listing of
* individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.infinispan.loaders.jdbc;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.HashSet;
import java.util.Iterator;
import java.util.NoSuchElementException;
import java.util.Set;
import org.infinispan.container.entries.InternalCacheEntry;
import org.infinispan.loaders.CacheLoaderException;
import org.infinispan.loaders.jdbc.connectionfactory.ConnectionFactory;
import org.infinispan.marshall.StreamingMarshaller;
import org.infinispan.loaders.jdbc.logging.Log;
import org.infinispan.util.logging.LogFactory;
/**
* The purpose of this class is to factorize the repeating code between {@link org.infinispan.loaders.jdbc.stringbased.JdbcStringBasedCacheStore}
* and {@link org.infinispan.loaders.jdbc.binary.JdbcBinaryCacheStore}. This class implements GOF's template method pattern.
*
* @author Mircea.Markus@jboss.com
*/
public abstract class DataManipulationHelper {
private static final Log log = LogFactory.getLog(DataManipulationHelper.class, Log.class);
private final ConnectionFactory connectionFactory;
private final TableManipulation tableManipulation;
protected StreamingMarshaller marshaller;
public DataManipulationHelper(ConnectionFactory connectionFactory, TableManipulation tableManipulation, StreamingMarshaller marshaller) {
this.connectionFactory = connectionFactory;
this.tableManipulation = tableManipulation;
this.marshaller = marshaller;
}
public void clear() throws CacheLoaderException {
Connection conn = null;
PreparedStatement ps = null;
try {
String sql = tableManipulation.getDeleteAllRowsSql();
conn = connectionFactory.getConnection();
ps = conn.prepareStatement(sql);
int result = ps.executeUpdate();
if (log.isTraceEnabled()) {
log.tracef("Successfully removed %d rows.", result);
}
} catch (SQLException ex) {
log.failedClearingJdbcCacheStore(ex);
throw new CacheLoaderException("Failed clearing cache store", ex);
} finally {
JdbcUtil.safeClose(ps);
connectionFactory.releaseConnection(conn);
}
}
public final void fromStreamSupport(ObjectInput objectInput) throws CacheLoaderException {
Connection conn = null;
PreparedStatement ps = null;
try {
conn = connectionFactory.getConnection();
String sql = tableManipulation.getInsertRowSql();
ps = conn.prepareStatement(sql);
int readCount = 0;
int batchSize = tableManipulation.getBatchSize();
Object objFromStream = marshaller.objectFromObjectStream(objectInput);
while (fromStreamProcess(objFromStream, ps, objectInput)) {
ps.addBatch();
readCount++;
if (readCount % batchSize == 0) {
ps.executeBatch();
if (log.isTraceEnabled()) {
log.tracef("Executing batch %s, batch size is %d", readCount / batchSize, batchSize);
}
}
objFromStream = marshaller.objectFromObjectStream(objectInput);
}
if (readCount % batchSize != 0) {
ps.executeBatch();//flush the batch
}
if (log.isTraceEnabled()) {
log.tracef("Successfully inserted %d buckets into the database, batch size is %d", readCount, batchSize);
}
} catch (IOException ex) {
log.ioErrorIntegratingState(ex);
throw new CacheLoaderException("I/O failure while integrating state into store", ex);
} catch (SQLException e) {
log.sqlFailureIntegratingState(e);
throw new CacheLoaderException("SQL failure while integrating state into store", e);
} catch (ClassNotFoundException e) {
log.classNotFoundIntegratingState(e);
throw new CacheLoaderException("Unexpected failure while integrating state into store", e);
} catch (InterruptedException ie) {
if (log.isTraceEnabled()) log.trace("Interrupted while reading from stream");
Thread.currentThread().interrupt();
} finally {
JdbcUtil.safeClose(ps);
connectionFactory.releaseConnection(conn);
}
}
public final void toStreamSupport(ObjectOutput objectOutput, byte streamDelimiter, boolean filterExpired) throws CacheLoaderException {
//now write our data
Connection connection = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
String sql = filterExpired ? tableManipulation.getLoadNonExpiredAllRowsSql() : tableManipulation.getLoadAllRowsSql();
if (log.isTraceEnabled()) {
log.tracef("Running sql %s", sql);
}
connection = connectionFactory.getConnection();
ps = connection.prepareStatement(sql);
if (filterExpired) {
ps.setLong(1, System.currentTimeMillis());
}
rs = ps.executeQuery();
rs.setFetchSize(tableManipulation.getFetchSize());
while (rs.next()) {
InputStream is = rs.getBinaryStream(1);
toStreamProcess(rs, is, objectOutput);
}
marshaller.objectToObjectStream(streamDelimiter, objectOutput);
} catch (SQLException e) {
log.sqlFailureStoringKeys(e);
throw new CacheLoaderException("SQL Error while storing string keys to database", e);
} catch (IOException e) {
log.ioErrorStoringKeys(e);
throw new CacheLoaderException("I/O Error while storing string keys to database", e);
}
finally {
JdbcUtil.safeClose(rs);
JdbcUtil.safeClose(ps);
connectionFactory.releaseConnection(connection);
}
}
public final Set<InternalCacheEntry> loadAllSupport(boolean filterExpired) throws CacheLoaderException {
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
String sql = filterExpired ? tableManipulation.getLoadNonExpiredAllRowsSql() : tableManipulation.getLoadAllRowsSql();
if (log.isTraceEnabled()) {
log.tracef("Running sql %s", sql);
}
conn = connectionFactory.getConnection();
ps = conn.prepareStatement(sql);
if (filterExpired) {
ps.setLong(1, System.currentTimeMillis());
}
rs = ps.executeQuery();
rs.setFetchSize(tableManipulation.getFetchSize());
Set<InternalCacheEntry> result = new HashSet<InternalCacheEntry>(tableManipulation.getFetchSize());
while (rs.next()) {
loadAllProcess(rs, result);
}
return result;
} catch (SQLException e) {
log.sqlFailureFetchingAllStoredEntries(e);
throw new CacheLoaderException("SQL error while fetching all StoredEntries", e);
} finally {
JdbcUtil.safeClose(rs);
JdbcUtil.safeClose(ps);
connectionFactory.releaseConnection(conn);
}
}
public Set<Object> loadAllKeysSupport(Set<Object> keysToExclude) throws CacheLoaderException {
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
String sql = getLoadAllKeysSql();
if (log.isTraceEnabled()) {
log.trace("Running sql '" + sql);
}
conn = connectionFactory.getConnection();
ps = conn.prepareStatement(sql);
rs = ps.executeQuery();
rs.setFetchSize(tableManipulation.getFetchSize());
Set<Object> result = new HashSet<Object>(tableManipulation.getFetchSize());
while (rs.next()) {
loadAllKeysProcess(rs, result, keysToExclude);
}
return result;
} catch (SQLException e) {
log.sqlFailureFetchingAllStoredEntries(e);
throw new CacheLoaderException("SQL error while fetching all StoredEntries", e);
} finally {
JdbcUtil.safeClose(rs);
JdbcUtil.safeClose(ps);
connectionFactory.releaseConnection(conn);
}
}
public final Set<InternalCacheEntry> loadSome(int maxEntries) throws CacheLoaderException {
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
String sql = tableManipulation.getLoadSomeRowsSql();
if (log.isTraceEnabled()) {
log.trace("Running sql '" + sql);
}
conn = connectionFactory.getConnection();
if (tableManipulation.isVariableLimitSupported()) {
ps = conn.prepareStatement(sql);
ps.setInt(1, maxEntries);
} else {
ps = conn.prepareStatement(sql.replace("?", String.valueOf(maxEntries)));
}
rs = ps.executeQuery();
rs.setFetchSize(tableManipulation.getFetchSize());
Set<InternalCacheEntry> result = new HashSet<InternalCacheEntry>(maxEntries);
while (rs.next()) {
loadAllProcess(rs, result, maxEntries);
}
return result;
} catch (SQLException e) {
log.sqlFailureFetchingAllStoredEntries(e);
throw new CacheLoaderException("SQL error while fetching all StoredEntries", e);
} finally {
JdbcUtil.safeClose(rs);
JdbcUtil.safeClose(ps);
connectionFactory.releaseConnection(conn);
}
}
public final Iterator<Set<InternalCacheEntry>> loadAllIterator(boolean filterExpired) throws CacheLoaderException {
Connection conn;
PreparedStatement ps;
ResultSet rs;
try {
String sql = filterExpired ? tableManipulation.getLoadNonExpiredAllRowsSql() :
tableManipulation.getLoadAllRowsSql();
if (log.isTraceEnabled()) {
log.tracef("Running sql %s", sql);
}
conn = connectionFactory.getConnection();
ps = conn.prepareStatement(sql);
if (filterExpired) {
ps.setLong(1, System.currentTimeMillis());
}
ps.setFetchSize(tableManipulation.getFetchSize());
rs = ps.executeQuery();
rs.setFetchSize(tableManipulation.getFetchSize());
return new LoadAllIterator(rs, ps, conn, tableManipulation.getFetchSize());
} catch (SQLException e) {
log.sqlFailureFetchingAllStoredEntries(e);
throw new CacheLoaderException("SQL error while fetching all StoredEntries", e);
}
}
public final Iterator<Set<InternalCacheEntry>> loadSomeIterator(int maxEntries) throws CacheLoaderException {
Connection conn;
PreparedStatement ps;
ResultSet rs;
try {
String sql = tableManipulation.getLoadSomeRowsSql();
if (log.isTraceEnabled()) {
log.trace("Running sql '" + sql);
}
conn = connectionFactory.getConnection();
if (tableManipulation.isVariableLimitSupported()) {
ps = conn.prepareStatement(sql);
ps.setInt(1, maxEntries);
} else {
ps = conn.prepareStatement(sql.replace("?", String.valueOf(maxEntries)));
}
ps.setFetchSize(tableManipulation.getFetchSize());
rs = ps.executeQuery();
rs.setFetchSize(tableManipulation.getFetchSize());
return new LoadAllIterator(rs, ps, conn, tableManipulation.getFetchSize());
} catch (SQLException e) {
log.sqlFailureFetchingAllStoredEntries(e);
throw new CacheLoaderException("SQL error while fetching all StoredEntries", e);
}
}
protected boolean includeKey(Object key, Set<Object> keysToExclude) {
return keysToExclude == null || !keysToExclude.contains(key);
}
protected abstract String getLoadAllKeysSql();
protected abstract void loadAllProcess(ResultSet rs, Set<InternalCacheEntry> result) throws SQLException, CacheLoaderException;
protected abstract void loadAllProcess(ResultSet rs, Set<InternalCacheEntry> result, int maxEntries) throws SQLException, CacheLoaderException;
protected abstract void loadAllKeysProcess(ResultSet rs, Set<Object> keys, Set<Object> keysToExclude) throws SQLException, CacheLoaderException;
protected abstract void toStreamProcess(ResultSet rs, InputStream is, ObjectOutput objectOutput) throws CacheLoaderException, SQLException, IOException;
protected abstract boolean fromStreamProcess(Object objFromStream, PreparedStatement ps, ObjectInput objectInput)
throws SQLException, CacheLoaderException, IOException, ClassNotFoundException, InterruptedException;
private class LoadAllIterator implements Iterator<Set<InternalCacheEntry>> {
private final ResultSet resultSet;
private final PreparedStatement preparedStatement;
private final Connection connection;
private final int fetchSize;
private boolean hasNext;
private LoadAllIterator(ResultSet resultSet, PreparedStatement preparedStatement, Connection connection, int fetchSize) {
this.resultSet = resultSet;
this.preparedStatement = preparedStatement;
this.connection = connection;
this.fetchSize = fetchSize;
iterateResultSet();
}
@Override
public boolean hasNext() {
return hasNext;
}
@Override
public Set<InternalCacheEntry> next() {
if (!hasNext) {
close();
throw new NoSuchElementException();
}
Set<InternalCacheEntry> internalCacheEntries = new HashSet<InternalCacheEntry>();
do {
if (!add(internalCacheEntries)) {
return internalCacheEntries;
}
iterateResultSet();
} while (hasNext && internalCacheEntries.size() < fetchSize);
return internalCacheEntries;
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
private boolean add(Set<InternalCacheEntry> result) {
try {
loadAllProcess(resultSet, result);
return true;
} catch (Exception e) {
hasNext = false;
close();
return false;
}
}
private void iterateResultSet() {
try {
hasNext = resultSet.next();
} catch (SQLException e) {
hasNext = false;
}
if (!hasNext) {
close();
}
}
private void close() {
JdbcUtil.safeClose(resultSet);
JdbcUtil.safeClose(preparedStatement);
connectionFactory.releaseConnection(connection);
}
}
}