/* ===================================================================
* DerbyMaintenanceRegistrationListener.java
*
* Created Dec 6, 2009 1:03:43 PM
*
* Copyright 2007-2009 SolarNetwork.net Dev Team
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA
* ===================================================================
*/
package net.solarnetwork.node.dao.jdbc.derby;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import org.osgi.framework.ServiceRegistration;
import org.quartz.CronTrigger;
import org.quartz.JobDetail;
import org.quartz.Scheduler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.jdbc.core.JdbcOperations;
import org.springframework.scheduling.quartz.JobDetailFactoryBean;
import net.solarnetwork.node.dao.jdbc.JdbcDao;
import net.solarnetwork.node.job.RandomizedCronTriggerFactoryBean;
import net.solarnetwork.node.job.SimpleTriggerAndJobDetail;
import net.solarnetwork.node.job.TriggerAndJobDetail;
import net.solarnetwork.node.util.BaseServiceListener;
import net.solarnetwork.node.util.RegisteredService;
/**
* An OSGi service registration listener for JdbcDao services, so various
* Derby-specific maintenance jobs can be automatically registered/unregistered
* with the job scheduler.
*
* <p>
* The idea of this listener is to automatically schedule jobs to perform
* maintenance on Derby database tables used by the Solar Node. Over time the
* tables will grow and maintenance procedures must be called to free up used
* disk space. This listener assumes any {@link JdbcDao} service will be using
* Derby, so this bundle should only be started on Solar Nodes actually using
* Derby (which is the default database implementation).
* </p>
*
* <p>
* The {@code maintenanceProperties} Properties can be used to customize each
* task. The properties are in the form
* <code>derby.maintenance.<em>schema</em>.<em>table</em>.<em>task</em></code>.
* The <em>schema</em> and <em>table</em> values will be the database schema and
* table names returned by {@link JdbcDao#getSchemaName()} and
* {@link JdbcDao#getTableNames()}. The <em>task</em> value will be one of the
* following:
* </p>
*
* <dl>
* <dt>compress.cron</dt>
* <dd>The Quartz cron expression to use for scheduling the
* {@link DerbyCompressTableJob}. If not found, this defaults to
* {@link #getCompressTableCronExpression()}.</dd>
* </dl>
*
* <p>
* The configurable properties of this class are:
* </p>
*
* <dl class="class-properties">
* <dt>scheduler</dt>
* <dd>The Quartz {@link Scheduler} for scheduling and un-scheduling jobs with
* as {@link TriggerAndJobDetail} services are registered and
* un-registered.</dd>
*
* <dt>jdbcOperations</dt>
* <dd>The {@link JdbcOperations} to use.</dd>
*
* <dt>compressTableCronExpression</dt>
* <dd>The default Quartz cron expression to use for scheduling the
* {@link DerbyCompressTableJob}. If a matching property is not found in the
* {@code maintenanceProperties} Properties, this value will be used for the
* cron expression for that job. Ideally all tables used in the Solar Node
* system will use different cron expressions because the compress maintenance
* can take a long time to complete and it is better to schedule different
* tables at different times. Defaults to
* {@link #DEFAULT_COMPRESS_TABLE_CRON_EXPRESSION}.</dd>
*
* <dt>maintenanceProperties</dt>
* <dd>Configuration properties to use when creating the maintenance jobs for
* each registered table. In general properties will be in the form
* <code>derby.maintenacne.schema.table.<em>task</em>. See the documentation for
* each task for more information.</dd>
* </dl>
*
* @author matt
* @version 1.2
*/
public class DerbyMaintenanceRegistrationListener
extends BaseServiceListener<JdbcDao, RegisteredService<JdbcDao>> {
/** The name of the {@link DerbyCompressTableJob} task. */
public static final String TASK_COMPRESS = "compress";
/**
* The default value for the {@code compressTableCronExpression} property.
*/
public static final String DEFAULT_COMPRESS_TABLE_CRON_EXPRESSION = "0 30 3 ? * WED,SAT";
private final Logger log = LoggerFactory.getLogger(DerbyMaintenanceRegistrationListener.class);
private JdbcOperations jdbcOperations = null;
private Properties maintenanceProperties = null;
private String compressTableCronExpression = DEFAULT_COMPRESS_TABLE_CRON_EXPRESSION;
/**
* Callback when a JdbcDao has been registered.
*
* @param jdbcDao
* the DAO
* @param properties
* the service properties
*/
public void onBind(JdbcDao jdbcDao, Map<String, ?> properties) {
if ( log.isDebugEnabled() ) {
log.debug("Bind called on [" + jdbcDao + "] with props " + properties);
}
List<ServiceRegistration<?>> services = new ArrayList<ServiceRegistration<?>>();
Hashtable<String, Object> serviceProps = new Hashtable<String, Object>();
serviceProps.put("Bundle-SymbolicName", getBundleContext().getBundle().getSymbolicName());
for ( String tableName : jdbcDao.getTableNames() ) {
JobDetail jobDetail = getCompressJobDetail(jdbcDao.getSchemaName(), tableName,
getJobName(jdbcDao.getSchemaName(), tableName, TASK_COMPRESS));
CronTrigger trigger = getCronTrigger(
getTaskPropertyValue(jdbcDao.getSchemaName(), tableName, TASK_COMPRESS + ".cron",
compressTableCronExpression),
jobDetail, getTriggerName(jdbcDao.getSchemaName(), tableName, TASK_COMPRESS));
SimpleTriggerAndJobDetail tjd = new SimpleTriggerAndJobDetail();
tjd.setJobDetail(jobDetail);
tjd.setTrigger(trigger);
tjd.setMessageSource(jdbcDao.getMessageSource());
ServiceRegistration<TriggerAndJobDetail> reg = getBundleContext()
.registerService(TriggerAndJobDetail.class, tjd, serviceProps);
services.add(reg);
}
this.addRegisteredService(new RegisteredService<JdbcDao>(jdbcDao, properties), services);
}
/**
* Callback when a JdbcDao has been un-registered.
*
* @param jdbcDao
* the DAO
* @param properties
* the service properties
*/
public void onUnbind(JdbcDao jdbcDao, Map<String, ?> properties) {
if ( jdbcDao == null ) {
// Gemini Blueprint calls this when availability="optional" and no services available
return;
}
if ( log.isDebugEnabled() ) {
log.debug("Unbind called on [" + jdbcDao + "] with props " + properties);
}
removeRegisteredService(jdbcDao, properties);
}
private JobDetail getCompressJobDetail(String schema, String table, String name) {
JobDetailFactoryBean jobDetail = new JobDetailFactoryBean();
jobDetail.setJobClass(DerbyCompressTableJob.class);
jobDetail.setName(name);
Map<String, Object> jobData = new HashMap<String, Object>();
jobData.put("schema", schema.toUpperCase());
jobData.put("table", table.toUpperCase());
jobData.put("jdbcOperations", jdbcOperations);
jobDetail.setJobDataAsMap(jobData);
jobDetail.afterPropertiesSet();
return jobDetail.getObject();
}
private CronTrigger getCronTrigger(String cronExpression, JobDetail jobDetail, String name) {
RandomizedCronTriggerFactoryBean cronTrigger = new RandomizedCronTriggerFactoryBean();
cronTrigger.setName(name);
try {
cronTrigger.setCronExpression(cronExpression);
cronTrigger.setJobDetail(jobDetail);
cronTrigger.setMisfireInstruction(CronTrigger.MISFIRE_INSTRUCTION_DO_NOTHING);
cronTrigger.setRandomSecond(true);
cronTrigger.afterPropertiesSet();
} catch ( ParseException e ) {
throw new RuntimeException(e);
}
return cronTrigger.getObject();
}
private String getJobName(String schema, String table, String task) {
return schema + '.' + table + '.' + task;
}
private String getTriggerName(String schema, String table, String task) {
return schema + '.' + table + '.' + task;
}
private String getTaskPropertyValue(String schema, String table, String task, String defaultValue) {
if ( maintenanceProperties == null ) {
return defaultValue;
}
String propKey = "derby.maintenance." + schema + '.' + table + '.' + task;
return maintenanceProperties.getProperty(propKey, defaultValue);
}
public Properties getMaintenanceProperties() {
return maintenanceProperties;
}
public void setMaintenanceProperties(Properties maintenanceProperties) {
this.maintenanceProperties = maintenanceProperties;
}
public String getCompressTableCronExpression() {
return compressTableCronExpression;
}
public void setCompressTableCronExpression(String compressTableCronExpression) {
this.compressTableCronExpression = compressTableCronExpression;
}
public JdbcOperations getJdbcOperations() {
return jdbcOperations;
}
public void setJdbcOperations(JdbcOperations jdbcOperations) {
this.jdbcOperations = jdbcOperations;
}
}