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}