org.jumpmind.symmetric.service.impl.TriggerRouterService.java Source code

Java tutorial

Introduction

Here is the source code for org.jumpmind.symmetric.service.impl.TriggerRouterService.java

Source

/**
 * 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.service.impl;

import static org.apache.commons.lang.StringUtils.isBlank;

import java.sql.Types;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;

import org.apache.commons.lang.StringUtils;
import org.jumpmind.db.model.Column;
import org.jumpmind.db.model.Database;
import org.jumpmind.db.model.Table;
import org.jumpmind.db.sql.ISqlRowMapper;
import org.jumpmind.db.sql.Row;
import org.jumpmind.properties.TypedProperties;
import org.jumpmind.symmetric.ISymmetricEngine;
import org.jumpmind.symmetric.Version;
import org.jumpmind.symmetric.common.Constants;
import org.jumpmind.symmetric.common.ParameterConstants;
import org.jumpmind.symmetric.common.TableConstants;
import org.jumpmind.symmetric.config.ITriggerCreationListener;
import org.jumpmind.symmetric.config.TriggerFailureListener;
import org.jumpmind.symmetric.io.data.DataEventType;
import org.jumpmind.symmetric.model.Channel;
import org.jumpmind.symmetric.model.NodeGroupLink;
import org.jumpmind.symmetric.model.NodeSecurity;
import org.jumpmind.symmetric.model.Router;
import org.jumpmind.symmetric.model.Trigger;
import org.jumpmind.symmetric.model.TriggerHistory;
import org.jumpmind.symmetric.model.TriggerReBuildReason;
import org.jumpmind.symmetric.model.TriggerRouter;
import org.jumpmind.symmetric.route.ConfigurationChangedDataRouter;
import org.jumpmind.symmetric.route.FileSyncDataRouter;
import org.jumpmind.symmetric.service.ClusterConstants;
import org.jumpmind.symmetric.service.IClusterService;
import org.jumpmind.symmetric.service.IConfigurationService;
import org.jumpmind.symmetric.service.IExtensionService;
import org.jumpmind.symmetric.service.IGroupletService;
import org.jumpmind.symmetric.service.INodeService;
import org.jumpmind.symmetric.service.ISequenceService;
import org.jumpmind.symmetric.service.ITriggerRouterService;
import org.jumpmind.symmetric.statistic.IStatisticManager;
import org.jumpmind.util.FormatUtils;
import org.jumpmind.util.KeyedCache;
import org.jumpmind.util.KeyedCache.IRefreshCache;

/**
 * @see ITriggerRouterService
 */
public class TriggerRouterService extends AbstractService implements ITriggerRouterService {

    private IClusterService clusterService;

    private IConfigurationService configurationService;

    private ISequenceService sequenceService;

    private IExtensionService extensionService;

    private Map<Boolean, KeyedCache<String, Router>> routersCache = new HashMap<Boolean, KeyedCache<String, Router>>();

    private Map<Boolean, KeyedCache<String, Trigger>> triggersCache = new HashMap<Boolean, KeyedCache<String, Trigger>>();;

    private Map<String, TriggerRoutersCache> triggerRouterCacheByNodeGroupId = new HashMap<String, TriggerRoutersCache>();

    private Map<Boolean, KeyedCache<Integer, TriggerRouter>> triggerRoutersCache = new HashMap<Boolean, KeyedCache<Integer, TriggerRouter>>();

    private long triggerRouterPerNodeCacheTime;

    private TriggerFailureListener failureListener = new TriggerFailureListener();

    private IStatisticManager statisticManager;

    private IGroupletService groupletService;

    private INodeService nodeService;

    private List<String> extraConfigTables = new ArrayList<String>();

    private Date lastUpdateTime;

    private Object cacheLock = new Object();

    /**
     * Cache the history for performance. History never changes and does not
     * grow big so this should be OK.
     */
    private HashMap<Integer, TriggerHistory> historyMap = new HashMap<Integer, TriggerHistory>();

    public TriggerRouterService(ISymmetricEngine engine) {
        super(engine.getParameterService(), engine.getSymmetricDialect());
        this.clusterService = engine.getClusterService();
        this.configurationService = engine.getConfigurationService();
        this.statisticManager = engine.getStatisticManager();
        this.groupletService = engine.getGroupletService();
        this.nodeService = engine.getNodeService();
        this.sequenceService = engine.getSequenceService();
        this.extensionService = engine.getExtensionService();
        engine.getExtensionService().addExtensionPoint(failureListener);
        setSqlMap(new TriggerRouterServiceSqlMap(symmetricDialect.getPlatform(), createSqlReplacementTokens()));
    }

    public boolean refreshFromDatabase() {
        Date date1 = sqlTemplate.queryForObject(getSql("selectMaxTriggerLastUpdateTime"), Date.class);
        Date date2 = sqlTemplate.queryForObject(getSql("selectMaxRouterLastUpdateTime"), Date.class);
        Date date3 = sqlTemplate.queryForObject(getSql("selectMaxTriggerRouterLastUpdateTime"), Date.class);
        Date date = maxDate(date1, date2, date3);

        if (date != null) {
            if (lastUpdateTime == null || lastUpdateTime.before(date)) {
                if (lastUpdateTime != null) {
                    log.info("Newer trigger router settings were detected");
                }
                lastUpdateTime = date;
                clearCache();
                return true;
            }
        }
        return false;
    }

    public List<Trigger> getTriggers(boolean substituteParameters) {
        return sqlTemplate.query("select " + getSql("selectTriggersColumnList", "selectTriggersSql"),
                new TriggerMapper(substituteParameters));
    }

    public boolean isTriggerBeingUsed(String triggerId) {
        return sqlTemplate.queryForInt(getSql("countTriggerRoutersByTriggerIdSql"), triggerId) > 0;
    }

    public boolean doesTriggerExist(String triggerId) {
        return sqlTemplate.queryForInt(getSql("countTriggerByTriggerIdSql"), triggerId) > 0;
    }

    public boolean doesTriggerExistForTable(String tableName) {
        return sqlTemplate.queryForInt(getSql("countTriggerByTableNameSql"), tableName) > 0;
    }

    public void deleteTrigger(Trigger trigger) {
        sqlTemplate.update(getSql("deleteTriggerSql"), (Object) trigger.getTriggerId());
    }

    public void dropTriggers() {
        List<TriggerHistory> activeHistories = getActiveTriggerHistories();
        for (TriggerHistory history : activeHistories) {
            if (!TableConstants.getTables(symmetricDialect.getTablePrefix())
                    .contains(history.getSourceTableName())) {
                dropTriggers(history, (StringBuilder) null);
            }
        }
    }

    public void dropTriggers(Set<String> tables) {
        List<TriggerHistory> activeHistories = null;
        for (String table : tables) {
            if (doesTriggerExistForTable(table)) {
                activeHistories = this.getActiveTriggerHistories(table);
                for (TriggerHistory history : activeHistories) {
                    dropTriggers(history, (StringBuilder) null);
                }
            }
        }
    }

    protected void deleteTriggerHistory(TriggerHistory history) {
        sqlTemplate.update(getSql("deleteTriggerHistorySql"), history.getTriggerHistoryId());
    }

    public void createTriggersOnChannelForTables(String channelId, String catalogName, String schemaName,
            List<String> tables, String lastUpdateBy) {
        createTriggersOnChannelForTablesWithReturn(channelId, catalogName, schemaName, tables, lastUpdateBy);
    }

    public List<Trigger> createTriggersOnChannelForTablesWithReturn(String channelId, String catalogName,
            String schemaName, List<String> tables, String lastUpdateBy) {

        List<Trigger> createdTriggers = new ArrayList<Trigger>();

        List<Trigger> existingTriggers = getTriggers(false);
        for (String table : tables) {
            Trigger trigger = new Trigger();
            trigger.setChannelId(channelId);
            trigger.setSourceCatalogName(catalogName);
            trigger.setSourceSchemaName(schemaName);
            trigger.setSourceTableName(table);
            String triggerId = table;
            if (table.length() > 50) {
                triggerId = table.substring(0, 13) + "_" + UUID.randomUUID().toString();
            }

            boolean uniqueNameCreated = false;
            int suffix = 0;
            while (!uniqueNameCreated) {
                String triggerIdPriorToCheck = triggerId;
                for (Trigger existingTrigger : existingTriggers) {
                    if (triggerId.equals(existingTrigger.getTriggerId())) {
                        String suffixString = "_" + suffix;
                        if (suffix == 0) {
                            triggerId = triggerId + suffixString;
                        } else {
                            triggerId = triggerId.substring(0, triggerId.length() - ("_" + (suffix - 1)).length())
                                    + suffixString;
                        }
                        suffix++;
                    }
                }

                if (triggerId.equals(triggerIdPriorToCheck)) {
                    uniqueNameCreated = true;
                }
            }

            trigger.setTriggerId(triggerId);
            trigger.setLastUpdateBy(lastUpdateBy);
            trigger.setLastUpdateTime(new Date());
            trigger.setCreateTime(new Date());
            saveTrigger(trigger);
            createdTriggers.add(trigger);
        }
        return createdTriggers;

    }

    public Collection<Trigger> findMatchingTriggers(List<Trigger> triggers, String catalog, String schema,
            String table) {
        Set<Trigger> matches = new HashSet<Trigger>();
        for (Trigger trigger : triggers) {
            boolean catalogMatches = trigger.isSourceCatalogNameWildCarded()
                    || (catalog == null && trigger.getSourceCatalogName() == null)
                    || (StringUtils.isBlank(trigger.getSourceCatalogName()) && StringUtils.isNotBlank(catalog)
                            && catalog.equals(platform.getDefaultCatalog()))
                    || (StringUtils.isNotBlank(catalog) && catalog.equals(trigger.getSourceCatalogName()));
            boolean schemaMatches = trigger.isSourceSchemaNameWildCarded()
                    || (schema == null && trigger.getSourceSchemaName() == null)
                    || (StringUtils.isBlank(trigger.getSourceSchemaName()) && StringUtils.isNotBlank(schema)
                            && schema.equals(platform.getDefaultSchema()))
                    || (StringUtils.isNotBlank(schema) && schema.equals(trigger.getSourceSchemaName()));
            boolean tableMatches = trigger.isSourceTableNameWildCarded()
                    || table.equalsIgnoreCase(trigger.getSourceTableName());
            if (catalogMatches && schemaMatches && tableMatches) {
                matches.add(trigger);
            }
        }
        return matches;
    }

    public void inactivateTriggerHistory(TriggerHistory history) {
        sqlTemplate.update(getSql("inactivateTriggerHistorySql"),
                new Object[] { history.getErrorMessage(), history.getTriggerHistoryId() },
                new int[] { Types.VARCHAR, Types.INTEGER });
    }

    public Map<Long, TriggerHistory> getHistoryRecords() {
        final Map<Long, TriggerHistory> retMap = new HashMap<Long, TriggerHistory>();
        sqlTemplate.query(getSql("allTriggerHistSql"), new TriggerHistoryMapper(retMap));
        return retMap;
    }

    protected boolean isTriggerNameInUse(List<TriggerHistory> activeTriggerHistories, String triggerId,
            String triggerName) {
        for (TriggerHistory triggerHistory : activeTriggerHistories) {
            if (!triggerHistory.getTriggerId().equals(triggerId)
                    && ((triggerHistory.getNameForDeleteTrigger() != null
                            && triggerHistory.getNameForDeleteTrigger().equals(triggerName))
                            || (triggerHistory.getNameForInsertTrigger() != null
                                    && triggerHistory.getNameForInsertTrigger().equals(triggerName))
                            || (triggerHistory.getNameForUpdateTrigger() != null
                                    && triggerHistory.getNameForUpdateTrigger().equals(triggerName)))) {
                return true;
            }
        }
        return false;
    }

    public TriggerHistory findTriggerHistory(String catalogName, String schemaName, String tableName) {
        List<TriggerHistory> list = findTriggerHistories(catalogName, schemaName, tableName);
        return list.size() > 0 ? list.get(0) : null;
    }

    public List<TriggerHistory> findTriggerHistories(String catalogName, String schemaName, String tableName) {
        List<TriggerHistory> listToReturn = new ArrayList<TriggerHistory>();
        List<TriggerHistory> triggerHistories = getActiveTriggerHistories();
        if (triggerHistories != null && triggerHistories.size() > 0) {
            for (TriggerHistory triggerHistory : triggerHistories) {
                boolean matches = true;
                if (StringUtils.isNotBlank(catalogName)) {
                    matches = catalogName.equals(triggerHistory.getSourceCatalogName());
                }

                if (matches && StringUtils.isNotBlank(schemaName)) {
                    matches = schemaName.equals(triggerHistory.getSourceSchemaName());
                }

                if (matches && StringUtils.isNotBlank(tableName)) {
                    boolean ignoreCase = parameterService.is(ParameterConstants.DB_METADATA_IGNORE_CASE)
                            && !FormatUtils.isMixedCase(tableName);
                    matches = ignoreCase ? triggerHistory.getSourceTableName().equalsIgnoreCase(tableName)
                            : triggerHistory.getSourceTableName().equals(tableName);
                }

                if (matches) {
                    listToReturn.add(triggerHistory);
                }

            }
        }
        return listToReturn;
    }

    public TriggerHistory getTriggerHistory(int histId) {
        TriggerHistory history = historyMap.get(histId);
        if (history == null && histId >= 0) {
            history = (TriggerHistory) sqlTemplate.queryForObject(getSql("triggerHistSql"),
                    new TriggerHistoryMapper(), histId);
            historyMap.put(histId, history);
        }
        return history;
    }

    public List<TriggerHistory> getActiveTriggerHistories(Trigger trigger) {
        List<TriggerHistory> active = getActiveTriggerHistories();
        List<TriggerHistory> list = new ArrayList<TriggerHistory>();
        for (TriggerHistory triggerHistory : active) {
            if (triggerHistory.getTriggerId().equals(trigger.getTriggerId())) {
                list.add(triggerHistory);
            }
        }
        return list;
    }

    public TriggerHistory getNewestTriggerHistoryForTrigger(String triggerId, String catalogName, String schemaName,
            String tableName) {
        List<TriggerHistory> triggerHistories = sqlTemplate.query(getSql("latestTriggerHistSql"),
                new TriggerHistoryMapper(), triggerId, tableName);
        for (TriggerHistory triggerHistory : triggerHistories) {
            if ((StringUtils.isBlank(catalogName) && StringUtils.isBlank(triggerHistory.getSourceCatalogName()))
                    || (StringUtils.isNotBlank(catalogName)
                            && catalogName.equals(triggerHistory.getSourceCatalogName()))) {
                if ((StringUtils.isBlank(schemaName) && StringUtils.isBlank(triggerHistory.getSourceSchemaName()))
                        || (StringUtils.isNotBlank(schemaName)
                                && schemaName.equals(triggerHistory.getSourceSchemaName()))) {
                    return triggerHistory;
                }
            }
        }
        return null;
    }

    @SuppressWarnings("unchecked")
    @Override
    public List<TriggerHistory> getActiveTriggerHistoriesFromCache() {
        return new ArrayList<TriggerHistory>(historyMap != null ? historyMap.values() : Collections.EMPTY_LIST);
    }

    /**
     * Get a list of trigger histories that are currently active
     */
    public List<TriggerHistory> getActiveTriggerHistories() {
        List<TriggerHistory> histories = sqlTemplate.query(getSql("allTriggerHistSql", "activeTriggerHistSql"),
                new TriggerHistoryMapper());
        for (TriggerHistory triggerHistory : histories) {
            historyMap.put(triggerHistory.getTriggerHistoryId(), triggerHistory);
        }
        return histories;
    }

    public List<TriggerHistory> getActiveTriggerHistories(String tableName) {
        return sqlTemplate.query(getSql("allTriggerHistSql", "triggerHistBySourceTableWhereSql"),
                new TriggerHistoryMapper(), tableName);
    }

    protected List<Trigger> buildTriggersForSymmetricTables(String version, String... tablesToExclude) {
        List<Trigger> triggers = new ArrayList<Trigger>();
        List<String> tables = new ArrayList<String>(
                TableConstants.getConfigTables(symmetricDialect.getTablePrefix()));

        if (extraConfigTables != null) {
            for (String extraTable : extraConfigTables) {
                tables.add(extraTable);
            }
        }

        List<Trigger> definedTriggers = getTriggers(false);
        for (Trigger trigger : definedTriggers) {
            if (tables.remove(trigger.getSourceTableName())) {
                logOnce(String.format(
                        "Not generating virtual triggers for %s because there is a user defined trigger already defined",
                        trigger.getSourceTableName()));
            }
        }

        if (tablesToExclude != null) {
            for (String tableToExclude : tablesToExclude) {
                String tablename = TableConstants.getTableName(tablePrefix, tableToExclude);
                if (!tables.remove(tablename)) {
                    if (!tables.remove(tablename.toUpperCase())) {
                        tables.remove(tablename.toLowerCase());
                    }
                }
            }
        }

        for (String tableName : tables) {
            Trigger trigger = buildTriggerForSymmetricTable(tableName);
            triggers.add(trigger);
        }
        return triggers;
    }

    protected Trigger buildTriggerForSymmetricTable(String tableName) {
        boolean syncChanges = !TableConstants.getTablesThatDoNotSync(tablePrefix).contains(tableName)
                && parameterService.is(ParameterConstants.AUTO_SYNC_CONFIGURATION);
        boolean syncOnIncoming = !configurationService.isMasterToMaster()
                && (parameterService.is(ParameterConstants.AUTO_SYNC_CONFIGURATION_ON_INCOMING, true) || tableName
                        .equals(TableConstants.getTableName(tablePrefix, TableConstants.SYM_TABLE_RELOAD_REQUEST)));
        Trigger trigger = new Trigger();
        trigger.setTriggerId(tableName);
        trigger.setSyncOnDelete(syncChanges);
        trigger.setSyncOnInsert(syncChanges);
        trigger.setSyncOnUpdate(syncChanges);
        trigger.setSyncOnIncomingBatch(syncOnIncoming);
        trigger.setSourceTableName(tableName);
        trigger.setUseCaptureOldData(false);
        if (TableConstants.getTableName(tablePrefix, TableConstants.SYM_NODE_HOST).equals(tableName)) {
            trigger.setChannelId(Constants.CHANNEL_HEARTBEAT);
        } else if (TableConstants.getTableName(tablePrefix, TableConstants.SYM_FILE_SNAPSHOT).equals(tableName)) {
            trigger.setChannelId(Constants.CHANNEL_DYNAMIC);
            trigger.setChannelExpression("$(curTriggerValue).$(curColumnPrefix)"
                    + platform.alterCaseToMatchDatabaseDefaultCase("channel_id"));
            trigger.setReloadChannelId(Constants.CHANNEL_FILESYNC_RELOAD);
            trigger.setUseCaptureOldData(true);
            trigger.setSyncOnIncomingBatch(false);
            boolean syncEnabled = parameterService.is(ParameterConstants.FILE_SYNC_ENABLE);
            trigger.setSyncOnInsert(syncEnabled);
            trigger.setSyncOnUpdate(syncEnabled); // Changed to false because of
                                                  // issues with the traffic
                                                  // file
            trigger.setSyncOnDelete(false);
        } else {
            trigger.setChannelId(Constants.CHANNEL_CONFIG);
        }

        if (!TableConstants.getTableName(tablePrefix, TableConstants.SYM_NODE_HOST).equals(tableName)
                && !TableConstants.getTableName(tablePrefix, TableConstants.SYM_NODE).equals(tableName)
                && !TableConstants.getTableName(tablePrefix, TableConstants.SYM_NODE_SECURITY).equals(tableName)
                && !TableConstants.getTableName(tablePrefix, TableConstants.SYM_TABLE_RELOAD_REQUEST)
                        .equals(tableName)) {
            trigger.setUseCaptureLobs(true);
        }
        // little trick to force the rebuild of SymmetricDS triggers every time
        // there is a new version of SymmetricDS
        trigger.setLastUpdateTime(new Date(Version.version().hashCode()));
        return trigger;
    }

    public List<TriggerRouter> buildTriggerRoutersForSymmetricTables(String version, NodeGroupLink nodeGroupLink,
            String... tablesToExclude) {
        int initialLoadOrder = 1;

        List<Trigger> triggers = buildTriggersForSymmetricTables(version, tablesToExclude);
        List<TriggerRouter> triggerRouters = new ArrayList<TriggerRouter>(triggers.size());

        for (int j = 0; j < triggers.size(); j++) {
            Trigger trigger = triggers.get(j);
            TriggerRouter triggerRouter = buildTriggerRoutersForSymmetricTables(version, trigger, nodeGroupLink);
            triggerRouter.setInitialLoadOrder(initialLoadOrder++);
            triggerRouters.add(triggerRouter);
        }
        return triggerRouters;
    }

    public String buildSymmetricTableRouterId(String triggerId, String sourceNodeGroupId,
            String targetNodeGroupId) {
        return replaceCharsToShortenName(
                String.format("%s_%s_2_%s", triggerId, sourceNodeGroupId, targetNodeGroupId));
    }

    protected TriggerRouter buildTriggerRoutersForSymmetricTables(String version, Trigger trigger,
            NodeGroupLink nodeGroupLink) {
        TriggerRouter triggerRouter = new TriggerRouter();
        triggerRouter.setTrigger(trigger);

        Router router = triggerRouter.getRouter();
        router.setRouterId(buildSymmetricTableRouterId(trigger.getTriggerId(), nodeGroupLink.getSourceNodeGroupId(),
                nodeGroupLink.getTargetNodeGroupId()));
        if (TableConstants.getTableName(tablePrefix, TableConstants.SYM_FILE_SNAPSHOT)
                .equals(trigger.getSourceTableName())) {
            router.setRouterType(FileSyncDataRouter.ROUTER_TYPE);
        } else {
            router.setRouterType(ConfigurationChangedDataRouter.ROUTER_TYPE);
        }
        router.setNodeGroupLink(nodeGroupLink);
        router.setLastUpdateTime(trigger.getLastUpdateTime());

        triggerRouter.setLastUpdateTime(trigger.getLastUpdateTime());

        return triggerRouter;
    }

    @Override
    public Set<TriggerRouter> getTriggerRouterForTableForCurrentNode(boolean substituteParameters,
            String catalogName, String schemaName, String tableName, boolean refreshCache) {
        return getTriggerRouterForTableForCurrentNode(substituteParameters, null, catalogName, schemaName,
                tableName, refreshCache);
    }

    public Set<TriggerRouter> getTriggerRouterForTableForCurrentNode(boolean substituteParameters,
            NodeGroupLink link, String catalogName, String schemaName, String tableName, boolean refreshCache) {
        TriggerRoutersCache cache = getTriggerRoutersCacheForCurrentNode(refreshCache);
        Collection<List<TriggerRouter>> triggerRouters = cache.triggerRoutersByTriggerId.values();
        HashSet<TriggerRouter> returnList = new HashSet<TriggerRouter>();
        for (List<TriggerRouter> list : triggerRouters) {
            for (TriggerRouter triggerRouter : list) {
                if (isMatch(link, triggerRouter)
                        && isMatch(catalogName, schemaName, tableName, triggerRouter.getTrigger())) {
                    returnList.add(triggerRouter);
                }
            }
        }
        return returnList;
    }

    protected boolean isMatch(NodeGroupLink link, TriggerRouter router) {
        if (link != null && router != null && router.getRouter() != null) {
            return link.getSourceNodeGroupId().equals(router.getRouter().getNodeGroupLink().getSourceNodeGroupId())
                    && link.getTargetNodeGroupId()
                            .equals(router.getRouter().getNodeGroupLink().getTargetNodeGroupId());
        } else {
            return true;
        }
    }

    protected boolean isMatch(String catalogName, String schemaName, String tableName, Trigger trigger) {
        if (!StringUtils.isBlank(tableName) && !tableName.equals(trigger.getSourceTableName())) {
            return false;
        } else if (StringUtils.isBlank(tableName) && !StringUtils.isBlank(trigger.getSourceTableName())) {
            return false;
        } else if (!StringUtils.isBlank(catalogName) && !catalogName.equals(trigger.getSourceCatalogName())) {
            return false;
        } else if (StringUtils.isBlank(catalogName) && !StringUtils.isBlank(trigger.getSourceCatalogName())) {
            return false;
        } else if (!StringUtils.isBlank(schemaName) && !schemaName.equals(trigger.getSourceSchemaName())) {
            return false;
        } else if (StringUtils.isBlank(schemaName) && !StringUtils.isBlank(trigger.getSourceSchemaName())) {
            return false;
        } else {
            return true;
        }
    }

    /**
     * Create a list of {@link TriggerRouter} for the SymmetricDS tables that
     * should have triggers created for them on the current node.
     */
    protected List<TriggerRouter> getConfigurationTablesTriggerRoutersForCurrentNode(String sourceNodeGroupId) {
        List<TriggerRouter> triggerRouters = new ArrayList<TriggerRouter>();
        List<NodeGroupLink> links = configurationService.getNodeGroupLinksFor(sourceNodeGroupId, false);
        for (NodeGroupLink nodeGroupLink : links) {
            triggerRouters.addAll(buildTriggerRoutersForSymmetricTables(Version.version(), nodeGroupLink));
        }
        return triggerRouters;
    }

    protected void mergeInConfigurationTablesTriggerRoutersForCurrentNode(String sourceNodeGroupId,
            List<TriggerRouter> configuredInDatabase) {
        List<TriggerRouter> virtualConfigTriggers = getConfigurationTablesTriggerRoutersForCurrentNode(
                sourceNodeGroupId);
        for (TriggerRouter trigger : virtualConfigTriggers) {
            if (trigger.getRouter().getNodeGroupLink().getSourceNodeGroupId().equalsIgnoreCase(sourceNodeGroupId)
                    && !doesTriggerRouterExistInList(configuredInDatabase, trigger)) {
                configuredInDatabase.add(trigger);
            }
        }
    }

    protected boolean doesTriggerRouterExistInList(List<TriggerRouter> triggerRouters,
            TriggerRouter triggerRouter) {
        for (TriggerRouter checkMe : triggerRouters) {
            if (checkMe.isSame(triggerRouter)) {
                return true;
            }
        }
        return false;
    }

    public TriggerRouter getTriggerRouterForCurrentNode(String triggerId, String routerId, boolean refreshCache) {
        TriggerRouter triggerRouter = null;
        List<TriggerRouter> triggerRouters = getTriggerRoutersForCurrentNode(refreshCache).get(triggerId);
        if (triggerRouters != null) {
            for (TriggerRouter testTriggerRouter : triggerRouters) {
                if (ConfigurationChangedDataRouter.ROUTER_TYPE.equals(testTriggerRouter.getRouter().getRouterType())
                        || testTriggerRouter.getRouter().getRouterId().equals(routerId)
                        || routerId.equals(Constants.UNKNOWN_ROUTER_ID)) {
                    triggerRouter = testTriggerRouter;
                    break;
                }
            }
        }

        if (triggerRouter == null) {
            log.warn("Could not find trigger router [{}:{}] in list {}",
                    new Object[] { triggerId, routerId, triggerRouters == null ? 0 : triggerRouters.toString() });
        }

        return triggerRouter;
    }

    public Map<String, List<TriggerRouter>> getTriggerRoutersForCurrentNode(boolean refreshCache) {
        return getTriggerRoutersCacheForCurrentNode(refreshCache).triggerRoutersByTriggerId;
    }

    public List<Trigger> getTriggersForCurrentNode(boolean refreshCache) {
        Map<String, List<TriggerRouter>> triggerRouters = getTriggerRoutersForCurrentNode(refreshCache);
        List<Trigger> triggers = new ArrayList<Trigger>(triggerRouters.size());
        for (List<TriggerRouter> list : triggerRouters.values()) {
            if (list.size() > 0) {
                triggers.add(list.get(0).getTrigger());
            }
        }
        return triggers;
    }

    protected TriggerRoutersCache getTriggerRoutersCacheForCurrentNode(boolean refreshCache) {
        String myNodeGroupId = parameterService.getNodeGroupId();
        long triggerRouterCacheTimeoutInMs = parameterService
                .getLong(ParameterConstants.CACHE_TIMEOUT_TRIGGER_ROUTER_IN_MS);
        TriggerRoutersCache cache = triggerRouterCacheByNodeGroupId == null ? null
                : triggerRouterCacheByNodeGroupId.get(myNodeGroupId);
        if (cache == null || refreshCache || System.currentTimeMillis()
                - this.triggerRouterPerNodeCacheTime > triggerRouterCacheTimeoutInMs) {
            synchronized (cacheLock) {
                this.triggerRouterPerNodeCacheTime = System.currentTimeMillis();
                Map<String, TriggerRoutersCache> newTriggerRouterCacheByNodeGroupId = new HashMap<String, TriggerRoutersCache>();
                List<TriggerRouter> triggerRouters = getAllTriggerRoutersForCurrentNode(myNodeGroupId);
                Map<String, List<TriggerRouter>> triggerRoutersByTriggerId = new HashMap<String, List<TriggerRouter>>(
                        triggerRouters.size());
                Map<String, Router> routers = new HashMap<String, Router>(triggerRouters.size());
                for (TriggerRouter triggerRouter : triggerRouters) {
                    if (triggerRouter.isEnabled()) {
                        boolean sourceEnabled = groupletService.isSourceEnabled(triggerRouter);
                        if (sourceEnabled) {
                            String triggerId = triggerRouter.getTrigger().getTriggerId();
                            List<TriggerRouter> list = triggerRoutersByTriggerId.get(triggerId);
                            if (list == null) {
                                list = new ArrayList<TriggerRouter>();
                                triggerRoutersByTriggerId.put(triggerId, list);
                            }
                            list.add(triggerRouter);
                            routers.put(triggerRouter.getRouter().getRouterId(), triggerRouter.getRouter());
                        }
                    }
                }

                newTriggerRouterCacheByNodeGroupId.put(myNodeGroupId,
                        new TriggerRoutersCache(triggerRoutersByTriggerId, routers));
                this.triggerRouterCacheByNodeGroupId = newTriggerRouterCacheByNodeGroupId;
                cache = triggerRouterCacheByNodeGroupId == null ? null
                        : triggerRouterCacheByNodeGroupId.get(myNodeGroupId);
            }
        }
        return cache;
    }

    /**
     * @see ITriggerRouterService#getActiveRouterByIdForCurrentNode(String,
     *      boolean)
     */
    public Router getActiveRouterByIdForCurrentNode(String routerId, boolean refreshCache) {
        return getTriggerRoutersCacheForCurrentNode(refreshCache).routersByRouterId.get(routerId);
    }

    /**
     * @see ITriggerRouterService#getRoutersByGroupLink(NodeGroupLink)
     */
    public List<Router> getRoutersByGroupLink(NodeGroupLink link) {
        return sqlTemplate.query(getSql("select", "selectRoutersColumnList", "selectRouterByNodeGroupLinkWhereSql"),
                new RouterMapper(true, configurationService.getNodeGroupLinks(false)), link.getSourceNodeGroupId(),
                link.getTargetNodeGroupId());
    }

    public Trigger getTriggerById(boolean substituteParameters, String triggerId) {
        return getTriggerById(substituteParameters, triggerId, true);
    }

    public Trigger getTriggerById(final boolean substituteParameters, String triggerId, boolean refreshCache) {
        Trigger trigger = null;
        KeyedCache<String, Trigger> cache = triggersCache.get(substituteParameters);
        if (cache == null) {
            synchronized (cacheLock) {
                if (cache == null) {
                    cache = new KeyedCache<String, Trigger>(
                            parameterService.getLong(ParameterConstants.CACHE_TIMEOUT_TRIGGER_ROUTER_IN_MS),
                            new IRefreshCache<String, Trigger>() {
                                @Override
                                public LinkedHashMap<String, Trigger> refresh() {
                                    List<Trigger> triggers = new ArrayList<Trigger>(
                                            getTriggers(substituteParameters));
                                    triggers.addAll(buildTriggersForSymmetricTables(Version.version()));
                                    LinkedHashMap<String, Trigger> cache = new LinkedHashMap<String, Trigger>(
                                            triggers.size());
                                    for (Trigger t : triggers) {
                                        cache.put(t.getTriggerId(), t);
                                    }
                                    return cache;
                                }
                            });
                    triggersCache.put(substituteParameters, cache);
                }
            }
        }
        trigger = cache.find(triggerId, refreshCache);
        if (trigger == null && !refreshCache) {
            trigger = cache.find(triggerId, true);
        }
        return trigger;
    }

    public Router getRouterById(boolean substituteParameters, String routerId) {
        return getRouterById(substituteParameters, routerId, true);
    }

    public Router getRouterById(final boolean substituteParameters, String routerId, boolean refreshCache) {
        Router router = null;
        KeyedCache<String, Router> cache = routersCache.get(substituteParameters);
        if (cache == null) {
            synchronized (cacheLock) {
                if (cache == null) {
                    cache = new KeyedCache<String, Router>(
                            parameterService.getLong(ParameterConstants.CACHE_TIMEOUT_TRIGGER_ROUTER_IN_MS),
                            new IRefreshCache<String, Router>() {
                                @Override
                                public LinkedHashMap<String, Router> refresh() {
                                    List<Router> routers = getRouters(substituteParameters);
                                    LinkedHashMap<String, Router> cache = new LinkedHashMap<String, Router>(
                                            routers.size());
                                    for (Router router : routers) {
                                        cache.put(router.getRouterId(), router);
                                    }
                                    return cache;
                                }
                            });
                    routersCache.put(substituteParameters, cache);
                }
            }
        }
        router = cache.find(routerId, refreshCache);
        if (router == null && !refreshCache) {
            router = cache.find(routerId, true);
        }
        return router;
    }

    public List<Router> getRouters(boolean substituteParameters) {
        return sqlTemplate.query(getSql("select ", "selectRoutersColumnList", "selectRoutersSql"),
                new RouterMapper(substituteParameters, configurationService.getNodeGroupLinks(false)));
    }

    private String getTriggerRouterSql(String sql) {
        return getSql("select ", "selectTriggerRoutersColumnList", "selectTriggerRoutersSql", sql);
    }

    public List<TriggerRouter> getTriggerRouters(final boolean substituteParameters, boolean refreshCache) {
        KeyedCache<Integer, TriggerRouter> cache = triggerRoutersCache.get(substituteParameters);
        if (cache == null) {
            synchronized (cacheLock) {
                if (cache == null) {
                    cache = new KeyedCache<Integer, TriggerRouter>(
                            parameterService.getLong(ParameterConstants.CACHE_TIMEOUT_TRIGGER_ROUTER_IN_MS),
                            new IRefreshCache<Integer, TriggerRouter>() {
                                @Override
                                public LinkedHashMap<Integer, TriggerRouter> refresh() {
                                    List<TriggerRouter> triggerRouters = enhanceTriggerRouters(substituteParameters,
                                            sqlTemplate.query(getTriggerRouterSql(null),
                                                    new TriggerRouterMapper()));
                                    LinkedHashMap<Integer, TriggerRouter> cache = new LinkedHashMap<Integer, TriggerRouter>(
                                            triggerRouters.size());
                                    for (TriggerRouter triggerRouter : triggerRouters) {
                                        cache.put(triggerRouter.hashCode(), triggerRouter);
                                    }
                                    return cache;
                                }
                            });
                    triggerRoutersCache.put(substituteParameters, cache);
                }
            }
        }
        return cache.getAll(refreshCache);
    }

    public List<TriggerRouter> getAllTriggerRoutersForCurrentNode(String sourceNodeGroupId) {
        List<TriggerRouter> triggerRouters = enhanceTriggerRouters(true,
                sqlTemplate.query(getTriggerRouterSql("activeTriggersForSourceNodeGroupSql"),
                        new TriggerRouterMapper(), sourceNodeGroupId));
        mergeInConfigurationTablesTriggerRoutersForCurrentNode(sourceNodeGroupId, triggerRouters);
        return triggerRouters;
    }

    public List<TriggerRouter> getAllTriggerRoutersForReloadForCurrentNode(String sourceNodeGroupId,
            String targetNodeGroupId) {
        return enhanceTriggerRouters(true, sqlTemplate.query(getTriggerRouterSql("activeTriggersForReloadSql"),
                new TriggerRouterMapper(), sourceNodeGroupId, targetNodeGroupId, Constants.CHANNEL_CONFIG));
    }

    public TriggerRouter findTriggerRouterById(boolean substituteParameters, String triggerId, String routerId) {
        List<TriggerRouter> configs = (List<TriggerRouter>) sqlTemplate.query(
                getTriggerRouterSql("selectTriggerRouterSql"), new TriggerRouterMapper(), triggerId, routerId);
        if (configs.size() > 0) {
            TriggerRouter triggerRouter = configs.get(0);
            triggerRouter.setRouter(getRouterById(substituteParameters, triggerRouter.getRouter().getRouterId()));
            triggerRouter
                    .setTrigger(getTriggerById(substituteParameters, triggerRouter.getTrigger().getTriggerId()));
            return triggerRouter;
        } else {
            return null;
        }
    }

    private List<TriggerRouter> enhanceTriggerRouters(boolean substituteParameters,
            List<TriggerRouter> triggerRouters) {
        HashMap<String, Router> routersById = new HashMap<String, Router>();
        for (Router router : getRouters(substituteParameters)) {
            routersById.put(router.getRouterId().trim().toUpperCase(), router);
        }
        HashMap<String, Trigger> triggersById = new HashMap<String, Trigger>();
        for (Trigger trigger : getTriggers(substituteParameters)) {
            triggersById.put(trigger.getTriggerId().trim().toUpperCase(), trigger);
        }
        for (TriggerRouter triggerRouter : triggerRouters) {
            triggerRouter
                    .setTrigger(triggersById.get(triggerRouter.getTrigger().getTriggerId().trim().toUpperCase()));
            triggerRouter.setRouter(routersById.get(triggerRouter.getRouter().getRouterId().trim().toUpperCase()));
        }
        return triggerRouters;
    }

    public void insert(TriggerHistory newHistRecord) {
        newHistRecord.setTriggerHistoryId((int) sequenceService.nextVal(Constants.SEQUENCE_TRIGGER_HIST));
        historyMap.put(newHistRecord.getTriggerHistoryId(), newHistRecord);
        sqlTemplate.update(getSql("insertTriggerHistorySql"),
                new Object[] { newHistRecord.getTriggerHistoryId(), newHistRecord.getTriggerId(),
                        newHistRecord.getSourceTableName(), newHistRecord.getTableHash(),
                        newHistRecord.getCreateTime(), newHistRecord.getColumnNames(),
                        newHistRecord.getPkColumnNames(), newHistRecord.getLastTriggerBuildReason().getCode(),
                        newHistRecord.getNameForDeleteTrigger(), newHistRecord.getNameForInsertTrigger(),
                        newHistRecord.getNameForUpdateTrigger(), newHistRecord.getSourceSchemaName(),
                        newHistRecord.getSourceCatalogName(), newHistRecord.getTriggerRowHash(),
                        newHistRecord.getTriggerTemplateHash(), newHistRecord.getErrorMessage() },
                new int[] { Types.INTEGER, Types.VARCHAR, Types.VARCHAR, Types.BIGINT, Types.TIMESTAMP,
                        Types.VARCHAR, Types.VARCHAR, Types.CHAR, Types.VARCHAR, Types.VARCHAR, Types.VARCHAR,
                        Types.VARCHAR, Types.VARCHAR, Types.BIGINT, Types.BIGINT, Types.VARCHAR });
    }

    @Override
    public void deleteTriggerRouter(String triggerId, String routerId) {
        sqlTemplate.update(getSql("deleteTriggerRouterSql"), triggerId, routerId);
        clearCache();
    }

    public void deleteTriggerRouter(TriggerRouter triggerRouter) {
        sqlTemplate.update(getSql("deleteTriggerRouterSql"), (Object) triggerRouter.getTrigger().getTriggerId(),
                triggerRouter.getRouter().getRouterId());
        clearCache();
    }

    public void saveTriggerRouter(TriggerRouter triggerRouter) {
        saveTriggerRouter(triggerRouter, false);
    }

    public void saveTriggerRouter(TriggerRouter triggerRouter, boolean updateTriggerRouterTableOnly) {
        if (!updateTriggerRouterTableOnly) {
            saveTrigger(triggerRouter.getTrigger());
            saveRouter(triggerRouter.getRouter());
        }
        triggerRouter.setLastUpdateTime(new Date());
        if (0 == sqlTemplate.update(getSql("updateTriggerRouterSql"),
                new Object[] { triggerRouter.getInitialLoadOrder(), triggerRouter.getInitialLoadSelect(),
                        triggerRouter.getInitialLoadDeleteStmt(), triggerRouter.getInitialLoadBatchCount(),
                        triggerRouter.isPingBackEnabled() ? 1 : 0, triggerRouter.getLastUpdateBy(),
                        triggerRouter.getLastUpdateTime(), triggerRouter.isEnabled() ? 1 : 0,
                        triggerRouter.getTrigger().getTriggerId(), triggerRouter.getRouter().getRouterId() },
                new int[] { Types.NUMERIC, Types.VARCHAR, Types.VARCHAR, Types.INTEGER, Types.SMALLINT,
                        Types.VARCHAR, Types.TIMESTAMP, Types.SMALLINT, Types.VARCHAR, Types.VARCHAR })) {
            triggerRouter.setCreateTime(triggerRouter.getLastUpdateTime());
            sqlTemplate.update(getSql("insertTriggerRouterSql"),
                    new Object[] { triggerRouter.getInitialLoadOrder(), triggerRouter.getInitialLoadSelect(),
                            triggerRouter.getInitialLoadDeleteStmt(), triggerRouter.getInitialLoadBatchCount(),
                            triggerRouter.isPingBackEnabled() ? 1 : 0, triggerRouter.getCreateTime(),
                            triggerRouter.getLastUpdateBy(), triggerRouter.getLastUpdateTime(),
                            triggerRouter.isEnabled() ? 1 : 0, triggerRouter.getTrigger().getTriggerId(),
                            triggerRouter.getRouter().getRouterId() },
                    new int[] { Types.NUMERIC, Types.VARCHAR, Types.VARCHAR, Types.INTEGER, Types.SMALLINT,
                            Types.TIMESTAMP, Types.VARCHAR, Types.TIMESTAMP, Types.SMALLINT, Types.VARCHAR,
                            Types.VARCHAR });
        }

        clearCache();
    }

    protected void resetTriggerRouterCacheByNodeGroupId() {
        triggerRouterPerNodeCacheTime = 0;
    }

    public void saveRouter(Router router) {
        router.setLastUpdateTime(new Date());
        router.nullOutBlankFields();
        if (0 == sqlTemplate.update(getSql("updateRouterSql"),
                new Object[] { router.getTargetCatalogName(), router.getTargetSchemaName(),
                        router.getTargetTableName(), router.getNodeGroupLink().getSourceNodeGroupId(),
                        router.getNodeGroupLink().getTargetNodeGroupId(), router.getRouterType(),
                        router.getRouterExpression(), router.isSyncOnUpdate() ? 1 : 0,
                        router.isSyncOnInsert() ? 1 : 0, router.isSyncOnDelete() ? 1 : 0,
                        router.isUseSourceCatalogSchema() ? 1 : 0, router.getLastUpdateBy(),
                        router.getLastUpdateTime(), router.getRouterId() },
                new int[] { Types.VARCHAR, Types.VARCHAR, Types.VARCHAR, Types.VARCHAR, Types.VARCHAR,
                        Types.VARCHAR, Types.VARCHAR, Types.SMALLINT, Types.SMALLINT, Types.SMALLINT,
                        Types.SMALLINT, Types.VARCHAR, Types.TIMESTAMP, Types.VARCHAR })) {
            router.setCreateTime(router.getLastUpdateTime());
            sqlTemplate.update(getSql("insertRouterSql"),
                    new Object[] { router.getTargetCatalogName(), router.getTargetSchemaName(),
                            router.getTargetTableName(), router.getNodeGroupLink().getSourceNodeGroupId(),
                            router.getNodeGroupLink().getTargetNodeGroupId(), router.getRouterType(),
                            router.getRouterExpression(), router.isSyncOnUpdate() ? 1 : 0,
                            router.isSyncOnInsert() ? 1 : 0, router.isSyncOnDelete() ? 1 : 0,
                            router.isUseSourceCatalogSchema() ? 1 : 0, router.getCreateTime(),
                            router.getLastUpdateBy(), router.getLastUpdateTime(), router.getRouterId() },
                    new int[] { Types.VARCHAR, Types.VARCHAR, Types.VARCHAR, Types.VARCHAR, Types.VARCHAR,
                            Types.VARCHAR, Types.VARCHAR, Types.SMALLINT, Types.SMALLINT, Types.SMALLINT,
                            Types.SMALLINT, Types.TIMESTAMP, Types.VARCHAR, Types.TIMESTAMP, Types.VARCHAR });
        }
        clearCache();
    }

    public boolean isRouterBeingUsed(String routerId) {
        return sqlTemplate.queryForInt(getSql("countTriggerRoutersByRouterIdSql"), routerId) > 0;
    }

    public void deleteRouter(Router router) {
        if (router != null) {
            sqlTemplate.update(getSql("deleteRouterSql"), (Object) router.getRouterId());
        }
    }

    public void saveTrigger(Trigger trigger) {
        trigger.setLastUpdateTime(new Date());
        trigger.nullOutBlankFields();
        if (0 == sqlTemplate.update(getSql("updateTriggerSql"), new Object[] { trigger.getSourceCatalogName(),
                trigger.getSourceSchemaName(), trigger.getSourceTableName(), trigger.getChannelId(),
                trigger.getReloadChannelId(), trigger.isSyncOnUpdate() ? 1 : 0, trigger.isSyncOnInsert() ? 1 : 0,
                trigger.isSyncOnDelete() ? 1 : 0, trigger.isSyncOnIncomingBatch() ? 1 : 0,
                trigger.isUseStreamLobs() ? 1 : 0, trigger.isUseCaptureLobs() ? 1 : 0,
                trigger.isUseCaptureOldData() ? 1 : 0, trigger.isUseHandleKeyUpdates() ? 1 : 0,
                trigger.getNameForUpdateTrigger(), trigger.getNameForInsertTrigger(),
                trigger.getNameForDeleteTrigger(), trigger.getSyncOnUpdateCondition(),
                trigger.getSyncOnInsertCondition(), trigger.getSyncOnDeleteCondition(),
                trigger.getCustomOnUpdateText(), trigger.getCustomOnInsertText(), trigger.getCustomOnDeleteText(),
                trigger.getTxIdExpression(), trigger.getExcludedColumnNames(), trigger.getSyncKeyNames(),
                trigger.getLastUpdateBy(), trigger.getLastUpdateTime(), trigger.getExternalSelect(),
                trigger.getChannelExpression(), trigger.getTriggerId() },
                new int[] { Types.VARCHAR, Types.VARCHAR, Types.VARCHAR, Types.VARCHAR, Types.VARCHAR,
                        Types.SMALLINT, Types.SMALLINT, Types.SMALLINT, Types.SMALLINT, Types.SMALLINT,
                        Types.SMALLINT, Types.SMALLINT, Types.SMALLINT, Types.VARCHAR, Types.VARCHAR, Types.VARCHAR,
                        Types.VARCHAR, Types.VARCHAR, Types.VARCHAR, Types.VARCHAR, Types.VARCHAR, Types.VARCHAR,
                        Types.VARCHAR, Types.VARCHAR, Types.VARCHAR, Types.VARCHAR, Types.TIMESTAMP, Types.VARCHAR,
                        Types.VARCHAR, Types.VARCHAR })) {
            trigger.setCreateTime(trigger.getLastUpdateTime());
            sqlTemplate.update(getSql("insertTriggerSql"),
                    new Object[] { trigger.getSourceCatalogName(), trigger.getSourceSchemaName(),
                            trigger.getSourceTableName(), trigger.getChannelId(), trigger.getReloadChannelId(),
                            trigger.isSyncOnUpdate() ? 1 : 0, trigger.isSyncOnInsert() ? 1 : 0,
                            trigger.isSyncOnDelete() ? 1 : 0, trigger.isSyncOnIncomingBatch() ? 1 : 0,
                            trigger.isUseStreamLobs() ? 1 : 0, trigger.isUseCaptureLobs() ? 1 : 0,
                            trigger.isUseCaptureOldData() ? 1 : 0, trigger.isUseHandleKeyUpdates() ? 1 : 0,
                            trigger.getNameForUpdateTrigger(), trigger.getNameForInsertTrigger(),
                            trigger.getNameForDeleteTrigger(), trigger.getSyncOnUpdateCondition(),
                            trigger.getSyncOnInsertCondition(), trigger.getSyncOnDeleteCondition(),
                            trigger.getCustomOnUpdateText(), trigger.getCustomOnInsertText(),
                            trigger.getCustomOnDeleteText(), trigger.getTxIdExpression(),
                            trigger.getExcludedColumnNames(), trigger.getSyncKeyNames(), trigger.getCreateTime(),
                            trigger.getLastUpdateBy(), trigger.getLastUpdateTime(), trigger.getExternalSelect(),
                            trigger.getChannelExpression(), trigger.getTriggerId() },
                    new int[] { Types.VARCHAR, Types.VARCHAR, Types.VARCHAR, Types.VARCHAR, Types.VARCHAR,
                            Types.SMALLINT, Types.SMALLINT, Types.SMALLINT, Types.SMALLINT, Types.SMALLINT,
                            Types.SMALLINT, Types.SMALLINT, Types.SMALLINT, Types.VARCHAR, Types.VARCHAR,
                            Types.VARCHAR, Types.VARCHAR, Types.VARCHAR, Types.VARCHAR, Types.VARCHAR,
                            Types.VARCHAR, Types.VARCHAR, Types.VARCHAR, Types.VARCHAR, Types.VARCHAR,
                            Types.TIMESTAMP, Types.VARCHAR, Types.TIMESTAMP, Types.VARCHAR, Types.VARCHAR,
                            Types.VARCHAR });
        }

        clearCache();
    }

    public void syncTriggers() {
        syncTriggers(false);
    }

    public void syncTriggers(boolean force) {
        syncTriggers((StringBuilder) null, force);
    }

    public void syncTriggers(StringBuilder sqlBuffer, boolean force) {
        if ((parameterService.is(ParameterConstants.AUTO_SYNC_TRIGGERS) || isCalledFromSymmetricAdminTool())) {
            synchronized (this) {
                if (clusterService.lock(ClusterConstants.SYNCTRIGGERS)) {
                    try {
                        String additionalMessage = "";
                        if (isCalledFromSymmetricAdminTool()
                                && !parameterService.is(ParameterConstants.AUTO_SYNC_TRIGGERS)) {
                            additionalMessage = " " + ParameterConstants.AUTO_SYNC_TRIGGERS
                                    + " is set to false, but the sync triggers process will run so that needed changes can be written to a file so they can be applied manually.  Once all of the triggers have been successfully applied this process should not show triggers being created";
                        }

                        log.info("Synchronizing triggers{}", additionalMessage);

                        // make sure all tables are freshly read in
                        platform.resetCachedTableModel();

                        clearCache();

                        // make sure channels are read from the database
                        configurationService.clearCache();

                        List<Trigger> triggersForCurrentNode = getTriggersForCurrentNode(true);

                        boolean createTriggersForTables = false;
                        String nodeId = nodeService.findIdentityNodeId();
                        if (StringUtils.isNotBlank(nodeId)) {
                            NodeSecurity nodeSecurity = nodeService.findNodeSecurity(nodeId);
                            if (nodeSecurity != null && (nodeSecurity.isInitialLoadEnabled()
                                    || nodeSecurity.getInitialLoadTime() == null)) {
                                createTriggersForTables = parameterService
                                        .is(ParameterConstants.TRIGGER_CREATE_BEFORE_INITIAL_LOAD);
                                if (!createTriggersForTables) {
                                    log.info("Trigger creation has been disabled by "
                                            + ParameterConstants.TRIGGER_CREATE_BEFORE_INITIAL_LOAD
                                            + " because an initial load is in progress or has not yet been requested");
                                }
                            } else {
                                createTriggersForTables = true;
                            }
                        }

                        if (!createTriggersForTables) {
                            triggersForCurrentNode.clear();
                        }

                        List<TriggerHistory> activeTriggerHistories = getActiveTriggerHistories();
                        inactivateTriggers(triggersForCurrentNode, sqlBuffer, activeTriggerHistories);
                        updateOrCreateDatabaseTriggers(triggersForCurrentNode, sqlBuffer, force, true,
                                activeTriggerHistories, true);
                        resetTriggerRouterCacheByNodeGroupId();
                    } finally {
                        clusterService.unlock(ClusterConstants.SYNCTRIGGERS);
                        log.info("Done synchronizing triggers");
                    }
                } else {
                    log.info("Sync triggers was locked by the cluster service");
                }
            }
        } else {
            log.info("Not synchronizing triggers.  {} is set to false", ParameterConstants.AUTO_SYNC_TRIGGERS);
        }
    }

    public void clearCache() {
        synchronized (cacheLock) {
            this.triggerRouterPerNodeCacheTime = 0;
            List<KeyedCache<?, ?>> caches = new ArrayList<KeyedCache<?, ?>>();
            caches.addAll(triggerRoutersCache.values());
            caches.addAll(triggersCache.values());
            caches.addAll(routersCache.values());
            for (KeyedCache<?, ?> keyedCache : caches) {
                keyedCache.clear();
            }
        }
    }

    protected Set<String> getTriggerIdsFrom(List<Trigger> triggersThatShouldBeActive) {
        Set<String> triggerIds = new HashSet<String>(triggersThatShouldBeActive.size());
        for (Trigger trigger : triggersThatShouldBeActive) {
            triggerIds.add(trigger.getTriggerId());
        }
        return triggerIds;
    }

    protected Trigger getTriggerFromList(String triggerId, List<Trigger> triggersThatShouldBeActive) {
        for (Trigger trigger : triggersThatShouldBeActive) {
            if (trigger.getTriggerId().equals(triggerId)) {
                return trigger;
            }
        }
        return null;
    }

    protected void inactivateTriggers(List<Trigger> triggersThatShouldBeActive, StringBuilder sqlBuffer,
            List<TriggerHistory> activeTriggerHistories) {
        boolean ignoreCase = this.parameterService.is(ParameterConstants.DB_METADATA_IGNORE_CASE);
        Map<String, Set<Table>> tablesByTriggerId = new HashMap<String, Set<Table>>();
        for (TriggerHistory history : activeTriggerHistories) {
            boolean removeTrigger = false;
            Set<Table> tables = tablesByTriggerId.get(history.getTriggerId());
            Trigger trigger = getTriggerFromList(history.getTriggerId(), triggersThatShouldBeActive);
            if (tables == null && trigger != null) {
                tables = getTablesForTrigger(trigger, triggersThatShouldBeActive, false);
                tablesByTriggerId.put(trigger.getTriggerId(), tables);
            }

            if (tables == null || tables.size() == 0 || trigger == null) {
                removeTrigger = true;
            } else {
                boolean foundTable = false;

                for (Table table : tables) {
                    boolean matchesCatalog = isEqual(trigger.isSourceCatalogNameWildCarded() ? table.getCatalog()
                            : trigger.getSourceCatalogName(), history.getSourceCatalogName(), ignoreCase);
                    boolean matchesSchema = isEqual(trigger.isSourceSchemaNameWildCarded() ? table.getSchema()
                            : trigger.getSourceSchemaName(), history.getSourceSchemaName(), ignoreCase);
                    boolean matchesTable = isEqual(
                            trigger.isSourceTableNameWildCarded() ? table.getName() : trigger.getSourceTableName(),
                            history.getSourceTableName(), ignoreCase);
                    foundTable |= matchesCatalog && matchesSchema && matchesTable;
                }

                if (!foundTable) {
                    removeTrigger = true;
                }
            }

            if (removeTrigger) {
                log.info("About to remove triggers for inactivated table: {}",
                        history.getFullyQualifiedSourceTableName());
                dropTriggers(history, sqlBuffer);
            }
        }
    }

    protected boolean isEqual(String one, String two, boolean ignoreCase) {
        if (ignoreCase) {
            return StringUtils.equalsIgnoreCase(one, two);
        } else {
            return StringUtils.equals(one, two);
        }
    }

    public void dropTriggers(TriggerHistory history) {
        dropTriggers(history, null);
    }

    protected void dropTriggers(TriggerHistory history, StringBuilder sqlBuffer) {

        if (StringUtils.isNotBlank(history.getNameForInsertTrigger())) {
            symmetricDialect.removeTrigger(sqlBuffer, history.getSourceCatalogName(), history.getSourceSchemaName(),
                    history.getNameForInsertTrigger(), history.getSourceTableName());
        }

        if (StringUtils.isNotBlank(history.getNameForDeleteTrigger())) {
            symmetricDialect.removeTrigger(sqlBuffer, history.getSourceCatalogName(), history.getSourceSchemaName(),
                    history.getNameForDeleteTrigger(), history.getSourceTableName());
        }

        if (StringUtils.isNotBlank(history.getNameForUpdateTrigger())) {
            symmetricDialect.removeTrigger(sqlBuffer, history.getSourceCatalogName(), history.getSourceSchemaName(),
                    history.getNameForUpdateTrigger(), history.getSourceTableName());
        }

        if (parameterService.is(ParameterConstants.AUTO_SYNC_TRIGGERS)) {
            for (ITriggerCreationListener l : extensionService
                    .getExtensionPointList(ITriggerCreationListener.class)) {
                l.triggerInactivated(null, history);
            }
        }

        boolean triggerExists = symmetricDialect.doesTriggerExist(history.getSourceCatalogName(),
                history.getSourceSchemaName(), history.getSourceTableName(), history.getNameForInsertTrigger());
        triggerExists |= symmetricDialect.doesTriggerExist(history.getSourceCatalogName(),
                history.getSourceSchemaName(), history.getSourceTableName(), history.getNameForUpdateTrigger());
        triggerExists |= symmetricDialect.doesTriggerExist(history.getSourceCatalogName(),
                history.getSourceSchemaName(), history.getSourceTableName(), history.getNameForDeleteTrigger());
        if (triggerExists) {
            log.warn(
                    "There are triggers that have been marked as inactive.  Please remove triggers represented by trigger_id={} and trigger_hist_id={}",
                    history.getTriggerId(), history.getTriggerHistoryId());
        } else {
            inactivateTriggerHistory(history);
        }
    }

    protected List<TriggerRouter> toList(Collection<List<TriggerRouter>> source) {
        ArrayList<TriggerRouter> list = new ArrayList<TriggerRouter>();
        for (List<TriggerRouter> triggerRouters : source) {
            for (TriggerRouter triggerRouter : triggerRouters) {
                list.add(triggerRouter);
            }
        }
        return list;
    }

    protected Set<Table> getTablesForTrigger(Trigger trigger, List<Trigger> triggers, boolean useTableCache) {
        Set<Table> tables = new HashSet<Table>();
        try {
            boolean ignoreCase = this.parameterService.is(ParameterConstants.DB_METADATA_IGNORE_CASE);

            List<String> catalogNames = new ArrayList<String>();
            if (trigger.isSourceCatalogNameWildCarded()) {
                List<String> all = platform.getDdlReader().getCatalogNames();
                for (String catalogName : all) {
                    if (trigger.matchesCatalogName(catalogName, ignoreCase)) {
                        catalogNames.add(catalogName);
                    }
                }
                if (catalogNames.size() == 0) {
                    catalogNames.add(null);
                }
            } else {
                if (isBlank(trigger.getSourceCatalogName())) {
                    catalogNames.add(platform.getDefaultCatalog());
                } else {
                    catalogNames.add(trigger.getSourceCatalogName());
                }
            }

            for (String catalogName : catalogNames) {
                List<String> schemaNames = new ArrayList<String>();
                if (trigger.isSourceSchemaNameWildCarded()) {
                    List<String> all = platform.getDdlReader().getSchemaNames(catalogName);
                    for (String schemaName : all) {
                        if (trigger.matchesSchemaName(schemaName, ignoreCase)) {
                            schemaNames.add(schemaName);
                        }
                    }
                    if (schemaNames.size() == 0) {
                        schemaNames.add(null);
                    }
                } else {
                    if (isBlank(trigger.getSourceSchemaName())) {
                        schemaNames.add(platform.getDefaultSchema());
                    } else {
                        schemaNames.add(trigger.getSourceSchemaName());
                    }
                }

                for (String schemaName : schemaNames) {
                    if (trigger.isSourceTableNameWildCarded()) {
                        Database database = symmetricDialect.getPlatform().readDatabase(catalogName, schemaName,
                                new String[] { "TABLE" });
                        Table[] tableArray = database.getTables();

                        for (Table table : tableArray) {
                            if (trigger.matches(table, catalogName, schemaName, ignoreCase)
                                    && !containsExactMatchForSourceTableName(table, triggers, ignoreCase)
                                    && !table.getName().toLowerCase().startsWith(tablePrefix)) {
                                tables.add(table);
                            }
                        }
                    } else {
                        Table table = symmetricDialect.getPlatform().getTableFromCache(catalogName, schemaName,
                                trigger.getSourceTableName(), !useTableCache);
                        if (table != null) {
                            tables.add(table);
                        }
                    }
                }
            }

        } catch (Exception ex) {
            log.error(String.format("Failed to retrieve tables for trigger with id of %s", trigger.getTriggerId()),
                    ex);
        }
        return tables;
    }

    private boolean containsExactMatchForSourceTableName(Table table, List<Trigger> triggers, boolean ignoreCase) {
        for (Trigger trigger : triggers) {
            String sourceCatalogName = trigger.getSourceCatalogName() != null ? trigger.getSourceCatalogName()
                    : platform.getDefaultCatalog();
            String sourceSchemaName = trigger.getSourceSchemaName() != null ? trigger.getSourceSchemaName()
                    : platform.getDefaultSchema();
            if (trigger.getSourceTableName().equals(table.getName())
                    && (sourceCatalogName == null || sourceCatalogName.equals(table.getCatalog()))
                    && (sourceSchemaName == null || sourceSchemaName.equals(table.getSchema()))) {
                return true;
            } else if (ignoreCase && trigger.getSourceTableName().equalsIgnoreCase(table.getName())
                    && (sourceCatalogName == null || sourceCatalogName.equalsIgnoreCase(table.getCatalog()))
                    && (sourceSchemaName == null || sourceSchemaName.equalsIgnoreCase(table.getSchema()))) {
                return true;
            }
        }
        return false;
    }

    public void syncTriggers(Table table, boolean force) {
        boolean ignoreCase = this.parameterService.is(ParameterConstants.DB_METADATA_IGNORE_CASE);

        /* Re-lookup just in case the table was just altered */
        platform.resetCachedTableModel();
        table = platform.getTableFromCache(table.getCatalog(), table.getSchema(), table.getName(), true);
        List<Trigger> triggersForCurrentNode = getTriggersForCurrentNode(true);
        List<TriggerHistory> activeTriggerHistories = getActiveTriggerHistories();
        for (Trigger trigger : triggersForCurrentNode) {
            if (trigger.matches(table, platform.getDefaultCatalog(), platform.getDefaultSchema(), ignoreCase)) {
                log.info("Synchronizing triggers for {}", table.getFullyQualifiedTableName());
                updateOrCreateDatabaseTriggers(trigger, table, null, force, true, activeTriggerHistories);
                log.info("Done synchronizing triggers for {}", table.getFullyQualifiedTableName());
            }
        }
    }

    protected void updateOrCreateDatabaseTriggers(List<Trigger> triggers, StringBuilder sqlBuffer, boolean force,
            boolean verifyInDatabase, List<TriggerHistory> activeTriggerHistories, boolean useTableCache) {
        for (Trigger trigger : triggers) {
            updateOrCreateDatabaseTrigger(trigger, triggers, sqlBuffer, force, verifyInDatabase,
                    activeTriggerHistories, useTableCache);
        }
    }

    protected void updateOrCreateDatabaseTrigger(Trigger trigger, List<Trigger> triggers, StringBuilder sqlBuffer,
            boolean force, boolean verifyInDatabase, List<TriggerHistory> activeTriggerHistories,
            boolean useTableCache) {
        Set<Table> tables = getTablesForTrigger(trigger, triggers, useTableCache);

        if (tables != null && tables.size() > 0) {
            for (Table table : tables) {
                updateOrCreateDatabaseTriggers(trigger, table, sqlBuffer, force, verifyInDatabase,
                        activeTriggerHistories);
            }
        } else {
            log.warn("Could not find any database tables matching '{}' in the datasource that is configured",
                    trigger.qualifiedSourceTableName());

            for (ITriggerCreationListener l : extensionService
                    .getExtensionPointList(ITriggerCreationListener.class)) {
                l.tableDoesNotExist(trigger);
            }
        }
    }

    public void syncTrigger(String triggerId, ITriggerCreationListener listener) {
        clearCache();
        Trigger trigger = getTriggerById(true, triggerId, false);

        StringBuilder sqlBuffer = new StringBuilder();
        try {
            if (listener != null) {
                extensionService.addExtensionPoint(listener);
            }

            List<Trigger> listOfTriggers = getTriggersForCurrentNode(false);
            List<TriggerHistory> allHistories = getActiveTriggerHistories();
            for (TriggerHistory triggerHistory : allHistories) {
                if (triggerHistory.getTriggerId().equals(triggerId)) {
                    dropTriggers(triggerHistory, sqlBuffer);
                }
            }
            if (listOfTriggers.contains(trigger)) {
                updateOrCreateDatabaseTrigger(trigger, listOfTriggers, sqlBuffer, true, false, allHistories, false);
            }
        } finally {
            if (listener != null) {
                extensionService.removeExtensionPoint(listener);
            }
        }
    }

    protected void updateOrCreateDatabaseTriggers(Trigger trigger, Table table, StringBuilder sqlBuffer,
            boolean force, boolean verifyInDatabase, List<TriggerHistory> activeTriggerHistories) {
        TriggerHistory newestHistory = null;
        TriggerReBuildReason reason = TriggerReBuildReason.NEW_TRIGGERS;

        String errorMessage = null;

        if (verifyInDatabase) {
            Channel channel = configurationService.getChannel(trigger.getChannelId());
            if (channel == null) {
                errorMessage = String.format(
                        "Trigger %s had an unrecognized channel_id of '%s'.  Please check to make sure the channel exists.  Creating trigger on the '%s' channel",
                        trigger.getTriggerId(), trigger.getChannelId(), Constants.CHANNEL_DEFAULT);
                log.error(errorMessage);
                trigger.setChannelId(Constants.CHANNEL_DEFAULT);
            }
        }

        try {

            boolean foundPk = false;
            Column[] columns = trigger.filterExcludedColumns(table.getColumns());
            for (Column column : columns) {
                foundPk |= column.isPrimaryKey();
            }
            if (!foundPk) {
                table = platform.makeAllColumnsPrimaryKeys(table);
            }

            TriggerHistory latestHistoryBeforeRebuild = getNewestTriggerHistoryForTrigger(trigger.getTriggerId(),
                    trigger.isSourceCatalogNameWildCarded() ? table.getCatalog() : trigger.getSourceCatalogName(),
                    trigger.isSourceSchemaNameWildCarded() ? table.getSchema() : trigger.getSourceSchemaName(),
                    trigger.isSourceTableNameWildCarded() ? table.getName() : trigger.getSourceTableName());

            boolean forceRebuildOfTriggers = false;
            if (latestHistoryBeforeRebuild == null) {
                reason = TriggerReBuildReason.NEW_TRIGGERS;
                forceRebuildOfTriggers = true;

            } else if (table.calculateTableHashcode() != latestHistoryBeforeRebuild.getTableHash()) {
                reason = TriggerReBuildReason.TABLE_SCHEMA_CHANGED;
                forceRebuildOfTriggers = true;

            } else if (trigger.hasChangedSinceLastTriggerBuild(latestHistoryBeforeRebuild.getCreateTime())
                    || trigger.toHashedValue() != latestHistoryBeforeRebuild.getTriggerRowHash()) {
                reason = TriggerReBuildReason.TABLE_SYNC_CONFIGURATION_CHANGED;
                forceRebuildOfTriggers = true;
            } else if (symmetricDialect.getTriggerTemplate().toHashedValue() != latestHistoryBeforeRebuild
                    .getTriggerTemplateHash()) {
                reason = TriggerReBuildReason.TRIGGER_TEMPLATE_CHANGED;
                forceRebuildOfTriggers = true;
            } else if (force) {
                reason = TriggerReBuildReason.FORCED;
                forceRebuildOfTriggers = true;
            }

            boolean supportsTriggers = symmetricDialect.getPlatform().getDatabaseInfo().isTriggersSupported();

            newestHistory = rebuildTriggerIfNecessary(sqlBuffer, forceRebuildOfTriggers, trigger,
                    DataEventType.INSERT, reason, latestHistoryBeforeRebuild, null,
                    trigger.isSyncOnInsert() && supportsTriggers, table, activeTriggerHistories);

            newestHistory = rebuildTriggerIfNecessary(sqlBuffer, forceRebuildOfTriggers, trigger,
                    DataEventType.UPDATE, reason, latestHistoryBeforeRebuild, newestHistory,
                    trigger.isSyncOnUpdate() && supportsTriggers, table, activeTriggerHistories);

            newestHistory = rebuildTriggerIfNecessary(sqlBuffer, forceRebuildOfTriggers, trigger,
                    DataEventType.DELETE, reason, latestHistoryBeforeRebuild, newestHistory,
                    trigger.isSyncOnDelete() && supportsTriggers, table, activeTriggerHistories);

            if (latestHistoryBeforeRebuild != null && newestHistory != null) {
                inactivateTriggerHistory(latestHistoryBeforeRebuild);
            }

            if (newestHistory != null) {
                activeTriggerHistories.add(newestHistory);
                newestHistory.setErrorMessage(errorMessage);
                if (parameterService.is(ParameterConstants.AUTO_SYNC_TRIGGERS)) {
                    for (ITriggerCreationListener l : extensionService
                            .getExtensionPointList(ITriggerCreationListener.class)) {
                        l.triggerCreated(trigger, newestHistory);
                    }
                }
            }

        } catch (Exception ex) {
            log.error(String.format("Failed to create triggers for %s", trigger.qualifiedSourceTableName()), ex);

            if (newestHistory != null) {
                // Make sure all the triggers are removed from the
                // table
                symmetricDialect.removeTrigger(null, trigger.getSourceCatalogName(), trigger.getSourceSchemaName(),
                        newestHistory.getNameForInsertTrigger(), trigger.getSourceTableName());
                symmetricDialect.removeTrigger(null, trigger.getSourceCatalogName(), trigger.getSourceSchemaName(),
                        newestHistory.getNameForUpdateTrigger(), trigger.getSourceTableName());
                symmetricDialect.removeTrigger(null, trigger.getSourceCatalogName(), trigger.getSourceSchemaName(),
                        newestHistory.getNameForDeleteTrigger(), trigger.getSourceTableName());
            }

            for (ITriggerCreationListener l : extensionService
                    .getExtensionPointList(ITriggerCreationListener.class)) {
                l.triggerFailed(trigger, ex);
            }
        }
    }

    protected TriggerHistory rebuildTriggerIfNecessary(StringBuilder sqlBuffer, boolean forceRebuild,
            Trigger trigger, DataEventType dmlType, TriggerReBuildReason reason, TriggerHistory oldhist,
            TriggerHistory hist, boolean triggerIsActive, Table table,
            List<TriggerHistory> activeTriggerHistories) {

        boolean triggerExists = false;
        boolean triggerRemoved = false;

        TriggerHistory newTriggerHist = new TriggerHistory(table, trigger, symmetricDialect.getTriggerTemplate(),
                reason);
        int maxTriggerNameLength = symmetricDialect.getMaxTriggerNameLength();

        if (trigger.isSyncOnInsert()) {
            newTriggerHist.setNameForInsertTrigger(getTriggerName(DataEventType.INSERT, maxTriggerNameLength,
                    trigger, table, activeTriggerHistories).toUpperCase());
        }

        if (trigger.isSyncOnUpdate()) {
            newTriggerHist.setNameForUpdateTrigger(getTriggerName(DataEventType.UPDATE, maxTriggerNameLength,
                    trigger, table, activeTriggerHistories).toUpperCase());
        }

        if (trigger.isSyncOnDelete()) {
            newTriggerHist.setNameForDeleteTrigger(getTriggerName(DataEventType.DELETE, maxTriggerNameLength,
                    trigger, table, activeTriggerHistories).toUpperCase());
        }

        String oldTriggerName = null;
        String oldSourceSchema = null;
        String oldCatalogName = null;
        if (oldhist != null) {
            oldTriggerName = oldhist.getTriggerNameForDmlType(dmlType);
            oldSourceSchema = oldhist.getSourceSchemaName();
            oldCatalogName = oldhist.getSourceCatalogName();
            triggerExists = symmetricDialect.doesTriggerExist(oldCatalogName, oldSourceSchema,
                    oldhist.getSourceTableName(), oldTriggerName);
        } else {
            // We had no trigger_hist row, lets validate that the trigger as
            // defined in the trigger row data does not exist as well.
            oldTriggerName = newTriggerHist.getTriggerNameForDmlType(dmlType);
            oldSourceSchema = table.getSchema();
            oldCatalogName = table.getCatalog();
            if (StringUtils.isNotBlank(oldTriggerName)) {
                triggerExists = symmetricDialect.doesTriggerExist(oldCatalogName, oldSourceSchema, table.getName(),
                        oldTriggerName);
            }
        }

        if (!triggerExists && forceRebuild) {
            reason = TriggerReBuildReason.TRIGGERS_MISSING;
        }

        if ((forceRebuild || !triggerIsActive) && triggerExists) {
            symmetricDialect.removeTrigger(sqlBuffer, oldCatalogName, oldSourceSchema, oldTriggerName,
                    trigger.isSourceTableNameWildCarded() ? table.getName() : trigger.getSourceTableName());
            triggerExists = false;
            triggerRemoved = true;
        }

        boolean isDeadTrigger = !trigger.isSyncOnInsert() && !trigger.isSyncOnUpdate() && !trigger.isSyncOnDelete();

        if (hist == null
                && (oldhist == null || (!triggerExists && triggerIsActive) || (isDeadTrigger && forceRebuild))) {
            insert(newTriggerHist);
            hist = getNewestTriggerHistoryForTrigger(trigger.getTriggerId(),
                    trigger.isSourceCatalogNameWildCarded() ? table.getCatalog() : trigger.getSourceCatalogName(),
                    trigger.isSourceSchemaNameWildCarded() ? table.getSchema() : trigger.getSourceSchemaName(),
                    trigger.isSourceTableNameWildCarded() ? table.getName() : trigger.getSourceTableName());
        }

        try {
            if (!triggerExists && triggerIsActive) {
                symmetricDialect.createTrigger(sqlBuffer, dmlType, trigger, hist,
                        configurationService.getChannel(trigger.getChannelId()), tablePrefix, table);
                if (triggerRemoved) {
                    statisticManager.incrementTriggersRebuiltCount(1);
                } else {
                    statisticManager.incrementTriggersCreatedCount(1);
                }
            } else if (triggerRemoved) {
                statisticManager.incrementTriggersRemovedCount(1);
            }

        } catch (RuntimeException ex) {
            if (hist != null) {
                log.info("Cleaning up trigger hist row of {} after failing to create the associated trigger",
                        hist.getTriggerHistoryId());
                hist.setErrorMessage(ex.getMessage());
                inactivateTriggerHistory(hist);
            }
            throw ex;
        }

        return hist;
    }

    protected static String replaceCharsToShortenName(String triggerName) {
        return triggerName.replaceAll("[^a-zA-Z0-9_]|[a|e|i|o|u|A|E|I|O|U]", "");
    }

    protected String getTriggerName(DataEventType dml, int maxTriggerNameLength, Trigger trigger, Table table,
            List<TriggerHistory> activeTriggerHistories) {

        String triggerName = null;
        switch (dml) {
        case INSERT:
            if (!StringUtils.isBlank(trigger.getNameForInsertTrigger())) {
                triggerName = trigger.getNameForInsertTrigger();
            }
            break;
        case UPDATE:
            if (!StringUtils.isBlank(trigger.getNameForUpdateTrigger())) {
                triggerName = trigger.getNameForUpdateTrigger();
            }
            break;
        case DELETE:
            if (!StringUtils.isBlank(trigger.getNameForDeleteTrigger())) {
                triggerName = trigger.getNameForDeleteTrigger();
            }
            break;
        default:
            break;
        }

        if (StringUtils.isBlank(triggerName)) {
            String triggerPrefix1 = tablePrefix + "_";
            String triggerSuffix1 = "on_" + dml.getCode().toLowerCase() + "_for_";
            String triggerSuffix2 = replaceCharsToShortenName(trigger.getTriggerId());
            if (trigger.isSourceTableNameWildCarded()) {
                triggerSuffix2 = replaceCharsToShortenName(table.getName());
            }
            String triggerSuffix3 = replaceCharsToShortenName("_" + parameterService.getNodeGroupId());
            triggerName = triggerPrefix1 + triggerSuffix1 + triggerSuffix2 + triggerSuffix3;
            // use the node group id as part of the trigger if we can because it
            // helps uniquely identify the trigger in embedded databases. In
            // hsqldb we choose the
            // correct connection based on the presence of a table that is named
            // for the trigger.
            // If the trigger isn't unique across all databases, then we can
            // choose the wrong connection.
            if (triggerName.length() > maxTriggerNameLength && maxTriggerNameLength > 0) {
                triggerName = triggerPrefix1 + triggerSuffix1 + triggerSuffix2;
            }
        }

        triggerName = triggerName.toUpperCase();

        if (triggerName.length() > maxTriggerNameLength && maxTriggerNameLength > 0) {
            triggerName = triggerName.substring(0, maxTriggerNameLength - 1);
            log.debug(
                    "We just truncated the trigger name for the {} trigger id={}.  You might want to consider manually providing a name for the trigger that is less than {} characters long",
                    new Object[] { dml.name().toLowerCase(), trigger.getTriggerId(), maxTriggerNameLength });
        }

        int duplicateCount = 0;
        while (isTriggerNameInUse(activeTriggerHistories, trigger.getTriggerId(), triggerName)) {
            duplicateCount++;
            String duplicateSuffix = Integer.toString(duplicateCount);
            if (triggerName.length() + duplicateSuffix.length() > maxTriggerNameLength) {
                triggerName = triggerName.substring(0, triggerName.length() - duplicateSuffix.length())
                        + duplicateSuffix;
            } else {
                triggerName = triggerName + duplicateSuffix;
            }
        }

        return triggerName;
    }

    class TriggerHistoryMapper implements ISqlRowMapper<TriggerHistory> {
        Map<Long, TriggerHistory> retMap = null;

        TriggerHistoryMapper() {
        }

        TriggerHistoryMapper(Map<Long, TriggerHistory> map) {
            this.retMap = map;
        }

        public TriggerHistory mapRow(Row rs) {
            TriggerHistory hist = new TriggerHistory();
            hist.setTriggerHistoryId(rs.getInt("trigger_hist_id"));
            hist.setTriggerId(rs.getString("trigger_id"));
            hist.setSourceTableName(rs.getString("source_table_name"));
            hist.setTableHash(rs.getInt("table_hash"));
            hist.setCreateTime(rs.getDateTime("create_time"));
            hist.setPkColumnNames(rs.getString("pk_column_names"));
            hist.setColumnNames(rs.getString("column_names"));
            hist.setLastTriggerBuildReason(
                    TriggerReBuildReason.fromCode(rs.getString("last_trigger_build_reason")));
            hist.setNameForDeleteTrigger(rs.getString("name_for_delete_trigger"));
            hist.setNameForInsertTrigger(rs.getString("name_for_insert_trigger"));
            hist.setNameForUpdateTrigger(rs.getString("name_for_update_trigger"));
            hist.setSourceSchemaName(rs.getString("source_schema_name"));
            hist.setSourceCatalogName(rs.getString("source_catalog_name"));
            hist.setTriggerRowHash(rs.getLong("trigger_row_hash"));
            hist.setTriggerTemplateHash(rs.getLong("trigger_template_hash"));
            hist.setErrorMessage(rs.getString("error_message"));
            if (this.retMap != null) {
                this.retMap.put((long) hist.getTriggerHistoryId(), hist);
            }
            return hist;
        }
    }

    class RouterMapper implements ISqlRowMapper<Router> {

        List<NodeGroupLink> nodeGroupLinks;

        boolean substituteParameters;

        public RouterMapper(boolean substituteParameters, List<NodeGroupLink> nodeGroupLinks) {
            this.nodeGroupLinks = nodeGroupLinks;
            this.substituteParameters = substituteParameters;
        }

        private NodeGroupLink getNodeGroupLink(String sourceNodeGroupId, String targetNodeGroupId) {
            for (NodeGroupLink nodeGroupLink : nodeGroupLinks) {
                if (nodeGroupLink.getSourceNodeGroupId().equals(sourceNodeGroupId)
                        && nodeGroupLink.getTargetNodeGroupId().equals(targetNodeGroupId)) {
                    return nodeGroupLink;
                }
            }
            return null;
        }

        public Router mapRow(Row rs) {
            Router router = new Router();
            router.setSyncOnInsert(rs.getBoolean("r_sync_on_insert"));
            router.setSyncOnUpdate(rs.getBoolean("r_sync_on_update"));
            router.setSyncOnDelete(rs.getBoolean("r_sync_on_delete"));
            router.setTargetCatalogName(rs.getString("target_catalog_name"));
            router.setNodeGroupLink(
                    getNodeGroupLink(rs.getString("source_node_group_id"), rs.getString("target_node_group_id")));
            router.setTargetSchemaName(rs.getString("target_schema_name"));
            router.setTargetTableName(rs.getString("target_table_name"));

            String condition = rs.getString("router_expression");
            if (!StringUtils.isBlank(condition)) {
                router.setRouterExpression(condition);
            }
            router.setRouterType(rs.getString("router_type"));
            router.setRouterId(rs.getString("router_id"));
            router.setUseSourceCatalogSchema(rs.getBoolean("use_source_catalog_schema"));
            router.setCreateTime(rs.getDateTime("r_create_time"));
            router.setLastUpdateTime(rs.getDateTime("r_last_update_time"));
            router.setLastUpdateBy(rs.getString("r_last_update_by"));

            if (substituteParameters) {
                TypedProperties parameters = parameterService.getAllParameters();
                router.setTargetCatalogName(
                        FormatUtils.replaceTokens(router.getTargetCatalogName(), parameters, true));
                router.setTargetSchemaName(
                        FormatUtils.replaceTokens(router.getTargetSchemaName(), parameters, true));
                router.setTargetTableName(FormatUtils.replaceTokens(router.getTargetTableName(), parameters, true));
            }

            return router;
        }
    }

    class TriggerMapper implements ISqlRowMapper<Trigger> {

        boolean substituteParameters;

        public TriggerMapper(boolean substituteParameters) {
            this.substituteParameters = substituteParameters;
        }

        public Trigger mapRow(Row rs) {
            Trigger trigger = new Trigger();
            trigger.setTriggerId(rs.getString("trigger_id"));
            trigger.setChannelId(rs.getString("channel_id"));
            trigger.setReloadChannelId(rs.getString("reload_channel_id"));
            trigger.setSourceTableName(rs.getString("source_table_name"));
            trigger.setSyncOnInsert(rs.getBoolean("sync_on_insert"));
            trigger.setSyncOnUpdate(rs.getBoolean("sync_on_update"));
            trigger.setSyncOnDelete(rs.getBoolean("sync_on_delete"));
            trigger.setSyncOnIncomingBatch(rs.getBoolean("sync_on_incoming_batch"));
            trigger.setUseStreamLobs(rs.getBoolean("use_stream_lobs"));
            trigger.setUseCaptureLobs(rs.getBoolean("use_capture_lobs"));
            trigger.setUseCaptureOldData(rs.getBoolean("use_capture_old_data"));
            trigger.setUseHandleKeyUpdates(rs.getBoolean("use_handle_key_updates"));
            trigger.setNameForDeleteTrigger(rs.getString("name_for_delete_trigger"));
            trigger.setNameForInsertTrigger(rs.getString("name_for_insert_trigger"));
            trigger.setNameForUpdateTrigger(rs.getString("name_for_update_trigger"));
            String schema = rs.getString("source_schema_name");
            trigger.setSourceSchemaName(schema);
            String catalog = rs.getString("source_catalog_name");
            trigger.setSourceCatalogName(catalog);

            String condition = rs.getString("sync_on_insert_condition");
            if (!StringUtils.isBlank(condition)) {
                trigger.setSyncOnInsertCondition(condition);
            }
            condition = rs.getString("sync_on_update_condition");
            if (!StringUtils.isBlank(condition)) {
                trigger.setSyncOnUpdateCondition(condition);
            }
            condition = rs.getString("sync_on_delete_condition");
            if (!StringUtils.isBlank(condition)) {
                trigger.setSyncOnDeleteCondition(condition);
            }

            String text = rs.getString("custom_on_insert_text");
            if (!StringUtils.isBlank(text)) {
                trigger.setCustomOnInsertText(text);
            }
            text = rs.getString("custom_on_update_text");
            if (!StringUtils.isBlank(text)) {
                trigger.setCustomOnUpdateText(text);
            }
            text = rs.getString("custom_on_delete_text");
            if (!StringUtils.isBlank(text)) {
                trigger.setCustomOnDeleteText(text);
            }

            condition = rs.getString("external_select");
            if (!StringUtils.isBlank(condition)) {
                trigger.setExternalSelect(condition);
            }

            trigger.setChannelExpression(rs.getString("channel_expression"));
            trigger.setTxIdExpression(rs.getString("tx_id_expression"));

            trigger.setCreateTime(rs.getDateTime("t_create_time"));
            trigger.setLastUpdateTime(rs.getDateTime("t_last_update_time"));
            trigger.setLastUpdateBy(rs.getString("t_last_update_by"));
            trigger.setExcludedColumnNames(rs.getString("excluded_column_names"));
            trigger.setSyncKeyNames(rs.getString("sync_key_names"));

            if (substituteParameters) {
                TypedProperties parameters = parameterService.getAllParameters();
                trigger.setSourceCatalogName(
                        FormatUtils.replaceTokens(trigger.getSourceCatalogName(), parameters, true));
                trigger.setSourceSchemaName(
                        FormatUtils.replaceTokens(trigger.getSourceSchemaName(), parameters, true));
                trigger.setSourceTableName(
                        FormatUtils.replaceTokens(trigger.getSourceTableName(), parameters, true));
            }

            return trigger;
        }
    }

    class TriggerRouterMapper implements ISqlRowMapper<TriggerRouter> {

        public TriggerRouterMapper() {
        }

        public TriggerRouter mapRow(Row rs) {
            TriggerRouter triggerRouter = new TriggerRouter();

            Trigger trigger = new Trigger();
            trigger.setTriggerId(rs.getString("trigger_id"));
            triggerRouter.setTrigger(trigger);
            Router router = new Router();
            router.setRouterId(rs.getString("router_id"));
            triggerRouter.setRouter(router);
            triggerRouter.setCreateTime(rs.getDateTime("create_time"));
            triggerRouter.setLastUpdateTime(rs.getDateTime("last_update_time"));
            triggerRouter.setLastUpdateBy(rs.getString("last_update_by"));
            triggerRouter.setInitialLoadOrder(rs.getInt("initial_load_order"));
            triggerRouter.setInitialLoadSelect(rs.getString("initial_load_select"));
            triggerRouter.setInitialLoadBatchCount(rs.getInt("initial_load_batch_count"));
            triggerRouter.setEnabled(rs.getBoolean("enabled"));
            triggerRouter.setInitialLoadDeleteStmt(rs.getString("initial_load_delete_stmt"));

            triggerRouter.setPingBackEnabled(rs.getBoolean("ping_back_enabled"));

            return triggerRouter;
        }
    }

    public void addExtraConfigTable(String table) {
        if (this.extraConfigTables == null) {
            this.extraConfigTables = new ArrayList<String>();
        }
        this.extraConfigTables.add(table);
    }

    public Map<Trigger, Exception> getFailedTriggers() {
        return this.failureListener.getFailures();
    }

    public TriggerHistory findTriggerHistoryForGenericSync() {
        String triggerTableName = TableConstants.getTableName(tablePrefix, TableConstants.SYM_NODE);
        TriggerHistory history = findTriggerHistory(null, null, triggerTableName.toUpperCase());
        if (history == null) {
            history = findTriggerHistory(null, null, triggerTableName);
        }
        return history;
    }

    public Map<Integer, List<TriggerRouter>> fillTriggerRoutersByHistIdAndSortHist(String sourceNodeGroupId,
            String targetNodeGroupId, List<TriggerHistory> triggerHistories) {

        List<TriggerRouter> triggerRouters = new ArrayList<TriggerRouter>(
                getAllTriggerRoutersForReloadForCurrentNode(sourceNodeGroupId, targetNodeGroupId));

        final Map<Integer, List<TriggerRouter>> triggerRoutersByHistoryId = new HashMap<Integer, List<TriggerRouter>>(
                triggerHistories.size());

        for (TriggerHistory triggerHistory : triggerHistories) {
            List<TriggerRouter> triggerRoutersForTriggerHistory = new ArrayList<TriggerRouter>();
            triggerRoutersByHistoryId.put(triggerHistory.getTriggerHistoryId(), triggerRoutersForTriggerHistory);

            String triggerId = triggerHistory.getTriggerId();
            for (TriggerRouter triggerRouter : triggerRouters) {
                if (triggerRouter.getTrigger().getTriggerId().equals(triggerId)) {
                    triggerRoutersForTriggerHistory.add(triggerRouter);
                }
            }
        }

        final List<Table> sortedTables = getSortedTablesFor(triggerHistories);

        Comparator<TriggerHistory> comparator = new Comparator<TriggerHistory>() {
            public int compare(TriggerHistory o1, TriggerHistory o2) {
                List<TriggerRouter> triggerRoutersForTriggerHist1 = triggerRoutersByHistoryId
                        .get(o1.getTriggerHistoryId());
                int intialLoadOrder1 = 0;
                for (TriggerRouter triggerRouter1 : triggerRoutersForTriggerHist1) {
                    if (triggerRouter1.getInitialLoadOrder() > intialLoadOrder1) {
                        intialLoadOrder1 = triggerRouter1.getInitialLoadOrder();
                    }
                }

                List<TriggerRouter> triggerRoutersForTriggerHist2 = triggerRoutersByHistoryId
                        .get(o2.getTriggerHistoryId());
                int intialLoadOrder2 = 0;
                for (TriggerRouter triggerRouter2 : triggerRoutersForTriggerHist2) {
                    if (triggerRouter2.getInitialLoadOrder() > intialLoadOrder2) {
                        intialLoadOrder2 = triggerRouter2.getInitialLoadOrder();
                    }
                }

                if (intialLoadOrder1 < intialLoadOrder2) {
                    return -1;
                } else if (intialLoadOrder1 > intialLoadOrder2) {
                    return 1;
                }

                Table table1 = platform.getTableFromCache(o1.getSourceCatalogName(), o1.getSourceSchemaName(),
                        o1.getSourceTableName(), false);
                Table table2 = platform.getTableFromCache(o2.getSourceCatalogName(), o2.getSourceSchemaName(),
                        o2.getSourceTableName(), false);

                return new Integer(sortedTables.indexOf(table1))
                        .compareTo(new Integer(sortedTables.indexOf(table2)));
            };
        };

        Collections.sort(triggerHistories, comparator);

        return triggerRoutersByHistoryId;

    }

    protected List<Table> getSortedTablesFor(List<TriggerHistory> histories) {
        List<Table> tables = new ArrayList<Table>(histories.size());
        for (TriggerHistory triggerHistory : histories) {
            Table table = platform.getTableFromCache(triggerHistory.getSourceCatalogName(),
                    triggerHistory.getSourceSchemaName(), triggerHistory.getSourceTableName(), false);
            if (table != null) {
                tables.add(table);
            }
        }
        return Database.sortByForeignKeys(tables);
    }

    class TriggerRoutersCache {

        public TriggerRoutersCache(Map<String, List<TriggerRouter>> triggerRoutersByTriggerId,
                Map<String, Router> routersByRouterId) {
            this.triggerRoutersByTriggerId = triggerRoutersByTriggerId;
            this.routersByRouterId = routersByRouterId;
        }

        Map<String, List<TriggerRouter>> triggerRoutersByTriggerId = new HashMap<String, List<TriggerRouter>>();
        Map<String, Router> routersByRouterId = new HashMap<String, Router>();
    }

}