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