Package org.apache.jcs.auxiliary.disk.jdbc

Source Code of org.apache.jcs.auxiliary.disk.jdbc.JDBCDiskCache

package org.apache.jcs.auxiliary.disk.jdbc;

/*
* 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.
*/

import java.io.IOException;
import java.io.Serializable;
import java.sql.Connection;
import java.sql.Date;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Set;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.jcs.auxiliary.AuxiliaryCacheAttributes;
import org.apache.jcs.auxiliary.disk.AbstractDiskCache;
import org.apache.jcs.engine.CacheConstants;
import org.apache.jcs.engine.behavior.ICacheElement;
import org.apache.jcs.engine.behavior.IElementSerializer;
import org.apache.jcs.engine.stats.StatElement;
import org.apache.jcs.engine.stats.behavior.IStatElement;
import org.apache.jcs.engine.stats.behavior.IStats;
import org.apache.jcs.utils.serialization.StandardSerializer;

/**
* This is the jdbc disk cache plugin.
* <p>
* It expects a table created by the following script. The table name is
* configurable.
* <p>
*
* <pre>
*                       drop TABLE JCS_STORE;
*
*                       CREATE TABLE JCS_STORE
*                       (
*                       CACHE_KEY                  VARCHAR(250)          NOT NULL,
*                       REGION                     VARCHAR(250)          NOT NULL,
*                       ELEMENT                    BLOB,
*                       CREATE_TIME                DATE,
*                       CREATE_TIME_SECONDS        BIGINT,
*                       MAX_LIFE_SECONDS           BIGINT,
*                       SYSTEM_EXPIRE_TIME_SECONDS BIGINT,
*                       IS_ETERNAL                 CHAR(1),
*                       PRIMARY KEY (CACHE_KEY, REGION)
*                       );
* </pre>
*
* <p>
* The cleanup thread will delete non eternal items where (now - create time) >
* max life seconds * 1000
* <p>
* To speed up the deletion the SYSTEM_EXPIRE_TIME_SECONDS is used instead. It
* is recommended that an index be created on this column is you will have over
* a million records.
* <p>
* @author Aaron Smuts
*/
public class JDBCDiskCache
    extends AbstractDiskCache
{
    private final static Log log = LogFactory.getLog( JDBCDiskCache.class );

    private static final long serialVersionUID = -7169488308515823492L;

    private IElementSerializer elementSerializer = new StandardSerializer();

    private JDBCDiskCacheAttributes jdbcDiskCacheAttributes;

    private int updateCount = 0;

    private int getCount = 0;

    // if count % interval == 0 then log
    private static final int LOG_INTERVAL = 100;

    private JDBCDiskCachePoolAccess poolAccess = null;

    private TableState tableState;

    /**
     * Constructs a JDBC Disk Cache for the provided cache attributes. The table
     * state object is used to mark deletions.
     * <p>
     * @param cattr
     * @param tableState
     */
    public JDBCDiskCache( JDBCDiskCacheAttributes cattr, TableState tableState )
    {
        super( cattr );

        this.setTableState( tableState );

        setJdbcDiskCacheAttributes( cattr );

        if ( log.isInfoEnabled() )
        {
            log.info( "jdbcDiskCacheAttributes = " + getJdbcDiskCacheAttributes() );
        }

        /**
         * This initializes the pool access.
         */
        initializePoolAccess( cattr );

        // Initialization finished successfully, so set alive to true.
        alive = true;
    }

    /**
     * Registers the driver and creates a poolAccess class.
     * @param cattr
     */
    protected void initializePoolAccess( JDBCDiskCacheAttributes cattr )
    {
        try
        {
            try
            {
                // org.gjt.mm.mysql.Driver
                Class.forName( cattr.getDriverClassName() );
            }
            catch ( ClassNotFoundException e )
            {
                log.error( "Couldn't find class for driver [" + cattr.getDriverClassName() + "]", e );
            }

            poolAccess = new JDBCDiskCachePoolAccess( cattr.getName() );

            poolAccess.setupDriver( cattr.getUrl() + cattr.getDatabase(), cattr.getUserName(), cattr.getPassword(),
                                    cattr.getMaxActive() );

            poolAccess.logDriverStats();
        }
        catch ( Exception e )
        {
            log.error( "Problem getting connection.", e );
        }
    }

    /*
     * (non-Javadoc)
     * @see org.apache.jcs.auxiliary.disk.AbstractDiskCache#doUpdate(org.apache.jcs.engine.behavior.ICacheElement)
     */
    public void doUpdate( ICacheElement ce )
    {
        incrementUpdateCount();

        if ( log.isDebugEnabled() )
        {
            log.debug( "updating, ce = " + ce );
        }

        Connection con;
        try
        {
            con = poolAccess.getConnection();
        }
        catch ( SQLException e )
        {
            log.error( "Problem getting conenction.", e );
            return;
        }

        try
        {
            // TEST
            Statement sStatement = null;
            try
            {
                sStatement = con.createStatement();
                alive = true;
            }
            catch ( SQLException e )
            {
                log.error( "Problem creating statement.", e );
                alive = false;
            }
            finally
            {
                try
                {
                    sStatement.close();
                }
                catch ( SQLException e )
                {
                    log.error( "Problem closing statement.", e );
                }
            }

            if ( !alive )
            {
                if ( log.isInfoEnabled() )
                {
                    log.info( "Disk is not alive, aborting put." );
                }
                return;
            }

            if ( log.isDebugEnabled() )
            {
                log.debug( "Putting [" + ce.getKey() + "] on disk." );
            }

            byte[] element;

            try
            {
                element = serialize( ce );
            }
            catch ( IOException e )
            {
                log.error( "Could not serialize element", e );
                return;
            }

            boolean exists = false;

            // First do a query to determine if the element already exists
            if ( this.getJdbcDiskCacheAttributes().isTestBeforeInsert() )
            {
                exists = doesElementExist( ce );
            }

            // If it doesn't exist, insert it, otherwise update
            if ( !exists )
            {
                try
                {
                    String sqlI = "insert into "
                        + getJdbcDiskCacheAttributes().getTableName()
                        + " (CACHE_KEY, REGION, ELEMENT, MAX_LIFE_SECONDS, IS_ETERNAL, CREATE_TIME, CREATE_TIME_SECONDS, SYSTEM_EXPIRE_TIME_SECONDS) "
                        + " values (?, ?, ?, ?, ?, ?, ?, ?)";

                    PreparedStatement psInsert = con.prepareStatement( sqlI );
                    psInsert.setString( 1, (String) ce.getKey() );
                    psInsert.setString( 2, this.getCacheName() );
                    psInsert.setBytes( 3, element );
                    psInsert.setLong( 4, ce.getElementAttributes().getMaxLifeSeconds() );
                    if ( ce.getElementAttributes().getIsEternal() )
                    {
                        psInsert.setString( 5, "T" );
                    }
                    else
                    {
                        psInsert.setString( 5, "F" );
                    }
                    Date createTime = new Date( ce.getElementAttributes().getCreateTime() );
                    psInsert.setDate( 6, createTime );

                    long now = System.currentTimeMillis() / 1000;
                    psInsert.setLong( 7, now );

                    long expireTime = now + ce.getElementAttributes().getMaxLifeSeconds();
                    psInsert.setLong( 8, expireTime );

                    psInsert.execute();
                    psInsert.close();
                }
                catch ( SQLException e )
                {
                    if ( e.toString().indexOf( "Violation of unique index" ) != -1
                        || e.getMessage().indexOf( "Violation of unique index" ) != -1
                        || e.getMessage().indexOf( "Duplicate entry" ) != -1 )
                    {
                        exists = true;
                    }
                    else
                    {
                        log.error( "Could not insert element", e );
                    }

                    // see if it exists, if we didn't already
                    if ( !exists && !this.getJdbcDiskCacheAttributes().isTestBeforeInsert() )
                    {
                        exists = doesElementExist( ce );
                    }
                }
            }

            // update if it exists.
            if ( exists )
            {
                String sqlU = null;
                try
                {
                    sqlU = "update " + getJdbcDiskCacheAttributes().getTableName()
                        + " set ELEMENT  = ?, CREATE_TIME = ?, CREATE_TIME_SECONDS = ?, "
                        + " SYSTEM_EXPIRE_TIME_SECONDS = ? " + " where CACHE_KEY = ? and REGION = ?";
                    PreparedStatement psUpdate = con.prepareStatement( sqlU );
                    psUpdate.setBytes( 1, element );

                    Date createTime = new Date( ce.getElementAttributes().getCreateTime() );
                    psUpdate.setDate( 2, createTime );

                    long now = System.currentTimeMillis() / 1000;
                    psUpdate.setLong( 3, now );

                    long expireTime = now + ce.getElementAttributes().getMaxLifeSeconds();
                    psUpdate.setLong( 4, expireTime );

                    psUpdate.setString( 5, (String) ce.getKey() );
                    psUpdate.setString( 6, this.getCacheName() );
                    psUpdate.execute();
                    psUpdate.close();

                    if ( log.isDebugEnabled() )
                    {
                        log.debug( "ran update " + sqlU );
                    }
                }
                catch ( SQLException e2 )
                {
                    log.error( "e2 sql [" + sqlU + "] Exception: ", e2 );
                }
            }
        }
        finally
        {
            try
            {
                con.close();
            }
            catch ( SQLException e )
            {
                log.error( "Problem closing connection.", e );
            }
        }

        if ( log.isInfoEnabled() )
        {
            if ( updateCount % LOG_INTERVAL == 0 )
            {
                // TODO make a log stats method
                log.info( "Update Count [" + updateCount + "]" );
            }
        }
    }

    /**
     * Does an element exist for this key?
     * <p>
     * @param ce
     * @return boolean
     */
    protected boolean doesElementExist( ICacheElement ce )
    {
        boolean exists = false;

        Connection con;
        try
        {
            con = poolAccess.getConnection();
        }
        catch ( SQLException e )
        {
            log.error( "Problem getting conenction.", e );
            return exists;
        }

        PreparedStatement psSelect = null;
        try
        {
            // don't select the element, since we want this to be fast.
            String sqlS = "select CACHE_KEY from " + getJdbcDiskCacheAttributes().getTableName()
                + " where REGION = ? and CACHE_KEY = ?";

            psSelect = con.prepareStatement( sqlS );
            psSelect.setString( 1, this.getCacheName() );
            psSelect.setString( 2, (String) ce.getKey() );

            ResultSet rs = psSelect.executeQuery();

            if ( rs.next() )
            {
                exists = true;
            }

            if ( log.isDebugEnabled() )
            {
                log.debug( "[" + ce.getKey() + "] existing status is " + exists );
            }

            rs.close();
        }
        catch ( SQLException e )
        {
            log.error( "Problem looking for item before insert.", e );
        }
        finally
        {
            try
            {
                if ( psSelect != null )
                {
                    psSelect.close();
                }
                psSelect.close();
            }
            catch ( SQLException e1 )
            {
                log.error( "Problem closing statement.", e1 );
            }

            try
            {
                con.close();
            }
            catch ( SQLException e )
            {
                log.error( "Problem closing connection.", e );
            }
        }

        return exists;
    }

    /**
     * Queries the database for the value. If it gets a result, the value is
     * deserialized.
     * <p>
     * @see org.apache.jcs.auxiliary.disk.AbstractDiskCache#doGet(java.io.Serializable)
     */
    public ICacheElement doGet( Serializable key )
    {
        incrementGetCount();

        if ( log.isDebugEnabled() )
        {
            log.debug( "Getting " + key + " from disk" );
        }

        if ( !alive )
        {
            return null;
        }

        ICacheElement obj = null;

        byte[] data = null;
        try
        {
            // region, key
            String selectString = "select ELEMENT from " + getJdbcDiskCacheAttributes().getTableName()
                + " where REGION = ? and CACHE_KEY = ?";

            Connection con = poolAccess.getConnection();
            try
            {
                PreparedStatement psSelect = null;
                try
                {
                    psSelect = con.prepareStatement( selectString );
                    psSelect.setString( 1, this.getCacheName() );
                    psSelect.setString( 2, key.toString() );

                    ResultSet rs = psSelect.executeQuery();
                    try
                    {
                        if ( rs.next() )
                        {
                            data = rs.getBytes( 1 );
                        }
                        if ( data != null )
                        {
                            try
                            {
                                // USE THE SERIALIZER
                                obj = (ICacheElement) getElementSerializer().deSerialize( data );
                            }
                            catch ( IOException ioe )
                            {
                                log.error( ioe );
                            }
                            catch ( Exception e )
                            {
                                log.error( "Problem getting item.", e );
                            }
                        }
                    }
                    finally
                    {
                        if ( rs != null )
                        {
                            rs.close();
                        }
                        rs.close();
                    }
                }
                finally
                {
                    if ( psSelect != null )
                    {
                        psSelect.close();
                    }
                    psSelect.close();
                }
            }
            finally
            {
                if ( con != null )
                {
                    con.close();
                }
            }
        }
        catch ( SQLException sqle )
        {
            log.error( sqle );
        }

        if ( log.isInfoEnabled() )
        {
            if ( getCount % LOG_INTERVAL == 0 )
            {
                // TODO make a log stats method
                log.info( "Get Count [" + getCount + "]" );
            }
        }

        return obj;
    }

    /**
     * Returns true if the removal was succesful; or false if there is nothing
     * to remove. Current implementation always result in a disk orphan.
     * <p>
     * @param key
     * @return boolean
     */
    public boolean doRemove( Serializable key )
    {
        // remove single item.
        String sql = "delete from " + getJdbcDiskCacheAttributes().getTableName()
            + " where REGION = ? and CACHE_KEY = ?";

        try
        {
            boolean partial = false;
            if ( key instanceof String && key.toString().endsWith( CacheConstants.NAME_COMPONENT_DELIMITER ) )
            {
                // remove all keys of the same name group.
                sql = "delete from " + getJdbcDiskCacheAttributes().getTableName()
                    + " where REGION = ? and CACHE_KEY like ?";
                partial = true;
            }
            Connection con = poolAccess.getConnection();
            PreparedStatement psSelect = null;
            try
            {
                psSelect = con.prepareStatement( sql );
                psSelect.setString( 1, this.getCacheName() );
                if ( partial )
                {
                    psSelect.setString( 2, key.toString() + "%" );
                }
                else
                {
                    psSelect.setString( 2, key.toString() );
                }

                psSelect.executeUpdate( );

                alive = true;
            }
            catch ( SQLException e )
            {
                log.error( "Problem creating statement. sql [" + sql + "]", e );
                alive = false;
            }
            finally
            {
                try
                {
                    if ( psSelect != null )
                    {
                        psSelect.close();
                    }
                    con.close();
                }
                catch ( SQLException e1 )
                {
                    log.error( "Problem closing statement.", e1 );
                }
            }

        }
        catch ( Exception e )
        {
            log.error( "Problem updating cache.", e );
            reset();
        }
        return false;
    }

    /** This should remove all elements. */
    public void doRemoveAll()
    {
        // it should never get here formt he abstract dis cache.
        if ( this.jdbcDiskCacheAttributes.isAllowRemoveAll() )
        {
            try
            {
                String sql = "delete from " + getJdbcDiskCacheAttributes().getTableName() + " where REGION = ?";
                Connection con = poolAccess.getConnection();
                PreparedStatement psDelete = null;
                try
                {
                    psDelete = con.prepareStatement( sql );
                    psDelete.setString( 1, this.getCacheName() );
                    alive = true;
                    psDelete.executeUpdate( );
                }
                catch ( SQLException e )
                {
                    log.error( "Problem creating statement.", e );
                    alive = false;
                }
                finally
                {
                    try
                    {
                        if ( psDelete != null )
                        {
                            psDelete.close();
                        }
                        con.close();
                    }
                    catch ( SQLException e1 )
                    {
                        log.error( "Problem closing statement.", e1 );
                    }
                }
            }
            catch ( Exception e )
            {
                log.error( "Problem removing all.", e );
                reset();
            }
        }
        else
        {
            if ( log.isInfoEnabled() )
            {
                log.info( "RemoveAll was requested but the request was not fulfilled: allowRemoveAll is set to false." );
            }
        }
    }

    /**
     * Removed the expired. (now - create time) > max life seconds * 1000
     * <p>
     * @return the number deleted
     */
    protected int deleteExpired()
    {
        int deleted = 0;

        try
        {
            getTableState().setState( TableState.DELETE_RUNNING );

            long now = System.currentTimeMillis() / 1000;

            // This is to slow when we push over a million records
            // String sql = "delete from " +
            // getJdbcDiskCacheAttributes().getTableName() + " where REGION = '"
            // + this.getCacheName() + "' and IS_ETERNAL = 'F' and (" + now
            // + " - CREATE_TIME_SECONDS) > MAX_LIFE_SECONDS";

            String sql = "delete from " + getJdbcDiskCacheAttributes().getTableName()
                + " where IS_ETERNAL = ? and REGION = ? and ? > SYSTEM_EXPIRE_TIME_SECONDS";

            Connection con = poolAccess.getConnection();
            PreparedStatement psDelete = null;
            try
            {
                psDelete = con.prepareStatement( sql );
                psDelete.setString( 1, "F" );
                psDelete.setString( 2, this.getCacheName() );
                psDelete.setLong( 3, now );

                alive = true;

                deleted = psDelete.executeUpdate( );
            }
            catch ( SQLException e )
            {
                log.error( "Problem creating statement.", e );
                alive = false;
            }
            finally
            {
                try
                {
                    if ( psDelete != null )
                    {
                        psDelete.close();
                    }
                    con.close();
                }
                catch ( SQLException e1 )
                {
                    log.error( "Problem closing statement.", e1 );
                }
            }
        }
        catch ( Exception e )
        {
            log.error( "Problem removing expired elements from the table.", e );
            reset();
        }
        finally
        {
            getTableState().setState( TableState.FREE );
        }

        return deleted;
    }

    /**
     * Typically this is used to handle errors by last resort, force content
     * update, or removeall
     */
    public void reset()
    {
        // nothing
    }

    /** Shuts down the pool */
    public void doDispose()
    {
        try
        {
            poolAccess.shutdownDriver();
        }
        catch ( Exception e )
        {
            log.error( "Problem shutting down.", e );
        }
    }

    /**
     * Returns the current cache size. Just does a count(*) for the region.
     * <p>
     * @return The size value
     */
    public int getSize()
    {
        int size = 0;

        // region, key
        String selectString = "select count(*) from " + getJdbcDiskCacheAttributes().getTableName()
            + " where REGION = ?";

        Connection con;
        try
        {
            con = poolAccess.getConnection();
        }
        catch ( SQLException e1 )
        {
            log.error( "Problem getting conenction.", e1 );
            return size;
        }
        try
        {
            PreparedStatement psSelect = null;
            try
            {
                psSelect = con.prepareStatement( selectString );
                psSelect.setString( 1, this.getCacheName() );
                ResultSet rs = null;

                rs = psSelect.executeQuery();
                try
                {
                    if ( rs.next() )
                    {
                        size = rs.getInt( 1 );
                    }
                }
                finally
                {
                    if ( rs != null )
                    {
                        rs.close();
                    }
                    rs.close();
                }
            }
            finally
            {
                if ( psSelect != null )
                {
                    psSelect.close();
                }
                psSelect.close();
            }
        }
        catch ( SQLException e )
        {
            log.error( "Problem getting size.", e );
        }
        finally
        {
            try
            {
                con.close();
            }
            catch ( SQLException e )
            {
                log.error( "Problem closing connection.", e );
            }
        }
        return size;
    }

    /**
     * Returns the serialized form of the given object in a byte array.
     * <p>
     * @param obj
     * @return byte[]
     * @throws IOException
     */
    protected byte[] serialize( Serializable obj )
        throws IOException
    {
        return getElementSerializer().serialize( obj );
    }

    /**
     * @param groupName
     * @return
     */
    public Set getGroupKeys( String groupName )
    {
        if ( true )
        {
            throw new UnsupportedOperationException( "Groups not implemented." );
        }
        return null;
    }

    /**
     * @param elementSerializer
     *            The elementSerializer to set.
     */
    public void setElementSerializer( IElementSerializer elementSerializer )
    {
        this.elementSerializer = elementSerializer;
    }

    /**
     * @return Returns the elementSerializer.
     */
    public IElementSerializer getElementSerializer()
    {
        return elementSerializer;
    }

    /** safely increment */
    private synchronized void incrementUpdateCount()
    {
        updateCount++;
    }

    /** safely increment */
    private synchronized void incrementGetCount()
    {
        getCount++;
    }

    /**
     * @param jdbcDiskCacheAttributes
     *            The jdbcDiskCacheAttributes to set.
     */
    protected void setJdbcDiskCacheAttributes( JDBCDiskCacheAttributes jdbcDiskCacheAttributes )
    {
        this.jdbcDiskCacheAttributes = jdbcDiskCacheAttributes;
    }

    /**
     * @return Returns the jdbcDiskCacheAttributes.
     */
    protected JDBCDiskCacheAttributes getJdbcDiskCacheAttributes()
    {
        return jdbcDiskCacheAttributes;
    }

    /**
     * @return Returns the AuxiliaryCacheAttributes.
     */
    public AuxiliaryCacheAttributes getAuxiliaryCacheAttributes()
    {
        return this.getJdbcDiskCacheAttributes();
    }

    /**
     * Extends the parent stats.
     */
    public IStats getStatistics()
    {
        IStats stats = super.getStatistics();
        stats.setTypeName( "JDBC/Abstract Disk Cache" );
        stats.getStatElements();

        ArrayList elems = new ArrayList();

        IStatElement se = null;

        se = new StatElement();
        se.setName( "Update Count" );
        se.setData( "" + updateCount );
        elems.add( se );

        se = new StatElement();
        se.setName( "Get Count" );
        se.setData( "" + getCount );
        elems.add( se );

        se = new StatElement();
        se.setName( "Size" );
        se.setData( "" + getSize() );
        elems.add( se );

        se = new StatElement();
        se.setName( "Active DB Connections" );
        se.setData( "" + poolAccess.getNumActiveInPool() );
        elems.add( se );

        se = new StatElement();
        se.setName( "Idle DB Connections" );
        se.setData( "" + poolAccess.getNumIdleInPool() );
        elems.add( se );

        se = new StatElement();
        se.setName( "DB URL" );
        se.setData( this.jdbcDiskCacheAttributes.getUrl() );
        elems.add( se );

        // get the stats from the event queue too
        // get as array, convert to list, add list to our outer list
        IStatElement[] eqSEs = stats.getStatElements();
        List eqL = Arrays.asList( eqSEs );
        elems.addAll( eqL );

        // get an array and put them in the Stats object
        IStatElement[] ses = (IStatElement[]) elems.toArray( new StatElement[0] );
        stats.setStatElements( ses );

        return stats;
    }

    /**
     * Returns the name of the table.
     * <p>
     * @return the table name or UNDEFINED
     */
    protected String getTableName()
    {
        String name = "UNDEFINED";
        if ( this.getJdbcDiskCacheAttributes() != null )
        {
            name = this.getJdbcDiskCacheAttributes().getTableName();
        }
        return name;
    }

    /**
     * @param tableState The tableState to set.
     */
    public void setTableState( TableState tableState )
    {
        this.tableState = tableState;
    }

    /**
     * @return Returns the tableState.
     */
    public TableState getTableState()
    {
        return tableState;
    }

    /**
     * For debugging.
     */
    public String toString()
    {
        return this.getStats();
    }
}
TOP

Related Classes of org.apache.jcs.auxiliary.disk.jdbc.JDBCDiskCache

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.