/*! ******************************************************************************
*
* Pentaho Data Integration
*
* Copyright (C) 2002-2013 by Pentaho : http://www.pentaho.com
*
*******************************************************************************
*
* Licensed 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.pentaho.di.core;
import java.io.BufferedOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.util.Enumeration;
import java.util.Hashtable;
import org.pentaho.di.core.exception.KettleEOFException;
import org.pentaho.di.core.exception.KettleFileException;
import org.pentaho.di.core.logging.LogChannel;
import org.pentaho.di.core.logging.LogChannelInterface;
import org.pentaho.di.core.row.RowMeta;
import org.pentaho.di.core.row.RowMetaInterface;
import org.pentaho.di.version.BuildVersion;
/**
* This class caches database queries so that the same query doesn't get called twice. Queries are often launched to the
* databases to get information on tables etc.
*
* @author Matt
* @since 15-01-04
*
*/
public class DBCache {
private static DBCache dbCache;
private Hashtable<DBCacheEntry, RowMetaInterface> cache;
private boolean usecache;
private LogChannelInterface log;
public void setActive() {
setActive( true );
}
public void setInactive() {
setActive( false );
}
public void setActive( boolean act ) {
usecache = act;
}
public boolean isActive() {
return usecache;
}
public void put( DBCacheEntry entry, RowMetaInterface fields ) {
if ( !usecache ) {
return;
}
RowMetaInterface copy = fields.clone();
cache.put( entry, copy );
// System.out.println("Cache store: "+copy.toStringMeta());
// System.out.println("Cache store entry="+entry.sql );
}
/**
* Get the fields as a row generated by a database cache entry
*
* @param entry
* the entry to look for
* @return the fields as a row generated by a database cache entry
*/
public RowMetaInterface get( DBCacheEntry entry ) {
if ( !usecache ) {
return null;
}
RowMetaInterface fields = cache.get( entry );
if ( fields != null ) {
fields = fields.clone(); // Copy it again!
// System.out.println("Cache hit!!, fields="+fields.toStringMeta() );
// System.out.println("Cache hit entry="+entry.sql );
}
return fields;
}
public int size() {
return cache.size();
}
/**
* Clear out all entries of database with a certain name
*
* @param dbname
* The name of the database for which we want to clear the cache or null if we want to clear it all.
*/
public void clear( String dbname ) {
if ( dbname == null ) {
cache = new Hashtable<DBCacheEntry, RowMetaInterface>();
setActive();
} else {
Enumeration<DBCacheEntry> keys = cache.keys();
while ( keys.hasMoreElements() ) {
DBCacheEntry entry = keys.nextElement();
if ( entry.sameDB( dbname ) ) {
// Same name: remove it!
cache.remove( entry );
}
}
}
}
public String getFilename() {
return Const.getKettleDirectory()
+ Const.FILE_SEPARATOR + "db.cache-" + BuildVersion.getInstance().getVersion();
}
private DBCache() throws KettleFileException {
try {
clear( null );
// Serialization support for the DB cache
//
log = new LogChannel( "DBCache" );
String filename = getFilename();
File file = new File( filename );
if ( file.canRead() ) {
log.logDetailed( "Loading database cache from file: [" + filename + "]" );
FileInputStream fis = null;
DataInputStream dis = null;
try {
fis = new FileInputStream( file );
dis = new DataInputStream( fis );
int counter = 0;
try {
while ( true ) {
DBCacheEntry entry = new DBCacheEntry( dis );
RowMetaInterface row = new RowMeta( dis );
cache.put( entry, row );
counter++;
}
} catch ( KettleEOFException eof ) {
log.logDetailed( "We read " + counter + " cached rows from the database cache!" );
}
} catch ( Exception e ) {
throw new Exception( e );
} finally {
if ( dis != null ) {
dis.close();
}
}
} else {
log.logDetailed( "The database cache doesn't exist yet." );
}
} catch ( Exception e ) {
throw new KettleFileException( "Couldn't read the database cache", e );
}
}
public void saveCache() throws KettleFileException {
try {
// Serialization support for the DB cache
//
String filename = getFilename();
File file = new File( filename );
if ( !file.exists() || file.canWrite() ) {
FileOutputStream fos = null;
DataOutputStream dos = null;
try {
fos = new FileOutputStream( file );
dos = new DataOutputStream( new BufferedOutputStream( fos, 10000 ) );
int counter = 0;
boolean ok = true;
Enumeration<DBCacheEntry> keys = cache.keys();
while ( ok && keys.hasMoreElements() ) {
// Save the database cache entry
DBCacheEntry entry = keys.nextElement();
entry.write( dos );
// Save the corresponding row as well.
RowMetaInterface rowMeta = get( entry );
if ( rowMeta != null ) {
rowMeta.writeMeta( dos );
counter++;
} else {
throw new KettleFileException( "The database cache contains an empty row. We can't save this!" );
}
}
log.logDetailed( "We wrote " + counter + " cached rows to the database cache!" );
} catch ( Exception e ) {
throw new Exception( e );
} finally {
if ( dos != null ) {
dos.close();
}
}
} else {
throw new KettleFileException( "We can't write to the cache file: " + filename );
}
} catch ( Exception e ) {
throw new KettleFileException( "Couldn't write to the database cache", e );
}
}
/**
* Create the database cache instance by loading it from disk
*
* @return the database cache instance.
* @throws KettleFileException
*/
public static final DBCache getInstance() {
if ( dbCache != null ) {
return dbCache;
}
try {
dbCache = new DBCache();
} catch ( KettleFileException kfe ) {
throw new RuntimeException( "Unable to create the database cache: " + kfe.getMessage() );
}
return dbCache;
}
}