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}