/**
* Licensed to JumpMind Inc under one or more contributor
* license agreements. See the NOTICE file distributed
* with this work for additional information regarding
* copyright ownership. JumpMind Inc licenses this file
* to you under the GNU General Public License, version 3.0 (GPLv3)
* (the "License"); you may not use this file except in compliance
* with the License.
*
* You should have received a copy of the GNU General Public License,
* version 3.0 (GPLv3) along with this library; if not, see
* <http://www.gnu.org/licenses/>.
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.jumpmind.symmetric.load;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.commons.lang.StringUtils;
import org.jumpmind.db.model.Table;
import org.jumpmind.extension.IBuiltInExtensionPoint;
import org.jumpmind.symmetric.ISymmetricEngine;
import org.jumpmind.symmetric.common.Constants;
import org.jumpmind.symmetric.common.ParameterConstants;
import org.jumpmind.symmetric.common.TableConstants;
import org.jumpmind.symmetric.io.data.CsvData;
import org.jumpmind.symmetric.io.data.DataContext;
import org.jumpmind.symmetric.io.data.DataEventType;
import org.jumpmind.symmetric.io.data.writer.DatabaseWriterFilterAdapter;
import org.jumpmind.symmetric.job.IJobManager;
import org.jumpmind.symmetric.model.IncomingBatch;
import org.jumpmind.symmetric.model.NodeSecurity;
import org.jumpmind.symmetric.service.INodeService;
import org.jumpmind.symmetric.service.IParameterService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* An out of the box filter that checks to see if the SymmetricDS configuration
* has changed. If it has, it will take the correct action to apply the
* configuration change to the current node.
*/
public class ConfigurationChangedDatabaseWriterFilter extends DatabaseWriterFilterAdapter implements
IBuiltInExtensionPoint, ILoadSyncLifecycleListener {
static final Logger log = LoggerFactory.getLogger(ConfigurationChangedDatabaseWriterFilter.class);
final String CTX_KEY_RESYNC_NEEDED = "Resync."
+ ConfigurationChangedDatabaseWriterFilter.class.getSimpleName() + hashCode();
final String CTX_KEY_FLUSH_GROUPLETS_NEEDED = "FlushGrouplets."
+ ConfigurationChangedDatabaseWriterFilter.class.getSimpleName() + hashCode();
final String CTX_KEY_FLUSH_LOADFILTERS_NEEDED = "FlushLoadFilters."
+ ConfigurationChangedDatabaseWriterFilter.class.getSimpleName() + hashCode();
final String CTX_KEY_RESYNC_TABLE_NEEDED = "Resync.Table"
+ ConfigurationChangedDatabaseWriterFilter.class.getSimpleName() + hashCode();
final String CTX_KEY_FLUSH_CHANNELS_NEEDED = "FlushChannels."
+ ConfigurationChangedDatabaseWriterFilter.class.getSimpleName() + hashCode();
final String CTX_KEY_FLUSH_TRANSFORMS_NEEDED = "FlushTransforms."
+ ConfigurationChangedDatabaseWriterFilter.class.getSimpleName() + hashCode();
final String CTX_KEY_FLUSH_PARAMETERS_NEEDED = "FlushParameters."
+ ConfigurationChangedDatabaseWriterFilter.class.getSimpleName() + hashCode();
final String CTX_KEY_FLUSH_CONFLICTS_NEEDED = "FlushConflicts."
+ ConfigurationChangedDatabaseWriterFilter.class.getSimpleName() + hashCode();
final String CTX_KEY_RESTART_JOBMANAGER_NEEDED = "RestartJobManager."
+ ConfigurationChangedDatabaseWriterFilter.class.getSimpleName() + hashCode();
final String CTX_KEY_REINITIALIZED = "Reinitialized."
+ ConfigurationChangedDatabaseWriterFilter.class.getSimpleName() + hashCode();
private ISymmetricEngine engine;
public ConfigurationChangedDatabaseWriterFilter(ISymmetricEngine engine) {
this.engine = engine;
}
@Override
public boolean beforeWrite(DataContext context, Table table, CsvData data) {
IParameterService parameterService = engine.getParameterService();
if (context.getBatch().getBatchId() == Constants.VIRTUAL_BATCH_FOR_REGISTRATION) {
if (parameterService.is(ParameterConstants.REGISTRATION_REINITIALIZE_ENABLED)
&& !Boolean.TRUE.equals(context.get(CTX_KEY_REINITIALIZED))) {
log.info("Reinitializing the database because the {} parameter was set to true",
ParameterConstants.REGISTRATION_REINITIALIZE_ENABLED);
engine.uninstall();
engine.setupDatabase(true);
engine.start();
context.put(CTX_KEY_REINITIALIZED, Boolean.TRUE);
}
}
return true;
}
@Override
public void afterWrite(DataContext context, Table table, CsvData data) {
recordSyncNeeded(context, table, data);
recordGroupletFlushNeeded(context, table);
recordLoadFilterFlushNeeded(context, table);
recordChannelFlushNeeded(context, table);
recordTransformFlushNeeded(context, table);
recordParametersFlushNeeded(context, table);
recordJobManagerRestartNeeded(context, table, data);
recordConflictFlushNeeded(context, table);
}
private void recordGroupletFlushNeeded(DataContext context, Table table) {
if (isGroupletFlushNeeded(table)) {
context.put(CTX_KEY_FLUSH_GROUPLETS_NEEDED, true);
}
}
private void recordLoadFilterFlushNeeded(DataContext context, Table table) {
if (isLoadFilterFlushNeeded(table)) {
context.put(CTX_KEY_FLUSH_LOADFILTERS_NEEDED, true);
}
}
private void recordSyncNeeded(DataContext context, Table table, CsvData data) {
if (isSyncTriggersNeeded(context, table)) {
context.put(CTX_KEY_RESYNC_NEEDED, true);
}
if (data.getDataEventType() == DataEventType.CREATE) {
@SuppressWarnings("unchecked")
Set<Table> tables = (Set<Table>)context.get(CTX_KEY_RESYNC_TABLE_NEEDED);
if (tables == null) {
tables = new HashSet<Table>();
context.put(CTX_KEY_RESYNC_TABLE_NEEDED, tables);
}
tables.add(table);
}
if (data.getDataEventType() == DataEventType.UPDATE &&
!engine.getParameterService().is(ParameterConstants.TRIGGER_CREATE_BEFORE_INITIAL_LOAD)) {
if (matchesTable(table, TableConstants.SYM_NODE_SECURITY)) {
Map<String,String> newData = data.toColumnNameValuePairs(table.getColumnNames(), CsvData.ROW_DATA);
String initialLoadEnabled = newData.get("INITIAL_LOAD_ENABLED");
String initialLoadTime = newData.get("INITIAL_LOAD_TIME");
if (StringUtils.isNotBlank(initialLoadTime) && "0".equals(initialLoadEnabled)) {
log.info("Requesting syncTriggers because {} is false and sym_node_security changed to indicate that an initial load has completed",
ParameterConstants.TRIGGER_CREATE_BEFORE_INITIAL_LOAD);
context.put(CTX_KEY_RESYNC_NEEDED, true);
}
}
}
}
private void recordJobManagerRestartNeeded(DataContext context, Table table, CsvData data) {
if (isJobManagerRestartNeeded(table, data)) {
context.put(CTX_KEY_RESTART_JOBMANAGER_NEEDED, true);
}
}
private void recordConflictFlushNeeded(DataContext context, Table table) {
if (isConflictFlushNeeded(table)) {
context.put(CTX_KEY_FLUSH_CONFLICTS_NEEDED, true);
}
}
private void recordParametersFlushNeeded(DataContext context, Table table) {
if (isParameterFlushNeeded(table)) {
context.put(CTX_KEY_FLUSH_PARAMETERS_NEEDED, true);
}
}
private void recordChannelFlushNeeded(DataContext context, Table table) {
if (isChannelFlushNeeded(table)) {
context.put(CTX_KEY_FLUSH_CHANNELS_NEEDED, true);
}
}
private void recordTransformFlushNeeded(DataContext context, Table table) {
if (isTransformFlushNeeded(table)) {
context.put(CTX_KEY_FLUSH_TRANSFORMS_NEEDED, true);
}
}
private boolean isSyncTriggersNeeded(DataContext context, Table table) {
boolean autoSync = engine.getParameterService().is(ParameterConstants.AUTO_SYNC_TRIGGERS_AFTER_CONFIG_LOADED) ||
context.getBatch().getBatchId() == Constants.VIRTUAL_BATCH_FOR_REGISTRATION;
return autoSync && (matchesTable(table, TableConstants.SYM_TRIGGER)
|| matchesTable(table, TableConstants.SYM_ROUTER)
|| matchesTable(table, TableConstants.SYM_TRIGGER_ROUTER)
|| matchesTable(table, TableConstants.SYM_TRIGGER_ROUTER_GROUPLET)
|| matchesTable(table, TableConstants.SYM_GROUPLET_LINK)
|| matchesTable(table, TableConstants.SYM_NODE_GROUP_LINK));
}
private boolean isGroupletFlushNeeded(Table table) {
return matchesTable(table, TableConstants.SYM_GROUPLET_LINK) ||
matchesTable(table, TableConstants.SYM_TRIGGER_ROUTER_GROUPLET) ||
matchesTable(table, TableConstants.SYM_GROUPLET);
}
private boolean isLoadFilterFlushNeeded(Table table) {
return matchesTable(table, TableConstants.SYM_LOAD_FILTER);
}
private boolean isChannelFlushNeeded(Table table) {
return matchesTable(table, TableConstants.SYM_CHANNEL);
}
private boolean isConflictFlushNeeded(Table table) {
return matchesTable(table, TableConstants.SYM_CONFLICT);
}
private boolean isParameterFlushNeeded(Table table) {
return matchesTable(table, TableConstants.SYM_PARAMETER);
}
private boolean isJobManagerRestartNeeded(Table table, CsvData data) {
return matchesTable(table, TableConstants.SYM_PARAMETER)
&& data.getCsvData(CsvData.ROW_DATA) != null
&& data.getCsvData(CsvData.ROW_DATA).contains("job.");
}
private boolean isTransformFlushNeeded(Table table) {
return matchesTable(table, TableConstants.SYM_TRANSFORM_COLUMN)
|| matchesTable(table, TableConstants.SYM_TRANSFORM_TABLE);
}
private boolean matchesTable(Table table, String tableSuffix) {
if (table != null && table.getName() != null) {
return table.getName().equalsIgnoreCase(
TableConstants.getTableName(engine.getParameterService().getTablePrefix(),
tableSuffix));
} else {
return false;
}
}
public void syncStarted(DataContext context) {
}
public void syncEnded(DataContext context, List<IncomingBatch> batchesProcessed, Throwable ex) {
IParameterService parameterService = engine.getParameterService();
if (context.get(CTX_KEY_RESTART_JOBMANAGER_NEEDED) != null) {
IJobManager jobManager = engine.getJobManager();
if (jobManager != null) {
log.info("About to restart jobs because a new schedule came through the data loader");
jobManager.stopJobs();
jobManager.startJobs();
}
context.remove(CTX_KEY_RESTART_JOBMANAGER_NEEDED);
}
/**
* No need to sync triggers until the entire sync process has finished just in case there
* are multiple batches that contain configuration changes
*/
if (context.get(CTX_KEY_RESYNC_NEEDED) != null
&& parameterService.is(ParameterConstants.AUTO_SYNC_TRIGGERS)) {
log.info("About to syncTriggers because new configuration came through the data loader");
engine.getTriggerRouterService().syncTriggers();
context.remove(CTX_KEY_RESYNC_NEEDED);
}
}
@Override
public void batchCommitted(DataContext context) {
IParameterService parameterService = engine.getParameterService();
INodeService nodeService = engine.getNodeService();
if (context.getBatch().getBatchId() == Constants.VIRTUAL_BATCH_FOR_REGISTRATION) {
// mark registration as complete
String nodeId = nodeService.findIdentityNodeId();
if (nodeId != null) {
NodeSecurity security = nodeService.findNodeSecurity(nodeId);
if (security != null &&
(security.isRegistrationEnabled() || security.getRegistrationTime() == null)) {
engine.getRegistrationService().markNodeAsRegistered(nodeId);
}
}
}
if (context.get(CTX_KEY_FLUSH_GROUPLETS_NEEDED) != null) {
log.info("Grouplets flushed because new grouplet config came through the data loader");
engine.getGroupletService().clearCache();
context.remove(CTX_KEY_FLUSH_GROUPLETS_NEEDED);
}
if (context.get(CTX_KEY_FLUSH_LOADFILTERS_NEEDED) != null) {
log.info("Load filters flushed because new filter config came through the data loader");
engine.getLoadFilterService().clearCache();
context.remove(CTX_KEY_FLUSH_LOADFILTERS_NEEDED);
}
if (context.get(CTX_KEY_FLUSH_CHANNELS_NEEDED) != null) {
log.info("Channels flushed because new channels came through the data loader");
engine.getConfigurationService().clearCache();
context.remove(CTX_KEY_FLUSH_CHANNELS_NEEDED);
}
if (context.get(CTX_KEY_FLUSH_TRANSFORMS_NEEDED) != null) {
log.info("About to refresh the cache of transformation because new configuration came through the data loader");
engine.getTransformService().clearCache();
log.info("About to clear the staging area because new transform configuration came through the data loader");
engine.getStagingManager().clean(0);
context.remove(CTX_KEY_FLUSH_TRANSFORMS_NEEDED);
}
if (context.get(CTX_KEY_FLUSH_CONFLICTS_NEEDED) != null) {
log.info("About to refresh the cache of conflict settings because new configuration came through the data loader");
engine.getDataLoaderService().clearCache();
context.remove(CTX_KEY_FLUSH_CONFLICTS_NEEDED);
}
if (context.get(CTX_KEY_FLUSH_PARAMETERS_NEEDED) != null) {
log.info("About to refresh the cache of parameters because new configuration came through the data loader");
parameterService.rereadParameters();
context.remove(CTX_KEY_FLUSH_PARAMETERS_NEEDED);
}
if (context.get(CTX_KEY_RESYNC_TABLE_NEEDED) != null
&& parameterService.is(ParameterConstants.AUTO_SYNC_TRIGGERS)) {
@SuppressWarnings("unchecked")
Set<Table> tables = (Set<Table>)context.get(CTX_KEY_RESYNC_TABLE_NEEDED);
for (Table table : tables) {
engine.getTriggerRouterService().syncTriggers(table, false);
}
context.remove(CTX_KEY_RESYNC_TABLE_NEEDED);
}
}
}