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.sql.SQLException;
23  import java.util.Properties;
24  import java.util.concurrent.ConcurrentHashMap;
25  import java.util.concurrent.ConcurrentMap;
26  import java.util.concurrent.ScheduledExecutorService;
27  import java.util.concurrent.TimeUnit;
28  import java.util.concurrent.locks.ReentrantLock;
29  
30  import org.apache.commons.jcs.auxiliary.AbstractAuxiliaryCacheFactory;
31  import org.apache.commons.jcs.auxiliary.AuxiliaryCacheAttributes;
32  import org.apache.commons.jcs.auxiliary.disk.jdbc.dsfactory.DataSourceFactory;
33  import org.apache.commons.jcs.auxiliary.disk.jdbc.dsfactory.JndiDataSourceFactory;
34  import org.apache.commons.jcs.auxiliary.disk.jdbc.dsfactory.SharedPoolDataSourceFactory;
35  import org.apache.commons.jcs.engine.behavior.ICompositeCacheManager;
36  import org.apache.commons.jcs.engine.behavior.IElementSerializer;
37  import org.apache.commons.jcs.engine.behavior.IRequireScheduler;
38  import org.apache.commons.jcs.engine.logging.behavior.ICacheEventLogger;
39  import org.apache.commons.jcs.utils.config.PropertySetter;
40  import org.apache.commons.logging.Log;
41  import org.apache.commons.logging.LogFactory;
42  
43  /**
44   * This factory should create JDBC auxiliary caches.
45   * <p>
46   * @author Aaron Smuts
47   */
48  public class JDBCDiskCacheFactory
49      extends AbstractAuxiliaryCacheFactory
50      implements IRequireScheduler
51  {
52      /** The logger */
53      private static final Log log = LogFactory.getLog( JDBCDiskCacheFactory.class );
54  
55      /**
56       * A map of TableState objects to table names. Each cache has a table state object, which is
57       * used to determine if any long processes such as deletes or optimizations are running.
58       */
59      private ConcurrentMap<String, TableState> tableStates;
60  
61      /** The background scheduler, one for all regions. Injected by the configurator */
62      protected ScheduledExecutorService scheduler;
63  
64      /**
65       * A map of table name to shrinker threads. This allows each table to have a different setting.
66       * It assumes that there is only one jdbc disk cache auxiliary defined per table.
67       */
68      private ConcurrentMap<String, ShrinkerThread> shrinkerThreadMap;
69  
70      /** Pool name to DataSourceFactories */
71      private ConcurrentMap<String, DataSourceFactory> dsFactories;
72  
73      /** Lock to allow lengthy initialization of DataSourceFactories */
74      private ReentrantLock dsFactoryLock;
75  
76      /** props prefix */
77      protected static final String POOL_CONFIGURATION_PREFIX = "jcs.jdbcconnectionpool.";
78  
79      /** .attributes */
80      protected static final String ATTRIBUTE_PREFIX = ".attributes";
81  
82      /**
83       * This factory method should create an instance of the jdbc cache.
84       * <p>
85       * @param rawAttr specific cache configuration attributes
86       * @param compositeCacheManager the global cache manager
87       * @param cacheEventLogger a specific logger for cache events
88       * @param elementSerializer a serializer for cache elements
89       * @return JDBCDiskCache the cache instance
90       * @throws SQLException if the cache instance could not be created
91       */
92      @Override
93      public <K, V> JDBCDiskCache<K, V> createCache( AuxiliaryCacheAttributes rawAttr,
94              ICompositeCacheManager compositeCacheManager,
95              ICacheEventLogger cacheEventLogger, IElementSerializer elementSerializer )
96              throws SQLException
97      {
98          JDBCDiskCacheAttributes cattr = (JDBCDiskCacheAttributes) rawAttr;
99          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 }