001package org.apache.commons.jcs3.auxiliary.disk.jdbc.mysql;
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.text.ParseException;
024import java.util.Date;
025import java.util.concurrent.TimeUnit;
026
027import javax.sql.DataSource;
028
029import org.apache.commons.jcs3.auxiliary.AuxiliaryCacheAttributes;
030import org.apache.commons.jcs3.auxiliary.disk.jdbc.JDBCDiskCacheFactory;
031import org.apache.commons.jcs3.auxiliary.disk.jdbc.TableState;
032import org.apache.commons.jcs3.auxiliary.disk.jdbc.dsfactory.DataSourceFactory;
033import org.apache.commons.jcs3.auxiliary.disk.jdbc.mysql.util.ScheduleParser;
034import org.apache.commons.jcs3.engine.behavior.ICompositeCacheManager;
035import org.apache.commons.jcs3.engine.behavior.IElementSerializer;
036import org.apache.commons.jcs3.engine.logging.behavior.ICacheEventLogger;
037import org.apache.commons.jcs3.log.Log;
038import org.apache.commons.jcs3.log.LogManager;
039
040/**
041 * This factory should create mysql disk caches.
042 */
043public class MySQLDiskCacheFactory
044    extends JDBCDiskCacheFactory
045{
046    /** The logger */
047    private static final Log log = LogManager.getLog( MySQLDiskCacheFactory.class );
048
049    /**
050     * This factory method should create an instance of the mysqlcache.
051     * <p>
052     * @param rawAttr specific cache configuration attributes
053     * @param compositeCacheManager the global cache manager
054     * @param cacheEventLogger a specific logger for cache events
055     * @param elementSerializer a serializer for cache elements
056     * @return MySQLDiskCache the cache instance
057     * @throws SQLException if the cache instance could not be created
058     */
059    @Override
060    public <K, V> MySQLDiskCache<K, V> createCache( final AuxiliaryCacheAttributes rawAttr,
061            final ICompositeCacheManager compositeCacheManager,
062            final ICacheEventLogger cacheEventLogger, final IElementSerializer elementSerializer )
063            throws SQLException
064    {
065        final MySQLDiskCacheAttributes cattr = (MySQLDiskCacheAttributes) rawAttr;
066        final TableState tableState = getTableState( cattr.getTableName() );
067        final DataSourceFactory dsFactory = getDataSourceFactory(cattr, compositeCacheManager.getConfigurationProperties());
068
069        final MySQLDiskCache<K, V> cache = new MySQLDiskCache<>( cattr, dsFactory, tableState);
070        cache.setCacheEventLogger( cacheEventLogger );
071        cache.setElementSerializer( elementSerializer );
072
073        // create a shrinker if we need it.
074        createShrinkerWhenNeeded( cattr, cache );
075        scheduleOptimizations( cattr, tableState, cache.getDataSource() );
076
077        return cache;
078
079    }
080
081    /**
082     * For each time in the optimization schedule, this calls schedule Optimization.
083     * <p>
084     * @param attributes configuration properties.
085     * @param tableState for noting optimization in progress, etc.
086     * @param ds the DataSource
087     */
088    protected void scheduleOptimizations( final MySQLDiskCacheAttributes attributes, final TableState tableState, final DataSource ds  )
089    {
090        if ( attributes != null )
091        {
092            if ( attributes.getOptimizationSchedule() != null )
093            {
094                log.info( "Will try to configure optimization for table [{0}] on schedule [{1}]",
095                        attributes::getTableName, attributes::getOptimizationSchedule);
096
097                final MySQLTableOptimizer optimizer = new MySQLTableOptimizer( attributes, tableState, ds );
098
099                // loop through the dates.
100                try
101                {
102                    // canĀ“t be null, otherwise ScheduleParser.createDatesForSchedule will throw ParseException
103                    final Date[] dates = ScheduleParser.createDatesForSchedule( attributes.getOptimizationSchedule() );
104                    for (final Date date : dates) {
105                        this.scheduleOptimization( date, optimizer );
106                    }
107                }
108                catch ( final ParseException e )
109                {
110                    log.warn( "Problem creating optimization schedule for table [{0}]",
111                            attributes.getTableName(), e );
112                }
113            }
114            else
115            {
116                log.info( "Optimization is not configured for table [{0}]",
117                        attributes.getTableName());
118            }
119        }
120    }
121
122    /**
123     * This takes in a single time and schedules the optimizer to be called at that time every day.
124     * <p>
125     * @param startTime -- HH:MM:SS format
126     * @param optimizer
127     */
128    protected void scheduleOptimization( final Date startTime, final MySQLTableOptimizer optimizer )
129    {
130        log.info( "startTime [{0}] for optimizer {1}", startTime, optimizer );
131
132        final Date now = new Date();
133        final long initialDelay = startTime.getTime() - now.getTime();
134
135        // have the daemon execute the optimization
136        getScheduledExecutorService().scheduleAtFixedRate(() -> optimizeTable(optimizer),
137                initialDelay, 86400L, TimeUnit.SECONDS );
138    }
139
140    /**
141     * This calls the optimizers' optimize table method. This is used by the timer.
142     */
143    private static void optimizeTable(final MySQLTableOptimizer optimizer)
144    {
145        if ( optimizer != null )
146        {
147            final boolean success = optimizer.optimizeTable();
148            log.info( "Optimization success status [{0}]", success );
149        }
150        else
151        {
152            log.warn( "OptimizerRunner: The optimizer is null. Could not optimize table." );
153        }
154    }
155}