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) > 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) > 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}