001package org.apache.commons.jcs.auxiliary.disk.jdbc;
002
003/*
004 * Licensed to the Apache Software Foundation (ASF) under one
005 * or more contributor license agreements.  See the NOTICE file
006 * distributed with this work for additional information
007 * regarding copyright ownership.  The ASF licenses this file
008 * to you under the Apache License, Version 2.0 (the
009 * "License"); you may not use this file except in compliance
010 * with the License.  You may obtain a copy of the License at
011 *
012 *   http://www.apache.org/licenses/LICENSE-2.0
013 *
014 * Unless required by applicable law or agreed to in writing,
015 * software distributed under the License is distributed on an
016 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
017 * KIND, either express or implied.  See the License for the
018 * specific language governing permissions and limitations
019 * under the License.
020 */
021
022import java.io.IOException;
023import java.sql.Connection;
024import java.sql.PreparedStatement;
025import java.sql.ResultSet;
026import java.sql.SQLException;
027import java.sql.Timestamp;
028import java.util.HashMap;
029import java.util.List;
030import java.util.Map;
031import java.util.Set;
032import java.util.concurrent.atomic.AtomicInteger;
033
034import javax.sql.DataSource;
035
036import org.apache.commons.jcs.auxiliary.AuxiliaryCacheAttributes;
037import org.apache.commons.jcs.auxiliary.disk.AbstractDiskCache;
038import org.apache.commons.jcs.auxiliary.disk.jdbc.dsfactory.DataSourceFactory;
039import org.apache.commons.jcs.engine.CacheConstants;
040import org.apache.commons.jcs.engine.behavior.ICacheElement;
041import org.apache.commons.jcs.engine.behavior.ICompositeCacheManager;
042import org.apache.commons.jcs.engine.behavior.IElementSerializer;
043import org.apache.commons.jcs.engine.logging.behavior.ICacheEvent;
044import org.apache.commons.jcs.engine.logging.behavior.ICacheEventLogger;
045import org.apache.commons.jcs.engine.stats.StatElement;
046import org.apache.commons.jcs.engine.stats.behavior.IStatElement;
047import org.apache.commons.jcs.engine.stats.behavior.IStats;
048import org.apache.commons.jcs.utils.serialization.StandardSerializer;
049import org.apache.commons.logging.Log;
050import org.apache.commons.logging.LogFactory;
051
052/**
053 * This is the jdbc disk cache plugin.
054 * <p>
055 * It expects a table created by the following script. The table name is configurable.
056 * <p>
057 *
058 * <pre>
059 *                       drop TABLE JCS_STORE;
060 *                       CREATE TABLE JCS_STORE
061 *                       (
062 *                       CACHE_KEY                  VARCHAR(250)          NOT NULL,
063 *                       REGION                     VARCHAR(250)          NOT NULL,
064 *                       ELEMENT                    BLOB,
065 *                       CREATE_TIME                TIMESTAMP,
066 *                       UPDATE_TIME_SECONDS        BIGINT,
067 *                       MAX_LIFE_SECONDS           BIGINT,
068 *                       SYSTEM_EXPIRE_TIME_SECONDS BIGINT,
069 *                       IS_ETERNAL                 CHAR(1),
070 *                       PRIMARY KEY (CACHE_KEY, REGION)
071 *                       );
072 * </pre>
073 * <p>
074 * The cleanup thread will delete non eternal items where (now - create time) &gt; max life seconds *
075 * 1000
076 * <p>
077 * To speed up the deletion the SYSTEM_EXPIRE_TIME_SECONDS is used instead. It is recommended that
078 * an index be created on this column is you will have over a million records.
079 * <p>
080 * @author Aaron Smuts
081 */
082public class JDBCDiskCache<K, V>
083    extends AbstractDiskCache<K, V>
084{
085    /** The local logger. */
086    private static final Log log = LogFactory.getLog( JDBCDiskCache.class );
087
088    /** custom serialization */
089    private IElementSerializer elementSerializer = new StandardSerializer();
090
091    /** configuration */
092    private JDBCDiskCacheAttributes jdbcDiskCacheAttributes;
093
094    /** # of times update was called */
095    private AtomicInteger updateCount = new AtomicInteger(0);
096
097    /** # of times get was called */
098    private AtomicInteger getCount = new AtomicInteger(0);
099
100    /** # of times getMatching was called */
101    private AtomicInteger getMatchingCount = new AtomicInteger(0);
102
103    /** if count % interval == 0 then log */
104    private static final int LOG_INTERVAL = 100;
105
106    /** db connection pool */
107    private DataSourceFactory dsFactory = null;
108
109    /** tracks optimization */
110    private TableState tableState;
111
112    /**
113     * Constructs a JDBC Disk Cache for the provided cache attributes. The table state object is
114     * used to mark deletions.
115     * <p>
116     * @param cattr the configuration object for this cache
117     * @param dsFactory the DataSourceFactory for this cache
118     * @param tableState an object to track table operations
119     * @param compositeCacheManager the global cache manager
120     */
121    public JDBCDiskCache( JDBCDiskCacheAttributes cattr, DataSourceFactory dsFactory, TableState tableState,
122                          ICompositeCacheManager compositeCacheManager )
123    {
124        super( cattr );
125
126        setTableState( tableState );
127        setJdbcDiskCacheAttributes( cattr );
128
129        if ( log.isInfoEnabled() )
130        {
131            log.info( "jdbcDiskCacheAttributes = " + getJdbcDiskCacheAttributes() );
132        }
133
134        // This initializes the pool access.
135        this.dsFactory = dsFactory;
136
137        // Initialization finished successfully, so set alive to true.
138        setAlive(true);
139    }
140
141    /**
142     * Inserts or updates. By default it will try to insert. If the item exists we will get an
143     * error. It will then update. This behavior is configurable. The cache can be configured to
144     * check before inserting.
145     * <p>
146     * @param ce
147     */
148    @Override
149    protected void processUpdate( ICacheElement<K, V> ce )
150    {
151        updateCount.incrementAndGet();
152
153        if ( log.isDebugEnabled() )
154        {
155            log.debug( "updating, ce = " + ce );
156        }
157
158        Connection con;
159        try
160        {
161            con = getDataSource().getConnection();
162        }
163        catch ( SQLException e )
164        {
165            log.error( "Problem getting connection.", e );
166            return;
167        }
168
169        try
170        {
171            if ( log.isDebugEnabled() )
172            {
173                log.debug( "Putting [" + ce.getKey() + "] on disk." );
174            }
175
176            byte[] element;
177
178            try
179            {
180                element = getElementSerializer().serialize( ce );
181            }
182            catch ( IOException e )
183            {
184                log.error( "Could not serialize element", e );
185                return;
186            }
187
188            insertOrUpdate( ce, con, element );
189        }
190        finally
191        {
192            try
193            {
194                con.close();
195            }
196            catch ( SQLException e )
197            {
198                log.error( "Problem closing connection.", e );
199            }
200        }
201
202        if ( log.isInfoEnabled() )
203        {
204            if ( updateCount.get() % LOG_INTERVAL == 0 )
205            {
206                // TODO make a log stats method
207                log.info( "Update Count [" + updateCount + "]" );
208            }
209        }
210    }
211
212    /**
213     * If test before insert it true, we check to see if the element exists. If the element exists
214     * we will update. Otherwise, we try inserting.  If this fails because the item exists, we will
215     * update.
216     * <p>
217     * @param ce
218     * @param con
219     * @param element
220     */
221    private void insertOrUpdate( ICacheElement<K, V> ce, Connection con, byte[] element )
222    {
223        boolean exists = false;
224
225        // First do a query to determine if the element already exists
226        if ( this.getJdbcDiskCacheAttributes().isTestBeforeInsert() )
227        {
228            exists = doesElementExist( ce, con );
229        }
230
231        // If it doesn't exist, insert it, otherwise update
232        if ( !exists )
233        {
234            exists = insertRow( ce, con, element );
235        }
236
237        // update if it exists.
238        if ( exists )
239        {
240            updateRow( ce, con, element );
241        }
242    }
243
244    /**
245     * This inserts a new row in the database.
246     * <p>
247     * @param ce
248     * @param con
249     * @param element
250     * @return true if the insertion fails because the record exists.
251     */
252    private boolean insertRow( ICacheElement<K, V> ce, Connection con, byte[] element )
253    {
254        boolean exists = false;
255        PreparedStatement psInsert = null;
256
257        try
258        {
259            String sqlI = "insert into "
260                + getJdbcDiskCacheAttributes().getTableName()
261                + " (CACHE_KEY, REGION, ELEMENT, MAX_LIFE_SECONDS, IS_ETERNAL, CREATE_TIME, UPDATE_TIME_SECONDS, SYSTEM_EXPIRE_TIME_SECONDS) "
262                + " values (?, ?, ?, ?, ?, ?, ?, ?)";
263
264            psInsert = con.prepareStatement( sqlI );
265            psInsert.setString( 1, (String) ce.getKey() );
266            psInsert.setString( 2, this.getCacheName() );
267            psInsert.setBytes( 3, element );
268            psInsert.setLong( 4, ce.getElementAttributes().getMaxLife() );
269            if ( ce.getElementAttributes().getIsEternal() )
270            {
271                psInsert.setString( 5, "T" );
272            }
273            else
274            {
275                psInsert.setString( 5, "F" );
276            }
277            Timestamp createTime = new Timestamp( ce.getElementAttributes().getCreateTime() );
278            psInsert.setTimestamp( 6, createTime );
279
280            long now = System.currentTimeMillis() / 1000;
281            psInsert.setLong( 7, now );
282
283            long expireTime = now + ce.getElementAttributes().getMaxLife();
284            psInsert.setLong( 8, expireTime );
285
286            psInsert.execute();
287        }
288        catch ( SQLException e )
289        {
290            if ("23000".equals(e.getSQLState()))
291            {
292                exists = true;
293            }
294            else
295            {
296                log.error( "Could not insert element", e );
297            }
298
299            // see if it exists, if we didn't already
300            if ( !exists && !this.getJdbcDiskCacheAttributes().isTestBeforeInsert() )
301            {
302                exists = doesElementExist( ce, con );
303            }
304        }
305        finally
306        {
307            if (psInsert != null)
308            {
309                try
310                {
311                    psInsert.close();
312                }
313                catch (SQLException e)
314                {
315                    log.error( "Problem closing statement.", e );
316                }
317            }
318        }
319
320        return exists;
321    }
322
323    /**
324     * This updates a row in the database.
325     * <p>
326     * @param ce
327     * @param con
328     * @param element
329     */
330    private void updateRow( ICacheElement<K, V> ce, Connection con, byte[] element )
331    {
332        String sqlU = null;
333        PreparedStatement psUpdate = null;
334
335        try
336        {
337            sqlU = "update " + getJdbcDiskCacheAttributes().getTableName()
338                + " set ELEMENT  = ?, CREATE_TIME = ?, UPDATE_TIME_SECONDS = ?, " + " SYSTEM_EXPIRE_TIME_SECONDS = ? "
339                + " where CACHE_KEY = ? and REGION = ?";
340            psUpdate = con.prepareStatement( sqlU );
341            psUpdate.setBytes( 1, element );
342
343            Timestamp createTime = new Timestamp( ce.getElementAttributes().getCreateTime() );
344            psUpdate.setTimestamp( 2, createTime );
345
346            long now = System.currentTimeMillis() / 1000;
347            psUpdate.setLong( 3, now );
348
349            long expireTime = now + ce.getElementAttributes().getMaxLife();
350            psUpdate.setLong( 4, expireTime );
351
352            psUpdate.setString( 5, (String) ce.getKey() );
353            psUpdate.setString( 6, this.getCacheName() );
354            psUpdate.execute();
355
356            if ( log.isDebugEnabled() )
357            {
358                log.debug( "ran update " + sqlU );
359            }
360        }
361        catch ( SQLException e2 )
362        {
363            log.error( "e2 sql [" + sqlU + "] Exception: ", e2 );
364        }
365        finally
366        {
367            if (psUpdate != null)
368            {
369                try
370                {
371                    psUpdate.close();
372                }
373                catch (SQLException e)
374                {
375                    log.error( "Problem closing statement.", e );
376                }
377            }
378        }
379    }
380
381    /**
382     * Does an element exist for this key?
383     * <p>
384     * @param ce the cache element
385     * @param con a database connection
386     * @return boolean
387     */
388    protected boolean doesElementExist( ICacheElement<K, V> ce, Connection con )
389    {
390        boolean exists = false;
391        PreparedStatement psSelect = null;
392        ResultSet rs = null;
393
394        try
395        {
396            // don't select the element, since we want this to be fast.
397            String sqlS = "select CACHE_KEY from " + getJdbcDiskCacheAttributes().getTableName()
398                + " where REGION = ? and CACHE_KEY = ?";
399
400            psSelect = con.prepareStatement( sqlS );
401            psSelect.setString( 1, this.getCacheName() );
402            psSelect.setString( 2, (String) ce.getKey() );
403
404            rs = psSelect.executeQuery();
405
406            if ( rs.next() )
407            {
408                exists = true;
409            }
410
411            if ( log.isDebugEnabled() )
412            {
413                log.debug( "[" + ce.getKey() + "] existing status is " + exists );
414            }
415        }
416        catch ( SQLException e )
417        {
418            log.error( "Problem looking for item before insert.", e );
419        }
420        finally
421        {
422            try
423            {
424                if ( rs != null )
425                {
426                    rs.close();
427                }
428            }
429            catch ( SQLException e )
430            {
431                log.error( "Problem closing result set.", e );
432            }
433            try
434            {
435                if ( psSelect != null )
436                {
437                    psSelect.close();
438                }
439            }
440            catch ( SQLException e )
441            {
442                log.error( "Problem closing statement.", e );
443            }
444        }
445
446        return exists;
447    }
448
449    /**
450     * Queries the database for the value. If it gets a result, the value is deserialized.
451     * <p>
452     * @param key
453     * @return ICacheElement
454     * @see org.apache.commons.jcs.auxiliary.disk.AbstractDiskCache#get(Object)
455     */
456    @Override
457    protected ICacheElement<K, V> processGet( K key )
458    {
459        getCount.incrementAndGet();
460
461        if ( log.isDebugEnabled() )
462        {
463            log.debug( "Getting [" + key + "] from disk" );
464        }
465
466        if ( !isAlive() )
467        {
468            return null;
469        }
470
471        ICacheElement<K, V> obj = null;
472
473        byte[] data = null;
474        try
475        {
476            // region, key
477            String selectString = "select ELEMENT from " + getJdbcDiskCacheAttributes().getTableName()
478                + " where REGION = ? and CACHE_KEY = ?";
479
480            Connection con = getDataSource().getConnection();
481            try
482            {
483                PreparedStatement psSelect = null;
484
485                try
486                {
487                    psSelect = con.prepareStatement( selectString );
488                    psSelect.setString( 1, this.getCacheName() );
489                    psSelect.setString( 2, key.toString() );
490
491                    ResultSet rs = psSelect.executeQuery();
492
493                    try
494                    {
495                        if ( rs.next() )
496                        {
497                            data = rs.getBytes( 1 );
498                        }
499                        if ( data != null )
500                        {
501                            try
502                            {
503                                // USE THE SERIALIZER
504                                obj = getElementSerializer().deSerialize( data, null );
505                            }
506                            catch ( IOException ioe )
507                            {
508                                log.error( "Problem getting item for key [" + key + "]", ioe );
509                            }
510                            catch ( Exception e )
511                            {
512                                log.error( "Problem getting item for key [" + key + "]", e );
513                            }
514                        }
515                    }
516                    finally
517                    {
518                        if ( rs != null )
519                        {
520                            rs.close();
521                        }
522                    }
523                }
524                finally
525                {
526                    if ( psSelect != null )
527                    {
528                        psSelect.close();
529                    }
530                }
531            }
532            finally
533            {
534                if ( con != null )
535                {
536                    con.close();
537                }
538            }
539        }
540        catch ( SQLException sqle )
541        {
542            log.error( "Caught a SQL exception trying to get the item for key [" + key + "]", sqle );
543        }
544
545        if ( log.isInfoEnabled() )
546        {
547            if ( getCount.get() % LOG_INTERVAL == 0 )
548            {
549                // TODO make a log stats method
550                log.info( "Get Count [" + getCount + "]" );
551            }
552        }
553        return obj;
554    }
555
556    /**
557     * This will run a like query. It will try to construct a usable query but different
558     * implementations will be needed to adjust the syntax.
559     * <p>
560     * @param pattern
561     * @return key,value map
562     */
563    @Override
564    protected Map<K, ICacheElement<K, V>> processGetMatching( String pattern )
565    {
566        getMatchingCount.incrementAndGet();
567
568        if ( log.isDebugEnabled() )
569        {
570            log.debug( "Getting [" + pattern + "] from disk" );
571        }
572
573        if ( !isAlive() )
574        {
575            return null;
576        }
577
578        Map<K, ICacheElement<K, V>> results = new HashMap<K, ICacheElement<K, V>>();
579
580        try
581        {
582            // region, key
583            String selectString = "select CACHE_KEY, ELEMENT from " + getJdbcDiskCacheAttributes().getTableName()
584                + " where REGION = ? and CACHE_KEY like ?";
585
586            Connection con = getDataSource().getConnection();
587            try
588            {
589                PreparedStatement psSelect = null;
590                try
591                {
592                    psSelect = con.prepareStatement( selectString );
593                    psSelect.setString( 1, this.getCacheName() );
594                    psSelect.setString( 2, constructLikeParameterFromPattern( pattern ) );
595
596                    ResultSet rs = psSelect.executeQuery();
597                    try
598                    {
599                        while ( rs.next() )
600                        {
601                            String key = rs.getString( 1 );
602                            byte[] data = rs.getBytes( 2 );
603                            if ( data != null )
604                            {
605                                try
606                                {
607                                    // USE THE SERIALIZER
608                                    ICacheElement<K, V> value = getElementSerializer().deSerialize( data, null );
609                                    results.put( (K) key, value );
610                                }
611                                catch ( IOException ioe )
612                                {
613                                    log.error( "Problem getting items for pattern [" + pattern + "]", ioe );
614                                }
615                                catch ( Exception e )
616                                {
617                                    log.error( "Problem getting items for pattern [" + pattern + "]", e );
618                                }
619                            }
620                        }
621                    }
622                    finally
623                    {
624                        if ( rs != null )
625                        {
626                            rs.close();
627                        }
628                    }
629                }
630                finally
631                {
632                    if ( psSelect != null )
633                    {
634                        psSelect.close();
635                    }
636                }
637            }
638            finally
639            {
640                if ( con != null )
641                {
642                    con.close();
643                }
644            }
645        }
646        catch ( SQLException sqle )
647        {
648            log.error( "Caught a SQL exception trying to get items for pattern [" + pattern + "]", sqle );
649        }
650
651        if ( log.isInfoEnabled() )
652        {
653            if ( getMatchingCount.get() % LOG_INTERVAL == 0 )
654            {
655                // TODO make a log stats method
656                log.info( "Get Matching Count [" + getMatchingCount + "]" );
657            }
658        }
659        return results;
660    }
661
662    /**
663     * @param pattern
664     * @return String to use in the like query.
665     */
666    public String constructLikeParameterFromPattern( String pattern )
667    {
668        String likePattern = pattern.replaceAll( "\\.\\+", "%" );
669        likePattern = likePattern.replaceAll( "\\.", "_" );
670
671        if ( log.isDebugEnabled() )
672        {
673            log.debug( "pattern = [" + likePattern + "]" );
674        }
675
676        return likePattern;
677    }
678
679    /**
680     * Returns true if the removal was successful; or false if there is nothing to remove. Current
681     * implementation always results in a disk orphan.
682     * <p>
683     * @param key
684     * @return boolean
685     */
686    @Override
687    protected boolean processRemove( K key )
688    {
689        // remove single item.
690        String sql = "delete from " + getJdbcDiskCacheAttributes().getTableName()
691            + " where REGION = ? and CACHE_KEY = ?";
692
693        try
694        {
695            boolean partial = false;
696            if ( key instanceof String && key.toString().endsWith( CacheConstants.NAME_COMPONENT_DELIMITER ) )
697            {
698                // remove all keys of the same name group.
699                sql = "delete from " + getJdbcDiskCacheAttributes().getTableName()
700                    + " where REGION = ? and CACHE_KEY like ?";
701                partial = true;
702            }
703            Connection con = getDataSource().getConnection();
704            PreparedStatement psSelect = null;
705            try
706            {
707                psSelect = con.prepareStatement( sql );
708                psSelect.setString( 1, this.getCacheName() );
709                if ( partial )
710                {
711                    psSelect.setString( 2, key.toString() + "%" );
712                }
713                else
714                {
715                    psSelect.setString( 2, key.toString() );
716                }
717
718                psSelect.executeUpdate();
719
720                setAlive(true);
721            }
722            catch ( SQLException e )
723            {
724                log.error( "Problem creating statement. sql [" + sql + "]", e );
725                setAlive(false);
726            }
727            finally
728            {
729                try
730                {
731                    if ( psSelect != null )
732                    {
733                        psSelect.close();
734                    }
735                    con.close();
736                }
737                catch ( SQLException e1 )
738                {
739                    log.error( "Problem closing statement.", e1 );
740                }
741            }
742        }
743        catch ( SQLException e )
744        {
745            log.error( "Problem updating cache.", e );
746            reset();
747        }
748        return false;
749    }
750
751    /**
752     * This should remove all elements. The auxiliary can be configured to forbid this behavior. If
753     * remove all is not allowed, the method balks.
754     */
755    @Override
756    protected void processRemoveAll()
757    {
758        // it should never get here from the abstract disk cache.
759        if ( this.jdbcDiskCacheAttributes.isAllowRemoveAll() )
760        {
761            try
762            {
763                String sql = "delete from " + getJdbcDiskCacheAttributes().getTableName() + " where REGION = ?";
764                Connection con = getDataSource().getConnection();
765                PreparedStatement psDelete = null;
766                try
767                {
768                    psDelete = con.prepareStatement( sql );
769                    psDelete.setString( 1, this.getCacheName() );
770                    setAlive(true);
771                    psDelete.executeUpdate();
772                }
773                catch ( SQLException e )
774                {
775                    log.error( "Problem creating statement.", e );
776                    setAlive(false);
777                }
778                finally
779                {
780                    try
781                    {
782                        if ( psDelete != null )
783                        {
784                            psDelete.close();
785                        }
786                        con.close();
787                    }
788                    catch ( SQLException e1 )
789                    {
790                        log.error( "Problem closing statement.", e1 );
791                    }
792                }
793            }
794            catch ( Exception e )
795            {
796                log.error( "Problem removing all.", e );
797                reset();
798            }
799        }
800        else
801        {
802            if ( log.isInfoEnabled() )
803            {
804                log.info( "RemoveAll was requested but the request was not fulfilled: allowRemoveAll is set to false." );
805            }
806        }
807    }
808
809    /**
810     * Removed the expired. (now - create time) &gt; max life seconds * 1000
811     * <p>
812     * @return the number deleted
813     */
814    protected int deleteExpired()
815    {
816        int deleted = 0;
817
818        try
819        {
820            getTableState().setState( TableState.DELETE_RUNNING );
821
822            long now = System.currentTimeMillis() / 1000;
823
824            // This is to slow when we push over a million records
825            // String sql = "delete from " +
826            // getJdbcDiskCacheAttributes().getTableName() + " where REGION = '"
827            // + this.getCacheName() + "' and IS_ETERNAL = 'F' and (" + now
828            // + " - UPDATE_TIME_SECONDS) > MAX_LIFE_SECONDS";
829
830            String sql = "delete from " + getJdbcDiskCacheAttributes().getTableName()
831                + " where IS_ETERNAL = ? and REGION = ? and ? > SYSTEM_EXPIRE_TIME_SECONDS";
832
833            Connection con = getDataSource().getConnection();
834            PreparedStatement psDelete = null;
835            try
836            {
837                psDelete = con.prepareStatement( sql );
838                psDelete.setString( 1, "F" );
839                psDelete.setString( 2, this.getCacheName() );
840                psDelete.setLong( 3, now );
841
842                setAlive(true);
843
844                deleted = psDelete.executeUpdate();
845            }
846            catch ( SQLException e )
847            {
848                log.error( "Problem creating statement.", e );
849                setAlive(false);
850            }
851            finally
852            {
853                try
854                {
855                    if ( psDelete != null )
856                    {
857                        psDelete.close();
858                    }
859                    con.close();
860                }
861                catch ( SQLException e1 )
862                {
863                    log.error( "Problem closing statement.", e1 );
864                }
865            }
866            logApplicationEvent( getAuxiliaryCacheAttributes().getName(), "deleteExpired",
867                                 "Deleted expired elements.  URL: " + getDiskLocation() );
868        }
869        catch ( Exception e )
870        {
871            logError( getAuxiliaryCacheAttributes().getName(), "deleteExpired", e.getMessage() + " URL: "
872                + getDiskLocation() );
873            log.error( "Problem removing expired elements from the table.", e );
874            reset();
875        }
876        finally
877        {
878            getTableState().setState( TableState.FREE );
879        }
880
881        return deleted;
882    }
883
884    /**
885     * Typically this is used to handle errors by last resort, force content update, or removeall
886     */
887    public void reset()
888    {
889        // nothing
890    }
891
892    /** Shuts down the pool */
893    @Override
894    public void processDispose()
895    {
896        ICacheEvent<K> cacheEvent = createICacheEvent( getCacheName(), (K)"none", ICacheEventLogger.DISPOSE_EVENT );
897        try
898        {
899            try
900            {
901                dsFactory.close();
902            }
903            catch ( SQLException e )
904            {
905                log.error( "Problem shutting down.", e );
906            }
907        }
908        finally
909        {
910            logICacheEvent( cacheEvent );
911        }
912    }
913
914    /**
915     * Returns the current cache size. Just does a count(*) for the region.
916     * <p>
917     * @return The size value
918     */
919    @Override
920    public int getSize()
921    {
922        int size = 0;
923
924        // region, key
925        String selectString = "select count(*) from " + getJdbcDiskCacheAttributes().getTableName()
926            + " where REGION = ?";
927
928        Connection con;
929        try
930        {
931            con = getDataSource().getConnection();
932        }
933        catch ( SQLException e )
934        {
935            log.error( "Problem getting connection.", e );
936            return size;
937        }
938
939        try
940        {
941            PreparedStatement psSelect = null;
942            try
943            {
944                psSelect = con.prepareStatement( selectString );
945                psSelect.setString( 1, this.getCacheName() );
946                ResultSet rs = null;
947
948                rs = psSelect.executeQuery();
949                try
950                {
951                    if ( rs.next() )
952                    {
953                        size = rs.getInt( 1 );
954                    }
955                }
956                finally
957                {
958                    if ( rs != null )
959                    {
960                        rs.close();
961                    }
962                }
963            }
964            finally
965            {
966                if ( psSelect != null )
967                {
968                    psSelect.close();
969                }
970            }
971        }
972        catch ( SQLException e )
973        {
974            log.error( "Problem getting size.", e );
975        }
976        finally
977        {
978            try
979            {
980                con.close();
981            }
982            catch ( SQLException e )
983            {
984                log.error( "Problem closing connection.", e );
985            }
986        }
987        return size;
988    }
989
990    /**
991     * Return the keys in this cache.
992     * <p>
993     * @see org.apache.commons.jcs.auxiliary.disk.AbstractDiskCache#getKeySet()
994     */
995    @Override
996    public Set<K> getKeySet() throws IOException
997    {
998        throw new UnsupportedOperationException( "Groups not implemented." );
999        // return null;
1000    }
1001
1002    /**
1003     * @param elementSerializer The elementSerializer to set.
1004     */
1005    @Override
1006    public void setElementSerializer( IElementSerializer elementSerializer )
1007    {
1008        this.elementSerializer = elementSerializer;
1009    }
1010
1011    /**
1012     * @return Returns the elementSerializer.
1013     */
1014    @Override
1015    public IElementSerializer getElementSerializer()
1016    {
1017        return elementSerializer;
1018    }
1019
1020    /**
1021     * @param jdbcDiskCacheAttributes The jdbcDiskCacheAttributes to set.
1022     */
1023    protected void setJdbcDiskCacheAttributes( JDBCDiskCacheAttributes jdbcDiskCacheAttributes )
1024    {
1025        this.jdbcDiskCacheAttributes = jdbcDiskCacheAttributes;
1026    }
1027
1028    /**
1029     * @return Returns the jdbcDiskCacheAttributes.
1030     */
1031    protected JDBCDiskCacheAttributes getJdbcDiskCacheAttributes()
1032    {
1033        return jdbcDiskCacheAttributes;
1034    }
1035
1036    /**
1037     * @return Returns the AuxiliaryCacheAttributes.
1038     */
1039    @Override
1040    public AuxiliaryCacheAttributes getAuxiliaryCacheAttributes()
1041    {
1042        return this.getJdbcDiskCacheAttributes();
1043    }
1044
1045    /**
1046     * Extends the parent stats.
1047     * <p>
1048     * @return IStats
1049     */
1050    @Override
1051    public IStats getStatistics()
1052    {
1053        IStats stats = super.getStatistics();
1054        stats.setTypeName( "JDBC/Abstract Disk Cache" );
1055
1056        List<IStatElement<?>> elems = stats.getStatElements();
1057
1058        elems.add(new StatElement<AtomicInteger>( "Update Count", updateCount ) );
1059        elems.add(new StatElement<AtomicInteger>( "Get Count", getCount ) );
1060        elems.add(new StatElement<AtomicInteger>( "Get Matching Count", getMatchingCount ) );
1061        elems.add(new StatElement<String>( "DB URL", getJdbcDiskCacheAttributes().getUrl()) );
1062
1063        stats.setStatElements( elems );
1064
1065        return stats;
1066    }
1067
1068    /**
1069     * Returns the name of the table.
1070     * <p>
1071     * @return the table name or UNDEFINED
1072     */
1073    protected String getTableName()
1074    {
1075        String name = "UNDEFINED";
1076        if ( this.getJdbcDiskCacheAttributes() != null )
1077        {
1078            name = this.getJdbcDiskCacheAttributes().getTableName();
1079        }
1080        return name;
1081    }
1082
1083    /**
1084     * @param tableState The tableState to set.
1085     */
1086    public void setTableState( TableState tableState )
1087    {
1088        this.tableState = tableState;
1089    }
1090
1091    /**
1092     * @return Returns the tableState.
1093     */
1094    public TableState getTableState()
1095    {
1096        return tableState;
1097    }
1098
1099    /**
1100     * This is used by the event logging.
1101     * <p>
1102     * @return the location of the disk, either path or ip.
1103     */
1104    @Override
1105    protected String getDiskLocation()
1106    {
1107        return this.jdbcDiskCacheAttributes.getUrl();
1108    }
1109
1110    /**
1111     * Public so managers can access it.
1112     * @return the dsFactory
1113     * @throws SQLException if getting a data source fails
1114     */
1115    public DataSource getDataSource() throws SQLException
1116    {
1117        return dsFactory.getDataSource();
1118    }
1119
1120    /**
1121     * For debugging.
1122     * <p>
1123     * @return this.getStats();
1124     */
1125    @Override
1126    public String toString()
1127    {
1128        return this.getStats();
1129    }
1130}