/*
* RapidMiner
*
* Copyright (C) 2001-2014 by RapidMiner and the contributors
*
* Complete list of developers available at our web site:
*
* http://rapidminer.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see http://www.gnu.org/licenses/.
*/
package com.rapidminer.tools.jdbc;
import java.sql.SQLException;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import com.rapidminer.tools.ProgressListener;
/**
* This class caches the table meta data so the DB is not queried all the time.
*
* @author Marco Boeck
*
*/
public class TableMetaDataCache {
private Map<String, Map<TableName, List<ColumnIdentifier>>> tableMap;
/** time of the last cache update */
private Map<String, Long> lastQueryTimeMap;
private Map<String, Object> lockMap;
private boolean refreshCacheAfterInterval;
/** time before the cache is refreshed again (in ms) */
private static final int CACHE_REFRESH_INTERVAL = 60000;
/** if true, will refresh cache if set amount of times expired since last cache update; if false will never update cache automatically */
/** the instance of this class */
private static TableMetaDataCache instance;
/**
* Creates a new {@link TableMetaDataCache} instance.
* @param refreshCacheAfterInterval
*/
private TableMetaDataCache(boolean refreshCacheAfterInterval) {
this.lastQueryTimeMap = new ConcurrentHashMap<String, Long>();
this.tableMap = new ConcurrentHashMap<String, Map<TableName, List<ColumnIdentifier>>>();
this.lockMap = new ConcurrentHashMap<String, Object>();
this.refreshCacheAfterInterval = refreshCacheAfterInterval;
}
/**
* Get the instance of {@link TableMetaDataCache}.
* @return
*/
public static synchronized TableMetaDataCache getInstance() {
if (instance == null) {
// currently, default for this cache is to NOT refresh after a given interval
instance = new TableMetaDataCache(false);
}
return instance;
}
/**
* Fetches meta data about all tables and, if selected, all columns in the database.
* The returned map maps table names to column descriptions.
* If fetchColumns is false, all lists in the returned map will be empty lists, so basically
* only the key set contains useful information.
* <p>
* This method is cached, so the data might not be up to date before the cache is refreshed.
* See {@link #clearCache()}.
* @param connectionName
* @param handler
* @param progressListener
* @param minProgress
* @param maxProgress
* @param fetchColumns
* @return
* @throws SQLException
*/
public Map<TableName, List<ColumnIdentifier>> getAllTableMetaData(String connectionName, DatabaseHandler handler,
ProgressListener progressListener, int minProgress, int maxProgress)
throws SQLException {
// does lock for this connection already exist?
if (!lockMap.containsKey(connectionName)) {
lockMap.put(connectionName, new Object());
}
// only lock for the same connectionName aka same connection - different connections don't need to wait
synchronized (lockMap.get(connectionName)) {
Map<TableName, List<ColumnIdentifier>> map = this.tableMap.get(connectionName);
if (map == null || (refreshCacheAfterInterval && (System.currentTimeMillis() - this.lastQueryTimeMap
.get(connectionName)) > CACHE_REFRESH_INTERVAL)) {
// if tableMap does not contain entry for this connectionName or the entry is too old (only if refreshCacheAfterInterval is true)
// update cache and return new map, otherwise return cached map
updateCache(connectionName, handler, progressListener, minProgress, maxProgress, true);
map = tableMap.get(connectionName);
}
progressListener.setCompleted(maxProgress);
return map;
}
}
/**
* Fetches meta data about all tables and, if selected, all columns in the database.
* The returned map maps table names to column descriptions.
* If fetchColumns is false, all lists in the returned map will be empty lists, so basically
* only the key set contains useful information.
* <p>
* This method is cached, so the data might not be up to date before the cache is refreshed.
* See {@link #clearCache()}.
* @param connectionName
* @param handler
* @return
* @throws SQLException
*/
public Map<TableName, List<ColumnIdentifier>> getAllTableMetaData(String connectionName, DatabaseHandler handler)
throws SQLException {
return getAllTableMetaData(connectionName, handler, null, 0, 0);
}
/**
* Fetches the for a given connection name.
* <p>
* This method is cached, so the data might not be up to date before the cache is refreshed.
* See {@link #clearCache()}.
* @param connectionName
* @param handler
* @return
* @throws SQLException
*/
public List<ColumnIdentifier> getAllColumnNames(String connectionName, DatabaseHandler handler, TableName tableName) throws SQLException {
// does lock for this connection already exist?
if (!lockMap.containsKey(connectionName)) {
lockMap.put(connectionName, new Object());
}
// only lock for the same connectionName aka same connection - different connections don't need to wait
synchronized (lockMap.get(connectionName)) {
Map<TableName, List<ColumnIdentifier>> map = this.tableMap.get(connectionName);
if (map == null || (refreshCacheAfterInterval && (System.currentTimeMillis() - this.lastQueryTimeMap
.get(connectionName)) > CACHE_REFRESH_INTERVAL)) {
// if tableMap does not contain entry for this connectionName or the entry is too old (only if refreshCacheAfterInterval is true)
// update cache and return new map, otherwise return cached map
updateCache(connectionName, handler, null, 0, 0, true);
map = this.tableMap.get(connectionName);
}
return map.get(tableName);
}
}
/**
* Clears the whole cache.
*/
public void clearCache() {
this.tableMap.clear();
}
/**
* Updates the cache.
* @throws SQLException
*/
private void updateCache(String connectionName, DatabaseHandler handler, ProgressListener progressListener,
int minProgress, int maxProgress, boolean fetchColumns) throws SQLException {
Map<TableName, List<ColumnIdentifier>> tableMetaMap = handler.getAllTableMetaData(progressListener, minProgress, maxProgress, fetchColumns);
this.tableMap.put(connectionName, tableMetaMap);
this.lastQueryTimeMap.put(connectionName, new Long(System.currentTimeMillis()));
}
}