View Javadoc
1   package org.apache.commons.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.sql.Connection;
24  import java.sql.PreparedStatement;
25  import java.sql.ResultSet;
26  import java.sql.SQLException;
27  import java.sql.Timestamp;
28  import java.util.HashMap;
29  import java.util.List;
30  import java.util.Map;
31  import java.util.Set;
32  import java.util.concurrent.atomic.AtomicInteger;
33  
34  import javax.sql.DataSource;
35  
36  import org.apache.commons.jcs.auxiliary.AuxiliaryCacheAttributes;
37  import org.apache.commons.jcs.auxiliary.disk.AbstractDiskCache;
38  import org.apache.commons.jcs.auxiliary.disk.jdbc.dsfactory.DataSourceFactory;
39  import org.apache.commons.jcs.engine.CacheConstants;
40  import org.apache.commons.jcs.engine.behavior.ICacheElement;
41  import org.apache.commons.jcs.engine.behavior.ICompositeCacheManager;
42  import org.apache.commons.jcs.engine.behavior.IElementSerializer;
43  import org.apache.commons.jcs.engine.logging.behavior.ICacheEvent;
44  import org.apache.commons.jcs.engine.logging.behavior.ICacheEventLogger;
45  import org.apache.commons.jcs.engine.stats.StatElement;
46  import org.apache.commons.jcs.engine.stats.behavior.IStatElement;
47  import org.apache.commons.jcs.engine.stats.behavior.IStats;
48  import org.apache.commons.jcs.utils.serialization.StandardSerializer;
49  import org.apache.commons.logging.Log;
50  import org.apache.commons.logging.LogFactory;
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) &gt; 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, V>
83      extends AbstractDiskCache<K, V>
84  {
85      /** The local logger. */
86      private static final Log log = LogFactory.getLog( JDBCDiskCache.class );
87  
88      /** custom serialization */
89      private IElementSerializer elementSerializer = new StandardSerializer();
90  
91      /** configuration */
92      private JDBCDiskCacheAttributes jdbcDiskCacheAttributes;
93  
94      /** # of times update was called */
95      private AtomicInteger updateCount = new AtomicInteger(0);
96  
97      /** # of times get was called */
98      private AtomicInteger getCount = new AtomicInteger(0);
99  
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 }