View Javadoc

1   package org.apache.jcs.auxiliary.disk.jdbc;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *   http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import java.io.IOException;
23  import java.io.Serializable;
24  import java.sql.Connection;
25  import java.sql.PreparedStatement;
26  import java.sql.ResultSet;
27  import java.sql.SQLException;
28  import java.sql.Statement;
29  import java.sql.Timestamp;
30  import java.util.ArrayList;
31  import java.util.Arrays;
32  import java.util.HashMap;
33  import java.util.List;
34  import java.util.Map;
35  import java.util.Set;
36  
37  import org.apache.commons.logging.Log;
38  import org.apache.commons.logging.LogFactory;
39  import org.apache.jcs.auxiliary.AuxiliaryCacheAttributes;
40  import org.apache.jcs.auxiliary.disk.AbstractDiskCache;
41  import org.apache.jcs.engine.CacheConstants;
42  import org.apache.jcs.engine.behavior.ICacheElement;
43  import org.apache.jcs.engine.behavior.ICompositeCacheManager;
44  import org.apache.jcs.engine.behavior.IElementSerializer;
45  import org.apache.jcs.engine.logging.behavior.ICacheEvent;
46  import org.apache.jcs.engine.logging.behavior.ICacheEventLogger;
47  import org.apache.jcs.engine.stats.StatElement;
48  import org.apache.jcs.engine.stats.behavior.IStatElement;
49  import org.apache.jcs.engine.stats.behavior.IStats;
50  import org.apache.jcs.utils.serialization.StandardSerializer;
51  
52  /**
53   * This is the jdbc disk cache plugin.
54   * <p>
55   * It expects a table created by the following script. The table name is configurable.
56   * <p>
57   *
58   * <pre>
59   *                       drop TABLE JCS_STORE;
60   *                       CREATE TABLE JCS_STORE
61   *                       (
62   *                       CACHE_KEY                  VARCHAR(250)          NOT NULL,
63   *                       REGION                     VARCHAR(250)          NOT NULL,
64   *                       ELEMENT                    BLOB,
65   *                       CREATE_TIME                TIMESTAMP,
66   *                       UPDATE_TIME_SECONDS        BIGINT,
67   *                       MAX_LIFE_SECONDS           BIGINT,
68   *                       SYSTEM_EXPIRE_TIME_SECONDS BIGINT,
69   *                       IS_ETERNAL                 CHAR(1),
70   *                       PRIMARY KEY (CACHE_KEY, REGION)
71   *                       );
72   * </pre>
73   * <p>
74   * The cleanup thread will delete non eternal items where (now - create time) > max life seconds *
75   * 1000
76   * <p>
77   * To speed up the deletion the SYSTEM_EXPIRE_TIME_SECONDS is used instead. It is recommended that
78   * an index be created on this column is you will have over a million records.
79   * <p>
80   * @author Aaron Smuts
81   */
82  public class JDBCDiskCache<K extends Serializable, V extends Serializable>
83      extends AbstractDiskCache<K, V>
84  {
85      /** The local logger. */
86      private final static Log log = LogFactory.getLog( JDBCDiskCache.class );
87  
88      /** Don't change. */
89      private static final long serialVersionUID = -7169488308515823492L;
90  
91      /** custom serialization */
92      private IElementSerializer elementSerializer = new StandardSerializer();
93  
94      /** configuration */
95      private JDBCDiskCacheAttributes jdbcDiskCacheAttributes;
96  
97      /** # of times update was called */
98      private int updateCount = 0;
99  
100     /** # of times get was called */
101     private int getCount = 0;
102 
103     /** # of times getMatching was called */
104     private int getMatchingCount = 0;
105 
106     /** if count % interval == 0 then log */
107     private static final int LOG_INTERVAL = 100;
108 
109     /** db connection pool */
110     private JDBCDiskCachePoolAccess poolAccess = null;
111 
112     /** tracks optimization */
113     private TableState tableState;
114 
115     /**
116      * Constructs a JDBC Disk Cache for the provided cache attributes. The table state object is
117      * used to mark deletions.
118      * <p>
119      * @param cattr
120      * @param tableState
121      * @param compositeCacheManager
122      */
123     public JDBCDiskCache( JDBCDiskCacheAttributes cattr, TableState tableState,
124                           ICompositeCacheManager compositeCacheManager )
125     {
126         super( cattr );
127 
128         setTableState( tableState );
129 
130         setJdbcDiskCacheAttributes( cattr );
131 
132         if ( log.isInfoEnabled() )
133         {
134             log.info( "jdbcDiskCacheAttributes = " + getJdbcDiskCacheAttributes() );
135         }
136 
137         // This initializes the pool access.
138         setPoolAccess( initializePoolAccess( cattr, compositeCacheManager ) );
139 
140         // Initialization finished successfully, so set alive to true.
141         alive = true;
142     }
143 
144     /**
145      * Registers the driver and creates a poolAccess class.
146      * <p>
147      * @param cattr
148      * @param compositeCacheManager
149      * @return JDBCDiskCachePoolAccess for testing
150      */
151     protected JDBCDiskCachePoolAccess initializePoolAccess( JDBCDiskCacheAttributes cattr,
152                                                             ICompositeCacheManager compositeCacheManager )
153     {
154         JDBCDiskCachePoolAccess poolAccess = null;
155         if ( cattr.getConnectionPoolName() != null )
156         {
157             JDBCDiskCachePoolAccessManager manager = JDBCDiskCachePoolAccessManager.getInstance( compositeCacheManager
158                 .getConfigurationProperties() );
159             poolAccess = manager.getJDBCDiskCachePoolAccess( cattr.getConnectionPoolName() );
160         }
161         else
162         {
163             try
164             {
165                 poolAccess = JDBCDiskCachePoolAccessFactory.createPoolAccess( cattr );
166             }
167             catch ( Exception e )
168             {
169                 logError( getAuxiliaryCacheAttributes().getName(), "initializePoolAccess", e.getMessage() + " URL: "
170                     + getDiskLocation() );
171                 log.error( "Problem getting connection.", e );
172             }
173         }
174         return poolAccess;
175     }
176 
177     /**
178      * Inserts or updates. By default it will try to insert. If the item exists we will get an
179      * error. It will then update. This behavior is configurable. The cache can be configured to
180      * check before inserting.
181      * <p>
182      * @param ce
183      */
184     @Override
185     protected void processUpdate( ICacheElement<K, V> ce )
186     {
187         incrementUpdateCount();
188 
189         if ( log.isDebugEnabled() )
190         {
191             log.debug( "updating, ce = " + ce );
192         }
193 
194         Connection con;
195         try
196         {
197             con = getPoolAccess().getConnection();
198         }
199         catch ( SQLException e )
200         {
201             log.error( "Problem getting connection.", e );
202             return;
203         }
204 
205         try
206         {
207             // TEST
208             Statement sStatement = null;
209             try
210             {
211                 sStatement = con.createStatement();
212                 alive = true;
213             }
214             catch ( SQLException e )
215             {
216                 log.error( "Problem creating statement.", e );
217                 alive = false;
218             }
219             finally
220             {
221                 try
222                 {
223                     if (sStatement != null)
224                     {
225                         sStatement.close();
226                     }
227                 }
228                 catch ( SQLException e )
229                 {
230                     log.error( "Problem closing statement.", e );
231                 }
232             }
233 
234             if ( !alive )
235             {
236                 if ( log.isInfoEnabled() )
237                 {
238                     log.info( "Disk is not alive, aborting put." );
239                 }
240                 return;
241             }
242 
243             if ( log.isDebugEnabled() )
244             {
245                 log.debug( "Putting [" + ce.getKey() + "] on disk." );
246             }
247 
248             byte[] element;
249 
250             try
251             {
252                 element = getElementSerializer().serialize( ce );
253             }
254             catch ( IOException e )
255             {
256                 log.error( "Could not serialize element", e );
257                 return;
258             }
259 
260             insertOrUpdate( ce, con, element );
261         }
262         finally
263         {
264             try
265             {
266                 con.close();
267             }
268             catch ( SQLException e )
269             {
270                 log.error( "Problem closing connection.", e );
271             }
272         }
273 
274         if ( log.isInfoEnabled() )
275         {
276             if ( updateCount % LOG_INTERVAL == 0 )
277             {
278                 // TODO make a log stats method
279                 log.info( "Update Count [" + updateCount + "]" );
280             }
281         }
282     }
283 
284     /**
285      * If test before insert it true, we check to see if the element exists. If the element exists
286      * we will update. Otherwise, we try inserting.  If this fails because the item exists, we will
287      * update.
288      * <p>
289      * @param ce
290      * @param con
291      * @param element
292      */
293     private void insertOrUpdate( ICacheElement<K, V> ce, Connection con, byte[] element )
294     {
295         boolean exists = false;
296 
297         // First do a query to determine if the element already exists
298         if ( this.getJdbcDiskCacheAttributes().isTestBeforeInsert() )
299         {
300             exists = doesElementExist( ce );
301         }
302 
303         // If it doesn't exist, insert it, otherwise update
304         if ( !exists )
305         {
306             exists = insertRow( ce, con, element );
307         }
308 
309         // update if it exists.
310         if ( exists )
311         {
312             updateRow( ce, con, element );
313         }
314     }
315 
316     /**
317      * This inserts a new row in the database.
318      * <p>
319      * @param ce
320      * @param con
321      * @param element
322      * @return true if the insertion fails because the record exists.
323      */
324     private boolean insertRow( ICacheElement<K, V> ce, Connection con, byte[] element )
325     {
326         boolean exists = false;
327         try
328         {
329             String sqlI = "insert into "
330                 + getJdbcDiskCacheAttributes().getTableName()
331                 + " (CACHE_KEY, REGION, ELEMENT, MAX_LIFE_SECONDS, IS_ETERNAL, CREATE_TIME, UPDATE_TIME_SECONDS, SYSTEM_EXPIRE_TIME_SECONDS) "
332                 + " values (?, ?, ?, ?, ?, ?, ?, ?)";
333 
334             PreparedStatement psInsert = con.prepareStatement( sqlI );
335             psInsert.setString( 1, (String) ce.getKey() );
336             psInsert.setString( 2, this.getCacheName() );
337             psInsert.setBytes( 3, element );
338             psInsert.setLong( 4, ce.getElementAttributes().getMaxLifeSeconds() );
339             if ( ce.getElementAttributes().getIsEternal() )
340             {
341                 psInsert.setString( 5, "T" );
342             }
343             else
344             {
345                 psInsert.setString( 5, "F" );
346             }
347             Timestamp createTime = new Timestamp( ce.getElementAttributes().getCreateTime() );
348             psInsert.setTimestamp( 6, createTime );
349 
350             long now = System.currentTimeMillis() / 1000;
351             psInsert.setLong( 7, now );
352 
353             long expireTime = now + ce.getElementAttributes().getMaxLifeSeconds();
354             psInsert.setLong( 8, expireTime );
355 
356             psInsert.execute();
357             psInsert.close();
358         }
359         catch ( SQLException e )
360         {
361             if ( e.toString().indexOf( "Violation of unique index" ) != -1
362                 || e.getMessage().indexOf( "Duplicate entry" ) != -1
363                 || e.getMessage().indexOf( "duplicate key" ) != -1
364                 || e.getMessage().indexOf( "primary key constraint" ) != -1 )
365             {
366                 exists = true;
367             }
368             else
369             {
370                 log.error( "Could not insert element", e );
371             }
372 
373             // see if it exists, if we didn't already
374             if ( !exists && !this.getJdbcDiskCacheAttributes().isTestBeforeInsert() )
375             {
376                 exists = doesElementExist( ce );
377             }
378         }
379         return exists;
380     }
381 
382     /**
383      * This updates a row in the database.
384      * <p>
385      * @param ce
386      * @param con
387      * @param element
388      */
389     private void updateRow( ICacheElement<K, V> ce, Connection con, byte[] element )
390     {
391         String sqlU = null;
392         try
393         {
394             sqlU = "update " + getJdbcDiskCacheAttributes().getTableName()
395                 + " set ELEMENT  = ?, CREATE_TIME = ?, UPDATE_TIME_SECONDS = ?, " + " SYSTEM_EXPIRE_TIME_SECONDS = ? "
396                 + " where CACHE_KEY = ? and REGION = ?";
397             PreparedStatement psUpdate = con.prepareStatement( sqlU );
398             psUpdate.setBytes( 1, element );
399 
400             Timestamp createTime = new Timestamp( ce.getElementAttributes().getCreateTime() );
401             psUpdate.setTimestamp( 2, createTime );
402 
403             long now = System.currentTimeMillis() / 1000;
404             psUpdate.setLong( 3, now );
405 
406             long expireTime = now + ce.getElementAttributes().getMaxLifeSeconds();
407             psUpdate.setLong( 4, expireTime );
408 
409             psUpdate.setString( 5, (String) ce.getKey() );
410             psUpdate.setString( 6, this.getCacheName() );
411             psUpdate.execute();
412             psUpdate.close();
413 
414             if ( log.isDebugEnabled() )
415             {
416                 log.debug( "ran update " + sqlU );
417             }
418         }
419         catch ( SQLException e2 )
420         {
421             log.error( "e2 sql [" + sqlU + "] Exception: ", e2 );
422         }
423     }
424 
425     /**
426      * Does an element exist for this key?
427      * <p>
428      * @param ce
429      * @return boolean
430      */
431     protected boolean doesElementExist( ICacheElement<K, V> ce )
432     {
433         boolean exists = false;
434 
435         Connection con;
436         try
437         {
438             con = getPoolAccess().getConnection();
439         }
440         catch ( SQLException e )
441         {
442             log.error( "Problem getting connection.", e );
443             return exists;
444         }
445 
446         PreparedStatement psSelect = null;
447         try
448         {
449             // don't select the element, since we want this to be fast.
450             String sqlS = "select CACHE_KEY from " + getJdbcDiskCacheAttributes().getTableName()
451                 + " where REGION = ? and CACHE_KEY = ?";
452 
453             psSelect = con.prepareStatement( sqlS );
454             psSelect.setString( 1, this.getCacheName() );
455             psSelect.setString( 2, (String) ce.getKey() );
456 
457             ResultSet rs = psSelect.executeQuery();
458 
459             if ( rs.next() )
460             {
461                 exists = true;
462             }
463 
464             if ( log.isDebugEnabled() )
465             {
466                 log.debug( "[" + ce.getKey() + "] existing status is " + exists );
467             }
468 
469             rs.close();
470         }
471         catch ( SQLException e )
472         {
473             log.error( "Problem looking for item before insert.", e );
474         }
475         finally
476         {
477             try
478             {
479                 if ( psSelect != null )
480                 {
481                     psSelect.close();
482                 }
483             }
484             catch ( SQLException e1 )
485             {
486                 log.error( "Problem closing statement.", e1 );
487             }
488 
489             try
490             {
491                 con.close();
492             }
493             catch ( SQLException e )
494             {
495                 log.error( "Problem closing connection.", e );
496             }
497         }
498 
499         return exists;
500     }
501 
502     /**
503      * Queries the database for the value. If it gets a result, the value is deserialized.
504      * <p>
505      * @param key
506      * @return ICacheElement
507      * @see org.apache.jcs.auxiliary.disk.AbstractDiskCache#doGet(java.io.Serializable)
508      */
509     @Override
510     protected ICacheElement<K, V> processGet( K key )
511     {
512         incrementGetCount();
513 
514         if ( log.isDebugEnabled() )
515         {
516             log.debug( "Getting [" + key + "] from disk" );
517         }
518 
519         if ( !alive )
520         {
521             return null;
522         }
523 
524         ICacheElement<K, V> obj = null;
525 
526         byte[] data = null;
527         try
528         {
529             // region, key
530             String selectString = "select ELEMENT from " + getJdbcDiskCacheAttributes().getTableName()
531                 + " where REGION = ? and CACHE_KEY = ?";
532 
533             Connection con = getPoolAccess().getConnection();
534             try
535             {
536                 PreparedStatement psSelect = null;
537                 try
538                 {
539                     psSelect = con.prepareStatement( selectString );
540                     psSelect.setString( 1, this.getCacheName() );
541                     psSelect.setString( 2, key.toString() );
542 
543                     ResultSet rs = psSelect.executeQuery();
544                     try
545                     {
546                         if ( rs.next() )
547                         {
548                             data = rs.getBytes( 1 );
549                         }
550                         if ( data != null )
551                         {
552                             try
553                             {
554                                 // USE THE SERIALIZER
555                                 obj = getElementSerializer().deSerialize( data );
556                             }
557                             catch ( IOException ioe )
558                             {
559                                 log.error( "Problem getting item for key [" + key + "]", ioe );
560                             }
561                             catch ( Exception e )
562                             {
563                                 log.error( "Problem getting item for key [" + key + "]", e );
564                             }
565                         }
566                     }
567                     finally
568                     {
569                         if ( rs != null )
570                         {
571                             rs.close();
572                         }
573                     }
574                 }
575                 finally
576                 {
577                     if ( psSelect != null )
578                     {
579                         psSelect.close();
580                     }
581                 }
582             }
583             finally
584             {
585                 if ( con != null )
586                 {
587                     con.close();
588                 }
589             }
590         }
591         catch ( SQLException sqle )
592         {
593             log.error( "Caught a SQL exception trying to get the item for key [" + key + "]", sqle );
594         }
595 
596         if ( log.isInfoEnabled() )
597         {
598             if ( getCount % LOG_INTERVAL == 0 )
599             {
600                 // TODO make a log stats method
601                 log.info( "Get Count [" + getCount + "]" );
602             }
603         }
604         return obj;
605     }
606 
607     /**
608      * This will run a like query. It will try to construct a usable query but different
609      * implementations will be needed to adjust the syntax.
610      * <p>
611      * @param pattern
612      * @return key,value map
613      */
614     @Override
615     protected Map<K, ICacheElement<K, V>> processGetMatching( String pattern )
616     {
617         incrementGetMatchingCount();
618 
619         if ( log.isDebugEnabled() )
620         {
621             log.debug( "Getting [" + pattern + "] from disk" );
622         }
623 
624         if ( !alive )
625         {
626             return null;
627         }
628 
629         Map<K, ICacheElement<K, V>> results = new HashMap<K, ICacheElement<K, V>>();
630 
631         try
632         {
633             // region, key
634             String selectString = "select CACHE_KEY, ELEMENT from " + getJdbcDiskCacheAttributes().getTableName()
635                 + " where REGION = ? and CACHE_KEY like ?";
636 
637             Connection con = getPoolAccess().getConnection();
638             try
639             {
640                 PreparedStatement psSelect = null;
641                 try
642                 {
643                     psSelect = con.prepareStatement( selectString );
644                     psSelect.setString( 1, this.getCacheName() );
645                     psSelect.setString( 2, constructLikeParameterFromPattern( pattern ) );
646 
647                     ResultSet rs = psSelect.executeQuery();
648                     try
649                     {
650                         while ( rs.next() )
651                         {
652                             String key = rs.getString( 1 );
653                             byte[] data = rs.getBytes( 2 );
654                             if ( data != null )
655                             {
656                                 try
657                                 {
658                                     // USE THE SERIALIZER
659                                     ICacheElement<K, V> value = getElementSerializer().deSerialize( data );
660                                     results.put( (K) key, value );
661                                 }
662                                 catch ( IOException ioe )
663                                 {
664                                     log.error( "Problem getting items for pattern [" + pattern + "]", ioe );
665                                 }
666                                 catch ( Exception e )
667                                 {
668                                     log.error( "Problem getting items for pattern [" + pattern + "]", e );
669                                 }
670                             }
671                         }
672                     }
673                     finally
674                     {
675                         if ( rs != null )
676                         {
677                             rs.close();
678                         }
679                     }
680                 }
681                 finally
682                 {
683                     if ( psSelect != null )
684                     {
685                         psSelect.close();
686                     }
687                 }
688             }
689             finally
690             {
691                 if ( con != null )
692                 {
693                     con.close();
694                 }
695             }
696         }
697         catch ( SQLException sqle )
698         {
699             log.error( "Caught a SQL exception trying to get items for pattern [" + pattern + "]", sqle );
700         }
701 
702         if ( log.isInfoEnabled() )
703         {
704             if ( getMatchingCount % LOG_INTERVAL == 0 )
705             {
706                 // TODO make a log stats method
707                 log.info( "Get Matching Count [" + getMatchingCount + "]" );
708             }
709         }
710         return results;
711     }
712 
713     /**
714      * @param pattern
715      * @return String to use in the like query.
716      */
717     public String constructLikeParameterFromPattern( String pattern )
718     {
719         String likePattern = pattern.replaceAll( "\\.\\+", "%" );
720         likePattern = likePattern.replaceAll( "\\.", "_" );
721 
722         if ( log.isDebugEnabled() )
723         {
724             log.debug( "pattern = [" + likePattern + "]" );
725         }
726 
727         return likePattern;
728     }
729 
730     /**
731      * Returns true if the removal was successful; or false if there is nothing to remove. Current
732      * implementation always results in a disk orphan.
733      * <p>
734      * @param key
735      * @return boolean
736      */
737     @Override
738     protected boolean processRemove( K key )
739     {
740         // remove single item.
741         String sql = "delete from " + getJdbcDiskCacheAttributes().getTableName()
742             + " where REGION = ? and CACHE_KEY = ?";
743 
744         try
745         {
746             boolean partial = false;
747             if ( key instanceof String && key.toString().endsWith( CacheConstants.NAME_COMPONENT_DELIMITER ) )
748             {
749                 // remove all keys of the same name group.
750                 sql = "delete from " + getJdbcDiskCacheAttributes().getTableName()
751                     + " where REGION = ? and CACHE_KEY like ?";
752                 partial = true;
753             }
754             Connection con = getPoolAccess().getConnection();
755             PreparedStatement psSelect = null;
756             try
757             {
758                 psSelect = con.prepareStatement( sql );
759                 psSelect.setString( 1, this.getCacheName() );
760                 if ( partial )
761                 {
762                     psSelect.setString( 2, key.toString() + "%" );
763                 }
764                 else
765                 {
766                     psSelect.setString( 2, key.toString() );
767                 }
768 
769                 psSelect.executeUpdate();
770 
771                 alive = true;
772             }
773             catch ( SQLException e )
774             {
775                 log.error( "Problem creating statement. sql [" + sql + "]", e );
776                 alive = false;
777             }
778             finally
779             {
780                 try
781                 {
782                     if ( psSelect != null )
783                     {
784                         psSelect.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 updating cache.", e );
797             reset();
798         }
799         return false;
800     }
801 
802     /**
803      * This should remove all elements. The auxiliary can be configured to forbid this behavior. If
804      * remove all is not allowed, the method balks.
805      */
806     @Override
807     protected void processRemoveAll()
808     {
809         // it should never get here formt he abstract dis cache.
810         if ( this.jdbcDiskCacheAttributes.isAllowRemoveAll() )
811         {
812             try
813             {
814                 String sql = "delete from " + getJdbcDiskCacheAttributes().getTableName() + " where REGION = ?";
815                 Connection con = getPoolAccess().getConnection();
816                 PreparedStatement psDelete = null;
817                 try
818                 {
819                     psDelete = con.prepareStatement( sql );
820                     psDelete.setString( 1, this.getCacheName() );
821                     alive = true;
822                     psDelete.executeUpdate();
823                 }
824                 catch ( SQLException e )
825                 {
826                     log.error( "Problem creating statement.", e );
827                     alive = false;
828                 }
829                 finally
830                 {
831                     try
832                     {
833                         if ( psDelete != null )
834                         {
835                             psDelete.close();
836                         }
837                         con.close();
838                     }
839                     catch ( SQLException e1 )
840                     {
841                         log.error( "Problem closing statement.", e1 );
842                     }
843                 }
844             }
845             catch ( Exception e )
846             {
847                 log.error( "Problem removing all.", e );
848                 reset();
849             }
850         }
851         else
852         {
853             if ( log.isInfoEnabled() )
854             {
855                 log.info( "RemoveAll was requested but the request was not fulfilled: allowRemoveAll is set to false." );
856             }
857         }
858     }
859 
860     /**
861      * Removed the expired. (now - create time) > max life seconds * 1000
862      * <p>
863      * @return the number deleted
864      */
865     protected int deleteExpired()
866     {
867         int deleted = 0;
868 
869         try
870         {
871             getTableState().setState( TableState.DELETE_RUNNING );
872 
873             long now = System.currentTimeMillis() / 1000;
874 
875             // This is to slow when we push over a million records
876             // String sql = "delete from " +
877             // getJdbcDiskCacheAttributes().getTableName() + " where REGION = '"
878             // + this.getCacheName() + "' and IS_ETERNAL = 'F' and (" + now
879             // + " - UPDATE_TIME_SECONDS) > MAX_LIFE_SECONDS";
880 
881             String sql = "delete from " + getJdbcDiskCacheAttributes().getTableName()
882                 + " where IS_ETERNAL = ? and REGION = ? and ? > SYSTEM_EXPIRE_TIME_SECONDS";
883 
884             Connection con = getPoolAccess().getConnection();
885             PreparedStatement psDelete = null;
886             try
887             {
888                 psDelete = con.prepareStatement( sql );
889                 psDelete.setString( 1, "F" );
890                 psDelete.setString( 2, this.getCacheName() );
891                 psDelete.setLong( 3, now );
892 
893                 alive = true;
894 
895                 deleted = psDelete.executeUpdate();
896             }
897             catch ( SQLException e )
898             {
899                 log.error( "Problem creating statement.", e );
900                 alive = false;
901             }
902             finally
903             {
904                 try
905                 {
906                     if ( psDelete != null )
907                     {
908                         psDelete.close();
909                     }
910                     con.close();
911                 }
912                 catch ( SQLException e1 )
913                 {
914                     log.error( "Problem closing statement.", e1 );
915                 }
916             }
917             logApplicationEvent( getAuxiliaryCacheAttributes().getName(), "deleteExpired",
918                                  "Deleted expired elements.  URL: " + getDiskLocation() );
919         }
920         catch ( Exception e )
921         {
922             logError( getAuxiliaryCacheAttributes().getName(), "deleteExpired", e.getMessage() + " URL: "
923                 + getDiskLocation() );
924             log.error( "Problem removing expired elements from the table.", e );
925             reset();
926         }
927         finally
928         {
929             getTableState().setState( TableState.FREE );
930         }
931 
932         return deleted;
933     }
934 
935     /**
936      * Typically this is used to handle errors by last resort, force content update, or removeall
937      */
938     public void reset()
939     {
940         // nothing
941     }
942 
943     /** Shuts down the pool */
944     @Override
945     public void processDispose()
946     {
947         ICacheEvent<K> cacheEvent = createICacheEvent( cacheName, (K)"none", ICacheEventLogger.DISPOSE_EVENT );
948         try
949         {
950             try
951             {
952                 getPoolAccess().shutdownDriver();
953             }
954             catch ( Exception e )
955             {
956                 log.error( "Problem shutting down.", e );
957             }
958         }
959         finally
960         {
961             logICacheEvent( cacheEvent );
962         }
963     }
964 
965     /**
966      * Returns the current cache size. Just does a count(*) for the region.
967      * <p>
968      * @return The size value
969      */
970     @Override
971     public int getSize()
972     {
973         int size = 0;
974 
975         // region, key
976         String selectString = "select count(*) from " + getJdbcDiskCacheAttributes().getTableName()
977             + " where REGION = ?";
978 
979         Connection con;
980         try
981         {
982             con = getPoolAccess().getConnection();
983         }
984         catch ( SQLException e1 )
985         {
986             log.error( "Problem getting connection.", e1 );
987             return size;
988         }
989         try
990         {
991             PreparedStatement psSelect = null;
992             try
993             {
994                 psSelect = con.prepareStatement( selectString );
995                 psSelect.setString( 1, this.getCacheName() );
996                 ResultSet rs = null;
997 
998                 rs = psSelect.executeQuery();
999                 try
1000                 {
1001                     if ( rs.next() )
1002                     {
1003                         size = rs.getInt( 1 );
1004                     }
1005                 }
1006                 finally
1007                 {
1008                     if ( rs != null )
1009                     {
1010                         rs.close();
1011                     }
1012                 }
1013             }
1014             finally
1015             {
1016                 if ( psSelect != null )
1017                 {
1018                     psSelect.close();
1019                 }
1020             }
1021         }
1022         catch ( SQLException e )
1023         {
1024             log.error( "Problem getting size.", e );
1025         }
1026         finally
1027         {
1028             try
1029             {
1030                 con.close();
1031             }
1032             catch ( SQLException e )
1033             {
1034                 log.error( "Problem closing connection.", e );
1035             }
1036         }
1037         return size;
1038     }
1039 
1040     /**
1041      * @param groupName
1042      * @return Set
1043      */
1044     @Override
1045     public Set<K> getGroupKeys( String groupName )
1046     {
1047         throw new UnsupportedOperationException( "Groups not implemented." );
1048         // return null;
1049     }
1050     
1051     /**
1052      * @return Set
1053      */
1054     @Override
1055     public Set<String> getGroupNames()
1056     {
1057         throw new UnsupportedOperationException( "Groups not implemented." );
1058         // return null;
1059     }
1060 
1061     /**
1062      * @param elementSerializer The elementSerializer to set.
1063      */
1064     @Override
1065     public void setElementSerializer( IElementSerializer elementSerializer )
1066     {
1067         this.elementSerializer = elementSerializer;
1068     }
1069 
1070     /**
1071      * @return Returns the elementSerializer.
1072      */
1073     @Override
1074     public IElementSerializer getElementSerializer()
1075     {
1076         return elementSerializer;
1077     }
1078 
1079     /** safely increment */
1080     private synchronized void incrementUpdateCount()
1081     {
1082         updateCount++;
1083     }
1084 
1085     /** safely increment */
1086     private synchronized void incrementGetCount()
1087     {
1088         getCount++;
1089     }
1090 
1091     /** safely increment */
1092     private synchronized void incrementGetMatchingCount()
1093     {
1094         getMatchingCount++;
1095     }
1096 
1097     /**
1098      * @param jdbcDiskCacheAttributes The jdbcDiskCacheAttributes to set.
1099      */
1100     protected void setJdbcDiskCacheAttributes( JDBCDiskCacheAttributes jdbcDiskCacheAttributes )
1101     {
1102         this.jdbcDiskCacheAttributes = jdbcDiskCacheAttributes;
1103     }
1104 
1105     /**
1106      * @return Returns the jdbcDiskCacheAttributes.
1107      */
1108     protected JDBCDiskCacheAttributes getJdbcDiskCacheAttributes()
1109     {
1110         return jdbcDiskCacheAttributes;
1111     }
1112 
1113     /**
1114      * @return Returns the AuxiliaryCacheAttributes.
1115      */
1116     public AuxiliaryCacheAttributes getAuxiliaryCacheAttributes()
1117     {
1118         return this.getJdbcDiskCacheAttributes();
1119     }
1120 
1121     /**
1122      * Extends the parent stats.
1123      * <p>
1124      * @return IStats
1125      */
1126     @Override
1127     public IStats getStatistics()
1128     {
1129         IStats stats = super.getStatistics();
1130         stats.setTypeName( "JDBC/Abstract Disk Cache" );
1131         stats.getStatElements();
1132 
1133         ArrayList<IStatElement> elems = new ArrayList<IStatElement>();
1134 
1135         IStatElement se = null;
1136 
1137         se = new StatElement();
1138         se.setName( "Update Count" );
1139         se.setData( "" + updateCount );
1140         elems.add( se );
1141 
1142         se = new StatElement();
1143         se.setName( "Get Count" );
1144         se.setData( "" + getCount );
1145         elems.add( se );
1146 
1147         se = new StatElement();
1148         se.setName( "Get Matching Count" );
1149         se.setData( "" + getMatchingCount );
1150         elems.add( se );
1151 
1152         se = new StatElement();
1153         se.setName( "Size" );
1154         se.setData( "" + getSize() );
1155         elems.add( se );
1156 
1157         se = new StatElement();
1158         se.setName( "Active DB Connections" );
1159         se.setData( "" + getPoolAccess().getNumActiveInPool() );
1160         elems.add( se );
1161 
1162         se = new StatElement();
1163         se.setName( "Idle DB Connections" );
1164         se.setData( "" + getPoolAccess().getNumIdleInPool() );
1165         elems.add( se );
1166 
1167         se = new StatElement();
1168         se.setName( "DB URL" );
1169         if ( getPoolAccess() != null )
1170         {
1171             se.setData( "" + getPoolAccess().getPoolUrl() );
1172         }
1173         else
1174         {
1175             se.setData( "" + getJdbcDiskCacheAttributes().getUrl() );
1176         }
1177         elems.add( se );
1178 
1179         // get the stats from the event queue too
1180         // get as array, convert to list, add list to our outer list
1181         IStatElement[] eqSEs = stats.getStatElements();
1182         List<IStatElement> eqL = Arrays.asList( eqSEs );
1183         elems.addAll( eqL );
1184 
1185         // get an array and put them in the Stats object
1186         IStatElement[] ses = elems.toArray( new StatElement[0] );
1187         stats.setStatElements( ses );
1188 
1189         return stats;
1190     }
1191 
1192     /**
1193      * Returns the name of the table.
1194      * <p>
1195      * @return the table name or UNDEFINED
1196      */
1197     protected String getTableName()
1198     {
1199         String name = "UNDEFINED";
1200         if ( this.getJdbcDiskCacheAttributes() != null )
1201         {
1202             name = this.getJdbcDiskCacheAttributes().getTableName();
1203         }
1204         return name;
1205     }
1206 
1207     /**
1208      * @param tableState The tableState to set.
1209      */
1210     public void setTableState( TableState tableState )
1211     {
1212         this.tableState = tableState;
1213     }
1214 
1215     /**
1216      * @return Returns the tableState.
1217      */
1218     public TableState getTableState()
1219     {
1220         return tableState;
1221     }
1222 
1223     /**
1224      * This is used by the event logging.
1225      * <p>
1226      * @return the location of the disk, either path or ip.
1227      */
1228     @Override
1229     protected String getDiskLocation()
1230     {
1231         return this.jdbcDiskCacheAttributes.getUrl();
1232     }
1233 
1234     /**
1235      * @param poolAccess the poolAccess to set
1236      */
1237     protected void setPoolAccess( JDBCDiskCachePoolAccess poolAccess )
1238     {
1239         this.poolAccess = poolAccess;
1240     }
1241 
1242     /**
1243      * Public so managers can access it.
1244      * @return the poolAccess
1245      */
1246     public JDBCDiskCachePoolAccess getPoolAccess()
1247     {
1248         return poolAccess;
1249     }
1250 
1251     /**
1252      * For debugging.
1253      * <p>
1254      * @return this.getStats();
1255      */
1256     @Override
1257     public String toString()
1258     {
1259         return this.getStats();
1260     }
1261 }