org.jumpmind.symmetric.AbstractSymmetricEngine.java Source code

Java tutorial

Introduction

Here is the source code for org.jumpmind.symmetric.AbstractSymmetricEngine.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;

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

import java.io.File;
import java.io.InputStreamReader;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;

import org.apache.commons.lang.StringUtils;
import org.jumpmind.db.io.DatabaseXmlUtil;
import org.jumpmind.db.model.Database;
import org.jumpmind.db.model.Table;
import org.jumpmind.db.platform.IDatabasePlatform;
import org.jumpmind.db.sql.ISqlTemplate;
import org.jumpmind.db.sql.SqlException;
import org.jumpmind.db.sql.SqlScript;
import org.jumpmind.db.sql.SqlScriptReader;
import org.jumpmind.properties.TypedProperties;
import org.jumpmind.security.ISecurityService;
import org.jumpmind.security.SecurityServiceFactory;
import org.jumpmind.security.SecurityServiceFactory.SecurityServiceType;
import org.jumpmind.symmetric.common.Constants;
import org.jumpmind.symmetric.common.ParameterConstants;
import org.jumpmind.symmetric.common.TableConstants;
import org.jumpmind.symmetric.db.ISymmetricDialect;
import org.jumpmind.symmetric.io.DefaultOfflineClientListener;
import org.jumpmind.symmetric.io.IOfflineClientListener;
import org.jumpmind.symmetric.io.stage.IStagingManager;
import org.jumpmind.symmetric.job.DefaultOfflineServerListener;
import org.jumpmind.symmetric.job.IJobManager;
import org.jumpmind.symmetric.model.FileTriggerRouter;
import org.jumpmind.symmetric.model.Grouplet;
import org.jumpmind.symmetric.model.Node;
import org.jumpmind.symmetric.model.NodeGroupLink;
import org.jumpmind.symmetric.model.NodeSecurity;
import org.jumpmind.symmetric.model.NodeStatus;
import org.jumpmind.symmetric.model.ProcessInfo;
import org.jumpmind.symmetric.model.ProcessInfo.Status;
import org.jumpmind.symmetric.model.RemoteNodeStatuses;
import org.jumpmind.symmetric.model.Router;
import org.jumpmind.symmetric.model.TriggerRouter;
import org.jumpmind.symmetric.service.IAcknowledgeService;
import org.jumpmind.symmetric.service.IBandwidthService;
import org.jumpmind.symmetric.service.IClusterService;
import org.jumpmind.symmetric.service.IConfigurationService;
import org.jumpmind.symmetric.service.IDataExtractorService;
import org.jumpmind.symmetric.service.IDataLoaderService;
import org.jumpmind.symmetric.service.IDataService;
import org.jumpmind.symmetric.service.IExtensionService;
import org.jumpmind.symmetric.service.IFileSyncService;
import org.jumpmind.symmetric.service.IGroupletService;
import org.jumpmind.symmetric.service.IIncomingBatchService;
import org.jumpmind.symmetric.service.ILoadFilterService;
import org.jumpmind.symmetric.service.INodeCommunicationService;
import org.jumpmind.symmetric.service.INodeService;
import org.jumpmind.symmetric.service.IOutgoingBatchService;
import org.jumpmind.symmetric.service.IParameterService;
import org.jumpmind.symmetric.service.IPullService;
import org.jumpmind.symmetric.service.IPurgeService;
import org.jumpmind.symmetric.service.IPushService;
import org.jumpmind.symmetric.service.IRegistrationService;
import org.jumpmind.symmetric.service.IRouterService;
import org.jumpmind.symmetric.service.ISequenceService;
import org.jumpmind.symmetric.service.IStatisticService;
import org.jumpmind.symmetric.service.ITransformService;
import org.jumpmind.symmetric.service.ITriggerRouterService;
import org.jumpmind.symmetric.service.impl.AcknowledgeService;
import org.jumpmind.symmetric.service.impl.BandwidthService;
import org.jumpmind.symmetric.service.impl.ClusterService;
import org.jumpmind.symmetric.service.impl.ConfigurationService;
import org.jumpmind.symmetric.service.impl.DataExtractorService;
import org.jumpmind.symmetric.service.impl.DataLoaderService;
import org.jumpmind.symmetric.service.impl.DataLoaderService.ConflictNodeGroupLink;
import org.jumpmind.symmetric.service.impl.DataService;
import org.jumpmind.symmetric.service.impl.FileSyncService;
import org.jumpmind.symmetric.service.impl.GroupletService;
import org.jumpmind.symmetric.service.impl.IncomingBatchService;
import org.jumpmind.symmetric.service.impl.LoadFilterService;
import org.jumpmind.symmetric.service.impl.NodeCommunicationService;
import org.jumpmind.symmetric.service.impl.NodeService;
import org.jumpmind.symmetric.service.impl.OutgoingBatchService;
import org.jumpmind.symmetric.service.impl.ParameterService;
import org.jumpmind.symmetric.service.impl.PullService;
import org.jumpmind.symmetric.service.impl.PurgeService;
import org.jumpmind.symmetric.service.impl.PushService;
import org.jumpmind.symmetric.service.impl.RegistrationService;
import org.jumpmind.symmetric.service.impl.RouterService;
import org.jumpmind.symmetric.service.impl.SequenceService;
import org.jumpmind.symmetric.service.impl.StatisticService;
import org.jumpmind.symmetric.service.impl.TransformService;
import org.jumpmind.symmetric.service.impl.TransformService.TransformTableNodeGroupLink;
import org.jumpmind.symmetric.service.impl.TriggerRouterService;
import org.jumpmind.symmetric.statistic.IStatisticManager;
import org.jumpmind.symmetric.statistic.StatisticManager;
import org.jumpmind.symmetric.transport.ConcurrentConnectionManager;
import org.jumpmind.symmetric.transport.IConcurrentConnectionManager;
import org.jumpmind.symmetric.transport.ITransportManager;
import org.jumpmind.symmetric.transport.TransportManagerFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;

abstract public class AbstractSymmetricEngine implements ISymmetricEngine {

    private static Map<String, ISymmetricEngine> registeredEnginesByUrl = new HashMap<String, ISymmetricEngine>();
    private static Map<String, ISymmetricEngine> registeredEnginesByName = new HashMap<String, ISymmetricEngine>();

    protected static final Logger log = LoggerFactory.getLogger(AbstractSymmetricEngine.class);

    private boolean started = false;

    private boolean starting = false;

    private boolean setup = false;

    protected String deploymentType;

    protected ITypedPropertiesFactory propertiesFactory;

    protected IDatabasePlatform platform;

    protected ISecurityService securityService;

    protected ParameterService parameterService;

    protected ISymmetricDialect symmetricDialect;

    protected INodeService nodeService;

    protected IConfigurationService configurationService;

    protected IBandwidthService bandwidthService;

    protected IStatisticService statisticService;

    protected IStatisticManager statisticManager;

    protected IConcurrentConnectionManager concurrentConnectionManager;

    protected ITransportManager transportManager;

    protected IClusterService clusterService;

    protected IPurgeService purgeService;

    protected ITransformService transformService;

    protected ILoadFilterService loadFilterService;

    protected ITriggerRouterService triggerRouterService;

    protected IOutgoingBatchService outgoingBatchService;

    protected IDataService dataService;

    protected IRouterService routerService;

    protected IDataExtractorService dataExtractorService;

    protected IRegistrationService registrationService;

    protected IDataLoaderService dataLoaderService;

    protected IIncomingBatchService incomingBatchService;

    protected IAcknowledgeService acknowledgeService;

    protected IPushService pushService;

    protected IPullService pullService;

    protected IJobManager jobManager;

    protected ISequenceService sequenceService;

    protected IExtensionService extensionService;

    protected IGroupletService groupletService;

    protected IStagingManager stagingManager;

    protected INodeCommunicationService nodeCommunicationService;

    protected IFileSyncService fileSyncService;

    protected Date lastRestartTime = null;

    abstract protected ITypedPropertiesFactory createTypedPropertiesFactory();

    abstract protected IDatabasePlatform createDatabasePlatform(TypedProperties properties);

    protected boolean registerEngine = true;

    protected AbstractSymmetricEngine(boolean registerEngine) {
        this.registerEngine = registerEngine;
    }

    /**
     * Locate a {@link StandaloneSymmetricEngine} in the same JVM
     */
    public static ISymmetricEngine findEngineByUrl(String url) {
        if (registeredEnginesByUrl != null && url != null) {
            return registeredEnginesByUrl.get(url);
        } else {
            return null;
        }
    }

    /**
     * Locate a {@link StandaloneSymmetricEngine} in the same JVM
     */
    public static ISymmetricEngine findEngineByName(String name) {
        if (registeredEnginesByName != null && name != null) {
            return registeredEnginesByName.get(name);
        } else {
            return null;
        }
    }

    public void setDeploymentType(String deploymentType) {
        this.deploymentType = deploymentType;
    }

    protected abstract SecurityServiceType getSecurityServiceType();

    protected void init() {
        if (propertiesFactory == null) {
            this.propertiesFactory = createTypedPropertiesFactory();
        }

        if (securityService == null) {
            this.securityService = SecurityServiceFactory.create(getSecurityServiceType(),
                    propertiesFactory.reload());
        }

        TypedProperties properties = this.propertiesFactory.reload();

        MDC.put("engineName", properties.get(ParameterConstants.ENGINE_NAME));

        this.platform = createDatabasePlatform(properties);

        this.parameterService = new ParameterService(platform, propertiesFactory,
                properties.get(ParameterConstants.RUNTIME_CONFIG_TABLE_PREFIX, "sym"));

        boolean parameterTableExists = this.platform.readTableFromDatabase(null, null,
                TableConstants.getTableName(properties.get(ParameterConstants.RUNTIME_CONFIG_TABLE_PREFIX),
                        TableConstants.SYM_PARAMETER)) != null;
        if (parameterTableExists) {
            this.parameterService.setDatabaseHasBeenInitialized(true);
            this.parameterService.rereadParameters();
        }

        this.platform.setMetadataIgnoreCase(this.parameterService.is(ParameterConstants.DB_METADATA_IGNORE_CASE));
        this.platform.setClearCacheModelTimeoutInMs(
                parameterService.getLong(ParameterConstants.CACHE_TIMEOUT_TABLES_IN_MS));

        this.symmetricDialect = createSymmetricDialect();
        this.extensionService = createExtensionService();
        this.extensionService.refresh();
        this.symmetricDialect.setExtensionService(extensionService);
        this.parameterService.setExtensionService(extensionService);

        this.bandwidthService = new BandwidthService(parameterService);
        this.sequenceService = new SequenceService(parameterService, symmetricDialect);
        this.stagingManager = createStagingManager();
        this.nodeService = new NodeService(parameterService, symmetricDialect, securityService, extensionService);
        this.configurationService = new ConfigurationService(parameterService, symmetricDialect, nodeService);
        this.clusterService = new ClusterService(parameterService, symmetricDialect);
        this.statisticService = new StatisticService(parameterService, symmetricDialect);
        this.statisticManager = new StatisticManager(parameterService, nodeService, configurationService,
                statisticService, clusterService);
        this.concurrentConnectionManager = new ConcurrentConnectionManager(parameterService, statisticManager);
        this.purgeService = new PurgeService(parameterService, symmetricDialect, clusterService, statisticManager);
        this.transformService = new TransformService(parameterService, symmetricDialect, configurationService,
                extensionService);
        this.loadFilterService = new LoadFilterService(parameterService, symmetricDialect, configurationService);
        this.groupletService = new GroupletService(this);
        this.triggerRouterService = new TriggerRouterService(this);
        this.outgoingBatchService = new OutgoingBatchService(parameterService, symmetricDialect, nodeService,
                configurationService, sequenceService, clusterService, extensionService);
        this.dataService = new DataService(this, extensionService);
        this.routerService = buildRouterService();
        this.nodeCommunicationService = buildNodeCommunicationService(clusterService, nodeService, parameterService,
                symmetricDialect);
        this.incomingBatchService = new IncomingBatchService(parameterService, symmetricDialect, clusterService);
        this.dataExtractorService = new DataExtractorService(this);
        this.transportManager = new TransportManagerFactory(this).create();
        this.dataLoaderService = buildDataLoaderService();
        this.registrationService = new RegistrationService(this);
        this.acknowledgeService = new AcknowledgeService(this);
        this.pushService = buildPushService();
        this.pullService = new PullService(parameterService, symmetricDialect, nodeService, dataLoaderService,
                registrationService, clusterService, nodeCommunicationService, configurationService,
                extensionService);
        this.fileSyncService = new FileSyncService(this);
        this.jobManager = createJobManager();

        extensionService.addExtensionPoint(
                new DefaultOfflineServerListener(statisticManager, nodeService, outgoingBatchService));

        IOfflineClientListener defaultlistener = new DefaultOfflineClientListener(parameterService, nodeService);
        extensionService.addExtensionPoint(defaultlistener);
        extensionService.addExtensionPoint(defaultlistener);

        if (registerEngine) {
            registerHandleToEngine();
        }

    }

    protected IPushService buildPushService() {
        return new PushService(this);
    }

    protected IDataLoaderService buildDataLoaderService() {
        return new DataLoaderService(this);
    }

    protected IRouterService buildRouterService() {
        return new RouterService(this);
    }

    protected INodeCommunicationService buildNodeCommunicationService(IClusterService clusterService,
            INodeService nodeService, IParameterService parameterService, ISymmetricDialect symmetricDialect) {
        return new NodeCommunicationService(clusterService, nodeService, parameterService, symmetricDialect);
    }

    abstract protected IStagingManager createStagingManager();

    abstract protected ISymmetricDialect createSymmetricDialect();

    abstract protected IExtensionService createExtensionService();

    abstract protected IJobManager createJobManager();

    public String getSyncUrl() {
        return parameterService.getSyncUrl();
    }

    public Properties getProperties() {
        Properties p = new Properties();
        p.putAll(parameterService.getAllParameters());
        return p;
    }

    public String getEngineName() {
        return parameterService.getString(ParameterConstants.ENGINE_NAME);
    }

    public void setup() {
        getParameterService().rereadParameters();
        if (!setup) {
            setupDatabase(false);
            parameterService.setDatabaseHasBeenInitialized(true);
            setup = true;
        }
    }

    public void setupDatabase(boolean force) {
        log.info("Initializing SymmetricDS database");
        if (parameterService.is(ParameterConstants.AUTO_CONFIGURE_DATABASE) || force) {
            symmetricDialect.initTablesAndDatabaseObjects();
        } else {
            log.info("SymmetricDS is not configured to auto-create the database");
        }
        configurationService.initDefaultChannels();
        clusterService.init();
        sequenceService.init();
        autoConfigRegistrationServer();
        parameterService.rereadParameters();
        log.info("Done initializing SymmetricDS database");
    }

    protected void autoConfigRegistrationServer() {
        Node node = nodeService.findIdentity();

        if (node == null) {
            buildTablesFromDdlUtilXmlIfProvided();
            loadFromScriptIfProvided();
        }

        node = nodeService.findIdentity();

        if (node == null && parameterService.isRegistrationServer()
                && parameterService.is(ParameterConstants.AUTO_INSERT_REG_SVR_IF_NOT_FOUND, false)) {
            log.info("Inserting rows for node, security, identity and group for registration server");
            String nodeId = parameterService.getExternalId();
            node = new Node(parameterService, symmetricDialect);
            node.setNodeId(node.getExternalId());
            nodeService.save(node);
            nodeService.insertNodeIdentity(nodeId);
            node = nodeService.findIdentity();
            nodeService.insertNodeGroup(node.getNodeGroupId(), null);
            NodeSecurity nodeSecurity = nodeService.findNodeSecurity(nodeId, true);
            nodeSecurity.setInitialLoadTime(new Date());
            nodeSecurity.setRegistrationTime(new Date());
            nodeSecurity.setInitialLoadEnabled(false);
            nodeSecurity.setRegistrationEnabled(false);
            nodeService.updateNodeSecurity(nodeSecurity);
        }
    }

    protected boolean buildTablesFromDdlUtilXmlIfProvided() {
        boolean loaded = false;
        String xml = parameterService.getString(ParameterConstants.AUTO_CONFIGURE_REG_SVR_DDLUTIL_XML);
        if (!StringUtils.isBlank(xml)) {
            File file = new File(xml);
            URL fileUrl = null;
            if (file.isFile()) {
                try {
                    fileUrl = file.toURI().toURL();
                } catch (MalformedURLException e) {
                    throw new RuntimeException(e);
                }
            } else {
                fileUrl = getClass().getResource(xml);
            }

            if (fileUrl != null) {
                try {
                    log.info("Building database schema from: {}", xml);
                    Database database = DatabaseXmlUtil.read(new InputStreamReader(fileUrl.openStream()));
                    IDatabasePlatform platform = symmetricDialect.getPlatform();
                    platform.createDatabase(database, true, true);
                    loaded = true;
                } catch (Exception e) {
                    log.error("", e);
                }
            }
        }
        return loaded;
    }

    /**
     * Give the end user the option to provide a script that will load a
     * registration server with an initial SymmetricDS setup.
     * 
     * Look first on the file system, then in the classpath for the SQL file.
     * 
     * @return true if the script was executed
     */
    protected boolean loadFromScriptIfProvided() {
        boolean loaded = false;
        String sqlScripts = parameterService.getString(ParameterConstants.AUTO_CONFIGURE_REG_SVR_SQL_SCRIPT);
        if (!StringUtils.isBlank(sqlScripts)) {
            String[] sqlScriptList = sqlScripts.split(",");
            for (String sqlScript : sqlScriptList) {
                sqlScript = sqlScript.trim();
                if (StringUtils.isNotBlank(sqlScript)) {
                    File file = new File(sqlScript);
                    URL fileUrl = null;
                    if (file.isFile()) {
                        try {
                            fileUrl = file.toURI().toURL();
                        } catch (MalformedURLException e) {
                            throw new RuntimeException(e);
                        }
                    } else {
                        fileUrl = getClass().getResource(sqlScript);
                        if (fileUrl == null) {
                            fileUrl = Thread.currentThread().getContextClassLoader().getResource(sqlScript);
                        }
                    }

                    if (fileUrl != null) {
                        new SqlScript(fileUrl, symmetricDialect.getPlatform().getSqlTemplate(), true,
                                SqlScriptReader.QUERY_ENDS,
                                getSymmetricDialect().getPlatform().getSqlScriptReplacementTokens()).execute();
                        loaded = true;
                    } else {
                        log.info(
                                "Could not find the sql script: {} to execute.  We would have run it if we had found it");
                    }
                }
            }
        }
        return loaded;
    }

    public synchronized boolean start() {
        return start(true);
    }

    public synchronized boolean start(boolean startJobs) {
        if (!starting && !started) {
            try {
                starting = true;
                symmetricDialect.verifyDatabaseIsCompatible();
                setup();
                if (isConfigured()) {
                    Node node = nodeService.findIdentity();
                    if (node != null && (!node.getExternalId().equals(getParameterService().getExternalId())
                            || !node.getNodeGroupId().equals(getParameterService().getNodeGroupId()))) {
                        if (parameterService.is(ParameterConstants.NODE_COPY_MODE_ENABLED, false)) {
                            registrationService.requestNodeCopy();
                        } else {
                            throw new SymmetricException(
                                    "The configured state does not match recorded database state.  The recorded external id is %s while the configured external id is %s. The recorded node group id is %s while the configured node group id is %s",
                                    new Object[] { node.getExternalId(), getParameterService().getExternalId(),
                                            node.getNodeGroupId(), getParameterService().getNodeGroupId() });
                        }
                    } else if (node != null) {

                        log.info("Starting registered node [group={}, id={}, externalId={}]",
                                new Object[] { node.getNodeGroupId(), node.getNodeId(), node.getExternalId() });

                        if (parameterService.is(ParameterConstants.AUTO_SYNC_TRIGGERS_AT_STARTUP, true)) {
                            triggerRouterService.syncTriggers();
                        } else {
                            log.info(ParameterConstants.AUTO_SYNC_TRIGGERS_AT_STARTUP + " is turned off");
                        }

                        if (parameterService.is(ParameterConstants.HEARTBEAT_SYNC_ON_STARTUP, false)
                                || isBlank(node.getDatabaseType())
                                || !StringUtils.equals(node.getSyncUrl(), parameterService.getSyncUrl())) {
                            heartbeat(false);
                        }

                    } else {
                        log.info("Starting unregistered node [group={}, externalId={}]",
                                parameterService.getNodeGroupId(), parameterService.getExternalId());
                    }

                    pushService.start();
                    dataLoaderService.start();

                    if (startJobs && jobManager != null) {
                        jobManager.startJobs();
                    }
                    log.info("Started SymmetricDS");
                    lastRestartTime = new Date();
                    started = true;

                } else {
                    log.error("Did not start SymmetricDS.  It has not been configured properly");
                }
            } catch (Throwable ex) {
                log.error("An error occurred while starting SymmetricDS", ex);
                /* Don't leave SymmetricDS in a half started state */
                stop();
            } finally {
                starting = false;
            }
        }

        log.info(
                "SymmetricDS: type={}, name={}, version={}, groupId={}, externalId={}, databaseName={}, databaseVersion={}, driverName={}, driverVersion={}",
                new Object[] { getDeploymentType(), getEngineName(), Version.version(),
                        getParameterService().getNodeGroupId(), getParameterService().getExternalId(),
                        symmetricDialect.getName(), symmetricDialect.getVersion(), symmetricDialect.getDriverName(),
                        symmetricDialect.getDriverVersion() });
        return started;
    }

    public synchronized void uninstall() {

        log.warn("Attempting an uninstall of all SymmetricDS database objects from the database");

        stop();

        log.info("Just cleaned {} files in the staging area during the uninstall.", getStagingManager().clean(0));

        try {

            Table table = platform.readTableFromDatabase(null, null, TableConstants
                    .getTableName(parameterService.getTablePrefix(), TableConstants.SYM_TRIGGER_ROUTER));
            if (table != null) {

                List<Grouplet> grouplets = groupletService.getGrouplets(true);
                for (Grouplet grouplet : grouplets) {
                    groupletService.deleteGrouplet(grouplet);
                }

                List<TriggerRouter> triggerRouters = triggerRouterService.getTriggerRouters(false, true);
                for (TriggerRouter triggerRouter : triggerRouters) {
                    triggerRouterService.deleteTriggerRouter(triggerRouter);
                }

                List<FileTriggerRouter> fileTriggerRouters = fileSyncService.getFileTriggerRouters();
                for (FileTriggerRouter fileTriggerRouter : fileTriggerRouters) {
                    fileSyncService.deleteFileTriggerRouter(fileTriggerRouter);
                }

                List<Router> routers = triggerRouterService.getRouters(true);
                for (Router router : routers) {
                    triggerRouterService.deleteRouter(router);
                }

            }

            table = platform.readTableFromDatabase(null, null,
                    TableConstants.getTableName(parameterService.getTablePrefix(), TableConstants.SYM_CONFLICT));
            if (table != null) {
                // need to remove all conflicts before we can remove the node
                // group links
                List<ConflictNodeGroupLink> conflicts = dataLoaderService.getConflictSettingsNodeGroupLinks();
                for (ConflictNodeGroupLink conflict : conflicts) {
                    dataLoaderService.delete(conflict);
                }
            }

            table = platform.readTableFromDatabase(null, null, TableConstants
                    .getTableName(parameterService.getTablePrefix(), TableConstants.SYM_TRANSFORM_TABLE));
            if (table != null) {
                // need to remove all transforms before we can remove the node
                // group links
                List<TransformTableNodeGroupLink> transforms = transformService.getTransformTables(false);
                for (TransformTableNodeGroupLink transformTable : transforms) {
                    transformService.deleteTransformTable(transformTable.getTransformId());
                }
            }

            table = platform.readTableFromDatabase(null, null,
                    TableConstants.getTableName(parameterService.getTablePrefix(), TableConstants.SYM_ROUTER));
            if (table != null) {
                List<Router> objects = triggerRouterService.getRouters(true);
                for (Router router : objects) {
                    triggerRouterService.deleteRouter(router);
                }
            }

            table = platform.readTableFromDatabase(null, null,
                    TableConstants.getTableName(parameterService.getTablePrefix(), TableConstants.SYM_CONFLICT));
            if (table != null) {
                List<ConflictNodeGroupLink> objects = dataLoaderService.getConflictSettingsNodeGroupLinks();
                for (ConflictNodeGroupLink obj : objects) {
                    dataLoaderService.delete(obj);
                }
            }

            table = platform.readTableFromDatabase(null, null, TableConstants
                    .getTableName(parameterService.getTablePrefix(), TableConstants.SYM_NODE_GROUP_LINK));
            if (table != null) {
                // remove the links so the symmetric table trigger will be
                // removed
                List<NodeGroupLink> links = configurationService.getNodeGroupLinks(false);
                for (NodeGroupLink nodeGroupLink : links) {
                    configurationService.deleteNodeGroupLink(nodeGroupLink);
                }
            }

            if (table != null) {
                // this should remove all triggers because we have removed all the
                // trigger configuration
                triggerRouterService.syncTriggers();
            }

        } catch (SqlException ex) {
            log.warn("Error while trying remove triggers on tables", ex);
        }

        // remove any additional triggers that may remain because they were not in trigger history
        symmetricDialect.cleanupTriggers();

        symmetricDialect.dropTablesAndDatabaseObjects();

        // force cache to be cleared
        nodeService.deleteIdentity();

        parameterService.setDatabaseHasBeenInitialized(false);

        log.warn("Finished uninstalling SymmetricDS database objects from the database");

    }

    public synchronized void stop() {

        log.info("Stopping SymmetricDS externalId={} version={} database={}",
                new Object[] { parameterService == null ? "?" : parameterService.getExternalId(), Version.version(),
                        symmetricDialect == null ? "?" : symmetricDialect.getName() });
        if (jobManager != null) {
            jobManager.stopJobs();
        }
        if (routerService != null) {
            routerService.stop();
        }
        if (nodeCommunicationService != null) {
            nodeCommunicationService.stop();
        }

        if (pushService != null) {
            pushService.stop();
        }

        if (dataLoaderService != null) {
            dataLoaderService.stop();
        }

        if (statisticManager != null) {
            List<ProcessInfo> infos = statisticManager.getProcessInfos();
            for (ProcessInfo processInfo : infos) {
                Thread thread = processInfo.getThread();
                if (processInfo.getStatus() != Status.OK && thread.isAlive()) {
                    log.info("Trying to interrupt thread '{}' ", thread.getName());
                    try {
                        thread.interrupt();
                    } catch (Exception e) {
                        log.info("Caught exception while attempting to interrupt thread", e);
                    }
                }
            }

            Thread.interrupted();
        }

        started = false;
        starting = false;

    }

    public synchronized void destroy() {
        removeMeFromMap(registeredEnginesByName);
        removeMeFromMap(registeredEnginesByUrl);
        if (parameterService != null) {
            parameterService.setDatabaseHasBeenInitialized(false);
            registeredEnginesByName.remove(getEngineName());
            registeredEnginesByUrl.remove(getSyncUrl());
        }
        stop();
        if (jobManager != null) {
            jobManager.destroy();
        }
    }

    public String reloadNode(String nodeId, String createBy) {
        return dataService.reloadNode(nodeId, false, createBy);
    }

    public String sendSQL(String nodeId, String catalogName, String schemaName, String tableName, String sql) {
        return dataService.sendSQL(nodeId, catalogName, schemaName, tableName, sql);
    }

    public RemoteNodeStatuses push() {
        MDC.put("engineName", getEngineName());
        return pushService.push(true);
    }

    public void syncTriggers() {
        MDC.put("engineName", getEngineName());
        triggerRouterService.syncTriggers();
    }

    public void forceTriggerRebuild() {
        MDC.put("engineName", getEngineName());
        triggerRouterService.syncTriggers(true);
    }

    public NodeStatus getNodeStatus() {
        return nodeService.getNodeStatus();
    }

    public void removeAndCleanupNode(String nodeId) {
        log.info("Removing node {}", nodeId);
        nodeService.deleteNode(nodeId, false);
        log.info("Done removing node ID {}", nodeId);
    }

    public RemoteNodeStatuses pull() {
        MDC.put("engineName", getEngineName());
        return pullService.pullData(true);
    }

    public void route() {
        MDC.put("engineName", getEngineName());
        routerService.routeData(true);
    }

    public void purge() {
        MDC.put("engineName", getEngineName());
        purgeService.purgeOutgoing(true);
        purgeService.purgeIncoming(true);
    }

    public boolean isConfigured() {
        boolean configurationValid = false;

        boolean isRegistrationServer = getNodeService().isRegistrationServer();

        boolean isSelfConfigurable = isRegistrationServer && (getParameterService()
                .is(ParameterConstants.AUTO_INSERT_REG_SVR_IF_NOT_FOUND, false)
                || StringUtils.isNotBlank(
                        getParameterService().getString(ParameterConstants.AUTO_CONFIGURE_REG_SVR_SQL_SCRIPT)));

        Table symNodeTable = symmetricDialect.getPlatform().readTableFromDatabase(null, null,
                TableConstants.getTableName(parameterService.getTablePrefix(), TableConstants.SYM_NODE));

        Node node = symNodeTable != null ? getNodeService().findIdentity() : null;

        long offlineNodeDetectionPeriodSeconds = getParameterService()
                .getLong(ParameterConstants.OFFLINE_NODE_DETECTION_PERIOD_MINUTES) * 60;
        long heartbeatSeconds = getParameterService().getLong(ParameterConstants.HEARTBEAT_SYNC_ON_PUSH_PERIOD_SEC);

        String registrationUrl = getParameterService().getRegistrationUrl();

        if (!isSelfConfigurable && node == null && isRegistrationServer) {
            log.warn(
                    "This node is configured as a registration server, but it is missing its node_identity.  It probably needs configured.",
                    ParameterConstants.REGISTRATION_URL);
        } else if (!isSelfConfigurable && node == null
                && StringUtils.isBlank(getParameterService().getRegistrationUrl())) {
            log.warn(
                    "Please set the property {} so this node may pull registration or manually insert configuration into the configuration tables",
                    ParameterConstants.REGISTRATION_URL);
        } else if (Constants.PLEASE_SET_ME.equals(registrationUrl)) {
            log.warn("Please set the registration.url for the node");
        } else if (Constants.PLEASE_SET_ME.equals(getParameterService().getNodeGroupId())) {
            log.warn("Please set the group.id for the node");
        } else if (Constants.PLEASE_SET_ME.equals(getParameterService().getExternalId())) {
            log.warn("Please set the external.id for the node");
        } else if (offlineNodeDetectionPeriodSeconds > 0 && offlineNodeDetectionPeriodSeconds <= heartbeatSeconds) {
            // Offline node detection is not disabled (-1) and the value is too
            // small (less than the heartbeat)
            log.warn(
                    "The {} property must be a longer period of time than the {} property.  Otherwise, nodes will be taken offline before the heartbeat job has a chance to run",
                    ParameterConstants.OFFLINE_NODE_DETECTION_PERIOD_MINUTES,
                    ParameterConstants.HEARTBEAT_SYNC_ON_PUSH_PERIOD_SEC);
        } else if (node != null && Version.isOlderMinorVersion(Version.version(), node.getSymmetricVersion())) {
            log.warn(
                    "SymmetricDS does not support automatic downgrading.  The current version running version of {} is older than the last running version of {}",
                    Version.version(), node.getSymmetricVersion());
        } else {
            if (node != null && Version.isOlderMinorVersion(node.getSymmetricVersion(), Version.version())) {
                log.debug("The current version of {} is newer than the last running version of {}",
                        Version.version(), node.getSymmetricVersion());
            }
            configurationValid = true;
        }

        return configurationValid;
    }

    public void heartbeat(boolean force) {
        MDC.put("engineName", getEngineName());
        dataService.heartbeat(force);
    }

    public void openRegistration(String nodeGroupId, String externalId) {
        MDC.put("engineName", getEngineName());
        registrationService.openRegistration(nodeGroupId, externalId);
    }

    public void clearCaches() {
        getTriggerRouterService().clearCache();
        getParameterService().rereadParameters();
        getTransformService().clearCache();
        getDataLoaderService().clearCache();
        getConfigurationService().clearCache();
        getNodeService().flushNodeAuthorizedCache();
    }

    public void reOpenRegistration(String nodeId) {
        MDC.put("engineName", getEngineName());
        registrationService.reOpenRegistration(nodeId);
    }

    public boolean isRegistered() {
        return nodeService.findIdentity() != null;
    }

    public boolean isStarted() {
        return started;
    }

    public boolean isStarting() {
        return starting;
    }

    public IConfigurationService getConfigurationService() {
        return configurationService;
    }

    public IParameterService getParameterService() {
        return parameterService;
    }

    public INodeService getNodeService() {
        return nodeService;
    }

    public IRegistrationService getRegistrationService() {
        return registrationService;
    }

    public IClusterService getClusterService() {
        return clusterService;
    }

    public IPurgeService getPurgeService() {
        return purgeService;
    }

    public IDataService getDataService() {
        return dataService;
    }

    public ISymmetricDialect getSymmetricDialect() {
        return symmetricDialect;
    }

    public IJobManager getJobManager() {
        return this.jobManager;
    }

    public IOutgoingBatchService getOutgoingBatchService() {
        return outgoingBatchService;
    }

    public IAcknowledgeService getAcknowledgeService() {
        return this.acknowledgeService;
    }

    public IBandwidthService getBandwidthService() {
        return bandwidthService;
    }

    public IDataExtractorService getDataExtractorService() {
        return this.dataExtractorService;
    }

    public IDataLoaderService getDataLoaderService() {
        return this.dataLoaderService;
    }

    public IIncomingBatchService getIncomingBatchService() {
        return this.incomingBatchService;
    }

    public IPullService getPullService() {
        return this.pullService;
    }

    public IPushService getPushService() {
        return this.pushService;
    }

    public IRouterService getRouterService() {
        return this.routerService;
    }

    public ISecurityService getSecurityService() {
        return securityService;
    }

    public IStatisticService getStatisticService() {
        return statisticService;
    }

    public IStatisticManager getStatisticManager() {
        return statisticManager;
    }

    public ITriggerRouterService getTriggerRouterService() {
        return triggerRouterService;
    }

    public String getDeploymentType() {
        return deploymentType;
    }

    public ITransformService getTransformService() {
        return this.transformService;
    }

    public ILoadFilterService getLoadFilterService() {
        return this.loadFilterService;
    }

    public IConcurrentConnectionManager getConcurrentConnectionManager() {
        return concurrentConnectionManager;
    }

    public String getTablePrefix() {
        return parameterService.getTablePrefix();
    }

    public ITransportManager getTransportManager() {
        return transportManager;
    }

    public IExtensionService getExtensionService() {
        return extensionService;
    }

    public IStagingManager getStagingManager() {
        return stagingManager;
    }

    public ISequenceService getSequenceService() {
        return sequenceService;
    }

    public INodeCommunicationService getNodeCommunicationService() {
        return nodeCommunicationService;
    }

    public IGroupletService getGroupletService() {
        return groupletService;
    }

    private void removeMeFromMap(Map<String, ISymmetricEngine> map) {
        Set<String> keys = new HashSet<String>(map.keySet());
        for (String key : keys) {
            if (this.equals(map.get(key))) {
                map.remove(key);
            }
        }
    }

    /**
     * Register this instance of the engine so it can be found by other
     * processes in the JVM.
     * 
     * @see #findEngineByUrl(String)
     */
    private void registerHandleToEngine() {
        String url = getSyncUrl();
        ISymmetricEngine alreadyRegister = registeredEnginesByUrl.get(url);
        if (alreadyRegister == null || alreadyRegister.equals(this)) {
            if (url != null) {
                registeredEnginesByUrl.put(url, this);
            }
        } else {
            log.warn("Could not register engine.  There was already an engine registered under the url: {}",
                    getSyncUrl());
        }

        alreadyRegister = registeredEnginesByName.get(getEngineName());
        if (alreadyRegister == null || alreadyRegister.equals(this)) {
            registeredEnginesByName.put(getEngineName(), this);
        } else {
            throw new EngineAlreadyRegisteredException(
                    "Could not register engine.  There was already an engine registered under the name: "
                            + getEngineName());
        }

    }

    public Date getLastRestartTime() {
        return lastRestartTime;
    }

    public ISqlTemplate getSqlTemplate() {
        return getSymmetricDialect().getPlatform().getSqlTemplate();
    }

    public Logger getLog() {
        return log;
    }

    @SuppressWarnings("unchecked")
    public <T> T getDataSource() {
        return (T) getSymmetricDialect().getPlatform().getDataSource();
    }

    public IDatabasePlatform getDatabasePlatform() {
        return getSymmetricDialect().getPlatform();
    }

    public IFileSyncService getFileSyncService() {
        return fileSyncService;
    }

}