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.sql.SQLException; 023import java.util.Properties; 024import java.util.concurrent.ConcurrentHashMap; 025import java.util.concurrent.ConcurrentMap; 026import java.util.concurrent.ScheduledExecutorService; 027import java.util.concurrent.TimeUnit; 028import java.util.concurrent.locks.ReentrantLock; 029 030import org.apache.commons.jcs.auxiliary.AbstractAuxiliaryCacheFactory; 031import org.apache.commons.jcs.auxiliary.AuxiliaryCacheAttributes; 032import org.apache.commons.jcs.auxiliary.disk.jdbc.dsfactory.DataSourceFactory; 033import org.apache.commons.jcs.auxiliary.disk.jdbc.dsfactory.JndiDataSourceFactory; 034import org.apache.commons.jcs.auxiliary.disk.jdbc.dsfactory.SharedPoolDataSourceFactory; 035import org.apache.commons.jcs.engine.behavior.ICompositeCacheManager; 036import org.apache.commons.jcs.engine.behavior.IElementSerializer; 037import org.apache.commons.jcs.engine.behavior.IRequireScheduler; 038import org.apache.commons.jcs.engine.logging.behavior.ICacheEventLogger; 039import org.apache.commons.jcs.utils.config.PropertySetter; 040import org.apache.commons.logging.Log; 041import org.apache.commons.logging.LogFactory; 042 043/** 044 * This factory should create JDBC auxiliary caches. 045 * <p> 046 * @author Aaron Smuts 047 */ 048public class JDBCDiskCacheFactory 049 extends AbstractAuxiliaryCacheFactory 050 implements IRequireScheduler 051{ 052 /** The logger */ 053 private static final Log log = LogFactory.getLog( JDBCDiskCacheFactory.class ); 054 055 /** 056 * A map of TableState objects to table names. Each cache has a table state object, which is 057 * used to determine if any long processes such as deletes or optimizations are running. 058 */ 059 private ConcurrentMap<String, TableState> tableStates; 060 061 /** The background scheduler, one for all regions. Injected by the configurator */ 062 protected ScheduledExecutorService scheduler; 063 064 /** 065 * A map of table name to shrinker threads. This allows each table to have a different setting. 066 * It assumes that there is only one jdbc disk cache auxiliary defined per table. 067 */ 068 private ConcurrentMap<String, ShrinkerThread> shrinkerThreadMap; 069 070 /** Pool name to DataSourceFactories */ 071 private ConcurrentMap<String, DataSourceFactory> dsFactories; 072 073 /** Lock to allow lengthy initialization of DataSourceFactories */ 074 private ReentrantLock dsFactoryLock; 075 076 /** props prefix */ 077 protected static final String POOL_CONFIGURATION_PREFIX = "jcs.jdbcconnectionpool."; 078 079 /** .attributes */ 080 protected static final String ATTRIBUTE_PREFIX = ".attributes"; 081 082 /** 083 * This factory method should create an instance of the jdbc cache. 084 * <p> 085 * @param rawAttr specific cache configuration attributes 086 * @param compositeCacheManager the global cache manager 087 * @param cacheEventLogger a specific logger for cache events 088 * @param elementSerializer a serializer for cache elements 089 * @return JDBCDiskCache the cache instance 090 * @throws SQLException if the cache instance could not be created 091 */ 092 @Override 093 public <K, V> JDBCDiskCache<K, V> createCache( AuxiliaryCacheAttributes rawAttr, 094 ICompositeCacheManager compositeCacheManager, 095 ICacheEventLogger cacheEventLogger, IElementSerializer elementSerializer ) 096 throws SQLException 097 { 098 JDBCDiskCacheAttributes cattr = (JDBCDiskCacheAttributes) rawAttr; 099 TableState tableState = getTableState( cattr.getTableName() ); 100 DataSourceFactory dsFactory = getDataSourceFactory(cattr, compositeCacheManager.getConfigurationProperties()); 101 102 JDBCDiskCache<K, V> cache = new JDBCDiskCache<K, V>( cattr, dsFactory, tableState, compositeCacheManager ); 103 cache.setCacheEventLogger( cacheEventLogger ); 104 cache.setElementSerializer( elementSerializer ); 105 106 // create a shrinker if we need it. 107 createShrinkerWhenNeeded( cattr, cache ); 108 109 return cache; 110 } 111 112 /** 113 * Initialize this factory 114 */ 115 @Override 116 public void initialize() 117 { 118 super.initialize(); 119 this.tableStates = new ConcurrentHashMap<String, TableState>(); 120 this.shrinkerThreadMap = new ConcurrentHashMap<String, ShrinkerThread>(); 121 this.dsFactories = new ConcurrentHashMap<String, DataSourceFactory>(); 122 this.dsFactoryLock = new ReentrantLock(); 123 } 124 125 /** 126 * Dispose of this factory, clean up shared resources 127 */ 128 @Override 129 public void dispose() 130 { 131 this.tableStates.clear(); 132 133 for (DataSourceFactory dsFactory : this.dsFactories.values()) 134 { 135 try 136 { 137 dsFactory.close(); 138 } 139 catch (SQLException e) 140 { 141 log.error("Could not close data source factory " + dsFactory.getName(), e); 142 } 143 } 144 145 this.dsFactories.clear(); 146 this.shrinkerThreadMap.clear(); 147 super.dispose(); 148 } 149 150 /** 151 * Get a table state for a given table name 152 * 153 * @param tableName 154 * @return a cached instance of the table state 155 */ 156 protected TableState getTableState(String tableName) 157 { 158 TableState newTableState = new TableState( tableName ); 159 TableState tableState = tableStates.putIfAbsent( tableName, newTableState ); 160 161 if ( tableState == null ) 162 { 163 tableState = newTableState; 164 } 165 166 return tableState; 167 } 168 169 /** 170 * @see org.apache.commons.jcs.engine.behavior.IRequireScheduler#setScheduledExecutorService(java.util.concurrent.ScheduledExecutorService) 171 */ 172 @Override 173 public void setScheduledExecutorService(ScheduledExecutorService scheduledExecutor) 174 { 175 this.scheduler = scheduledExecutor; 176 } 177 178 /** 179 * Get the scheduler service 180 * 181 * @return the scheduler 182 */ 183 protected ScheduledExecutorService getScheduledExecutorService() 184 { 185 return scheduler; 186 } 187 188 /** 189 * If UseDiskShrinker is true then we will create a shrinker daemon if necessary. 190 * <p> 191 * @param cattr 192 * @param raf 193 */ 194 protected void createShrinkerWhenNeeded( JDBCDiskCacheAttributes cattr, JDBCDiskCache<?, ?> raf ) 195 { 196 // add cache to shrinker. 197 if ( cattr.isUseDiskShrinker() ) 198 { 199 ScheduledExecutorService shrinkerService = getScheduledExecutorService(); 200 ShrinkerThread newShrinkerThread = new ShrinkerThread(); 201 ShrinkerThread shrinkerThread = shrinkerThreadMap.putIfAbsent( cattr.getTableName(), newShrinkerThread ); 202 203 if ( shrinkerThread == null ) 204 { 205 shrinkerThread = newShrinkerThread; 206 207 long intervalMillis = Math.max( 999, cattr.getShrinkerIntervalSeconds() * 1000 ); 208 if ( log.isInfoEnabled() ) 209 { 210 log.info( "Setting the shrinker to run every [" + intervalMillis + "] ms. for table [" 211 + cattr.getTableName() + "]" ); 212 } 213 shrinkerService.scheduleAtFixedRate(shrinkerThread, 0, intervalMillis, TimeUnit.MILLISECONDS); 214 } 215 216 shrinkerThread.addDiskCacheToShrinkList( raf ); 217 } 218 } 219 220 /** 221 * manages the DataSourceFactories. 222 * <p> 223 * @param cattr the cache configuration 224 * @param configProps the configuration properties object 225 * @return a DataSourceFactory 226 * @throws SQLException if a database access error occurs 227 */ 228 protected DataSourceFactory getDataSourceFactory( JDBCDiskCacheAttributes cattr, 229 Properties configProps ) throws SQLException 230 { 231 String poolName = null; 232 233 if (cattr.getConnectionPoolName() == null) 234 { 235 poolName = cattr.getCacheName() + "." + JDBCDiskCacheAttributes.DEFAULT_POOL_NAME; 236 } 237 else 238 { 239 poolName = cattr.getConnectionPoolName(); 240 } 241 242 243 DataSourceFactory dsFactory = this.dsFactories.get(poolName); 244 245 if (dsFactory == null) 246 { 247 dsFactoryLock.lock(); 248 249 try 250 { 251 // double check 252 dsFactory = this.dsFactories.get(poolName); 253 254 if (dsFactory == null) 255 { 256 JDBCDiskCacheAttributes dsConfig = null; 257 258 if (cattr.getConnectionPoolName() == null) 259 { 260 dsConfig = cattr; 261 } 262 else 263 { 264 dsConfig = new JDBCDiskCacheAttributes(); 265 String dsConfigAttributePrefix = POOL_CONFIGURATION_PREFIX + poolName + ATTRIBUTE_PREFIX; 266 PropertySetter.setProperties( dsConfig, 267 configProps, 268 dsConfigAttributePrefix + "." ); 269 270 dsConfig.setConnectionPoolName(poolName); 271 } 272 273 if ( dsConfig.getJndiPath() != null ) 274 { 275 dsFactory = new JndiDataSourceFactory(); 276 } 277 else 278 { 279 dsFactory = new SharedPoolDataSourceFactory(); 280 } 281 282 dsFactory.initialize(dsConfig); 283 this.dsFactories.put(poolName, dsFactory); 284 } 285 } 286 finally 287 { 288 dsFactoryLock.unlock(); 289 } 290 } 291 292 return dsFactory; 293 } 294}