All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.jumpmind.symmetric.fs.client.SyncJob Maven / Gradle / Ivy

There is a newer version: 3.4.9
Show newest version
/*
 * 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 Lesser General Public License (the
 * "License"); you may not use this file except in compliance
 * with the License. 
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, see           
 * .
 * 
 * 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.fs.client;

import java.util.Date;
import java.util.List;
import java.util.concurrent.ScheduledFuture;

import org.apache.commons.lang.StringUtils;
import org.jumpmind.properties.TypedProperties;
import org.jumpmind.symmetric.fs.SyncParameterConstants;
import org.jumpmind.symmetric.fs.client.SyncStatus.Stage;
import org.jumpmind.symmetric.fs.client.connector.ConnectorException;
import org.jumpmind.symmetric.fs.client.connector.ITransportConnector;
import org.jumpmind.symmetric.fs.client.connector.TransportConnectorFactory;
import org.jumpmind.symmetric.fs.config.Node;
import org.jumpmind.symmetric.fs.config.NodeDirectoryKey;
import org.jumpmind.symmetric.fs.config.ScriptAPI;
import org.jumpmind.symmetric.fs.config.ScriptIdentifier;
import org.jumpmind.symmetric.fs.config.SyncConfig;
import org.jumpmind.symmetric.fs.service.IPersisterServices;
import org.jumpmind.symmetric.fs.track.DirectoryChangeTracker;
import org.jumpmind.symmetric.fs.util.ConflictDetectedException;
import org.jumpmind.util.RandomTimeSlot;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.scheduling.support.CronTrigger;

public class SyncJob implements Runnable {

    final Logger log = LoggerFactory.getLogger(getClass());

    protected IPersisterServices persisterServices;
    protected IServerNodeLocker serverNodeLocker;
    protected TaskScheduler taskScheduler;
    protected Node serverNode;
    protected SyncConfig syncConfig;
    protected DirectoryChangeTracker directoryChangeTracker;
    protected ITransportConnector connector;
    protected TypedProperties properties;
    protected NodeDirectoryKey key;
    protected RandomTimeSlot randomTimeSlot;
    protected ISyncClientListener syncClientListener;
    protected ScriptAPI scriptApi;

    private boolean paused = false;

    private Date lastFinishTime;

    private boolean running = false;

    private long lastExecutionTimeInMs;

    private long totalExecutionTimeInMs;

    private long numberOfRuns;

    private boolean started;

    private ScheduledFuture scheduledJob;

    private Throwable exception;
    
    private List filesInConflict;

    public SyncJob(TransportConnectorFactory transportConnectorFactory,
            IPersisterServices persisterServices, IServerNodeLocker serverNodeLocker,
            TaskScheduler taskScheduler, Node node, SyncConfig config, TypedProperties properties,
            ISyncClientListener syncClientListener, ScriptAPI api) {
        this.persisterServices = persisterServices;
        this.serverNodeLocker = serverNodeLocker;
        this.syncClientListener = syncClientListener;
        this.taskScheduler = taskScheduler;
        this.serverNode = node;
        this.syncConfig = config;
        this.properties = properties;
        this.key = new NodeDirectoryKey(node, config.getClientDir());
        this.randomTimeSlot = new RandomTimeSlot(serverNode.getNodeId(), properties.getInt(
                SyncParameterConstants.JOB_RANDOM_MAX_START_TIME_MS, 100));
        this.connector = transportConnectorFactory.createTransportConnector(syncConfig, serverNode);
    }

    public boolean isStarted() {
        return started;
    }

    public boolean isPaused() {
        return paused;
    }

    public boolean isRunning() {
        return running;
    }

    public long getAverageExecutionTimeInMs() {
        if (numberOfRuns > 0) {
            return totalExecutionTimeInMs / numberOfRuns;
        } else {
            return 0;
        }
    }

    public long getLastExecutionTimeInMs() {
        return lastExecutionTimeInMs;
    }

    public Date getLastFinishTime() {
        return lastFinishTime;
    }

    public long getTotalExecutionTimeInMs() {
        return totalExecutionTimeInMs;
    }

    public long getNumberOfRuns() {
        return numberOfRuns;
    }

    public void pause() {
        paused = true;
    }

    public void unpause() {
        paused = false;
    }

    public void start() {
        if (this.scheduledJob == null) {
            if (!StringUtils.isBlank(syncConfig.getFrequency())) {
                String frequency = syncConfig.getFrequency();
                try {
                    long timeBetweenRunsInMs = Long.parseLong(frequency);
                    int startDelay = randomTimeSlot.getRandomValueSeededByExternalId();
                    if (timeBetweenRunsInMs > 0) {
                        this.scheduledJob = taskScheduler.scheduleWithFixedDelay(this, new Date(
                                System.currentTimeMillis() + startDelay), timeBetweenRunsInMs);
                        log.info("Started {} for node {} on periodic schedule: every {}ms",
                                new Object[] { syncConfig.getConfigId(), serverNode.getNodeId(),
                                        timeBetweenRunsInMs });
                        started = true;
                    } else {
                        log.error("Failed to schedule the {} job for node {}",
                                syncConfig.getConfigId(), serverNode.getNodeId());
                    }

                } catch (NumberFormatException ex) {
                    this.scheduledJob = taskScheduler.schedule(this, new CronTrigger(frequency));
                    log.info("Started {} for node {} with cron expression: {}", new Object[] {
                            syncConfig.getConfigId(), serverNode.getNodeId(), frequency });
                    started = true;
                }
            }
        }
    }

    public boolean stop() {
        boolean success = false;
        if (this.scheduledJob != null) {
            success = this.scheduledJob.cancel(true);
            this.scheduledJob = null;
            if (success) {
                log.info("{} for node {} has been cancelled.", syncConfig.getConfigId(),
                        serverNode.getNodeId());
                started = false;
            } else {
                log.warn("Failed to cancel this {} for node {}", syncConfig.getConfigId(),
                        serverNode.getNodeId());
            }
        }
        return success;
    }

    public void destroy() {
        stop();
        if (connector != null) {
            connector.destroy();
            connector = null;
        }
    }

    protected String getEngineName() {
        return properties.getProperty(SyncParameterConstants.ENGINE_NAME, "syncjob");
    }

    /*
     * This method is called from the job
     */
    public void run() {
        invoke(false);
    }

    public boolean invoke() {
        return invoke(true);
    }

    public boolean invoke(boolean force) {
        boolean ran = false;
        try {
            if (!Thread.interrupted()) {
                MDC.put("engineName", getEngineName());
                if (!paused || force) {
                    if (!running) {
                        running = true;
                        synchronized (this) {
                            ran = true;
                            long startTime = System.currentTimeMillis();
                            try {
                                doSync();
                            } finally {
                                lastFinishTime = new Date();
                                long endTime = System.currentTimeMillis();
                                lastExecutionTimeInMs = endTime - startTime;
                                totalExecutionTimeInMs += lastExecutionTimeInMs;
                                numberOfRuns++;
                                running = false;
                            }
                        }
                    }
                }
            } else {
                log.warn("This thread was interrupted.  Not executing the job until the interrupted status has cleared");
            }
        } catch (final Throwable ex) {
            exception = ex;
            log.error(ex.getMessage(), ex);
        }

        return ran;
    }

    protected void doSync() {
        if (serverNodeLocker.lock(serverNode)) {
            try {

                exception = null;

                SyncStatus syncStatus = persisterServices.getSyncStatusPersister().get(
                        SyncStatus.class, key);
                if (syncStatus == null) {
                    syncStatus = new SyncStatus(serverNode, syncConfig);
                    persisterServices.getSyncStatusPersister().save(syncStatus, key);
                }

                connector.connect(syncStatus);

                initDirectoryChangeTracker();

                if (syncStatus.getStage() == Stage.DONE) {
                    syncStatus.setStage(Stage.START);
                }

                while (syncStatus.getStage() != Stage.DONE) {

                    switch (syncStatus.getStage()) {
                        case START:
                            runScript(ScriptIdentifier.PRECLIENT, syncStatus);
                            syncStatus.setStage(Stage.RAN_PRESCRIPT);
                            persisterServices.getSyncStatusPersister().save(syncStatus, key);
                            break;
                        case RAN_PRESCRIPT:
                            syncStatus.setClientSnapshot(directoryChangeTracker.takeSnapshot());
                            syncStatus.setStage(Stage.RECORDED_FILES_TO_SEND);
                            persisterServices.getSyncStatusPersister().save(syncStatus, key);
                            break;
                        case RECORDED_FILES_TO_SEND:
                            connector.prepare(syncStatus);
                            if (syncStatus.getFilesInConflict().size() > 0) {
                                throw new ConflictDetectedException(syncStatus.getFilesInConflict());
                            }
                            syncStatus.setStage(Stage.SEND_FILES);
                            persisterServices.getSyncStatusPersister().save(syncStatus, key);
                            break;
                        case SEND_FILES:
                            connector.send(syncStatus);
                            syncStatus.setStage(Stage.RECEIVE_FILES);
                            persisterServices.getSyncStatusPersister().save(syncStatus, key);
                            break;
                        case RECEIVE_FILES:
                            connector.receive(syncStatus, directoryChangeTracker);
                            syncStatus.setStage(Stage.RUN_POSTSCRIPT);
                            persisterServices.getSyncStatusPersister().save(syncStatus, key);
                            break;
                        case RUN_POSTSCRIPT:
                            runScript(ScriptIdentifier.POSTCLIENT, syncStatus);
                            syncStatus.setStage(Stage.DONE);
                            persisterServices.getSyncStatusPersister().save(syncStatus, key);
                            break;
                        case DONE:
                            break;
                    }
                }
                
                filesInConflict = null;

            } catch (ConflictDetectedException ex) {
                filesInConflict = ex.getFilesInConflict();
                log.warn("Failed to sync.  The following files were in conflict: " + filesInConflict);
            } catch (ConnectorException ex) {
                log.warn("Connection issue: {}", ex.getMessage());
            } finally {
                serverNodeLocker.unlock(serverNode);

                if (connector != null) {
                    connector.close();
                }
            }
        }
    }

    protected boolean runScript(ScriptIdentifier identifier, SyncStatus syncStatus) {
        // TODO runScript
        return true;
    }

    protected void initDirectoryChangeTracker() {
        if (directoryChangeTracker == null) {
            directoryChangeTracker = new DirectoryChangeTracker(serverNode,
                    syncConfig.getClientDir(), syncConfig.getDirectorySpec(),
                    persisterServices.getDirectorySpecSnapshotPersister());
            directoryChangeTracker.start();
        }
    }

    public SyncStatus getSyncStatus() {
        return persisterServices.getSyncStatusPersister().get(SyncStatus.class, key);
    }

    public boolean hasError() {
        return exception != null;
    }

    public boolean hasConflict() {
        return filesInConflict != null;
    }

    public List getFilesInConflict() {
        return filesInConflict;
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy