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

Java tutorial

Introduction

Here is the source code for org.jumpmind.symmetric.service.impl.OutgoingBatchService.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 java.sql.Types;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;

import org.apache.commons.lang.StringUtils;
import org.jumpmind.db.sql.ISqlRowMapper;
import org.jumpmind.db.sql.ISqlTransaction;
import org.jumpmind.db.sql.Row;
import org.jumpmind.db.sql.mapper.StringMapper;
import org.jumpmind.symmetric.common.Constants;
import org.jumpmind.symmetric.common.ParameterConstants;
import org.jumpmind.symmetric.db.ISymmetricDialect;
import org.jumpmind.symmetric.ext.IOutgoingBatchFilter;
import org.jumpmind.symmetric.model.Channel;
import org.jumpmind.symmetric.model.NodeChannel;
import org.jumpmind.symmetric.model.NodeGroupChannelWindow;
import org.jumpmind.symmetric.model.NodeGroupLinkAction;
import org.jumpmind.symmetric.model.NodeHost;
import org.jumpmind.symmetric.model.NodeSecurity;
import org.jumpmind.symmetric.model.OutgoingBatch;
import org.jumpmind.symmetric.model.OutgoingBatch.Status;
import org.jumpmind.symmetric.model.OutgoingBatchByNodeChannelCount;
import org.jumpmind.symmetric.model.OutgoingBatchSummary;
import org.jumpmind.symmetric.model.OutgoingBatches;
import org.jumpmind.symmetric.model.OutgoingLoadSummary;
import org.jumpmind.symmetric.service.IClusterService;
import org.jumpmind.symmetric.service.IConfigurationService;
import org.jumpmind.symmetric.service.IExtensionService;
import org.jumpmind.symmetric.service.INodeService;
import org.jumpmind.symmetric.service.IOutgoingBatchService;
import org.jumpmind.symmetric.service.IParameterService;
import org.jumpmind.symmetric.service.ISequenceService;
import org.jumpmind.util.AppUtils;
import org.jumpmind.util.FormatUtils;

/**
 * @see IOutgoingBatchService
 */
public class OutgoingBatchService extends AbstractService implements IOutgoingBatchService {

    private INodeService nodeService;

    private IConfigurationService configurationService;

    private ISequenceService sequenceService;

    private IClusterService clusterService;

    private IExtensionService extensionService;

    public OutgoingBatchService(IParameterService parameterService, ISymmetricDialect symmetricDialect,
            INodeService nodeService, IConfigurationService configurationService, ISequenceService sequenceService,
            IClusterService clusterService, IExtensionService extensionService) {
        super(parameterService, symmetricDialect);
        this.nodeService = nodeService;
        this.configurationService = configurationService;
        this.sequenceService = sequenceService;
        this.clusterService = clusterService;
        this.extensionService = extensionService;
        setSqlMap(new OutgoingBatchServiceSqlMap(symmetricDialect.getPlatform(), createSqlReplacementTokens()));
    }

    @Override
    public int cancelLoadBatches(long loadId) {
        return sqlTemplate.update(getSql("cancelLoadBatchesSql"), loadId);
    }

    public void markAllAsSentForNode(String nodeId, boolean includeConfigChannel) {
        OutgoingBatches batches = null;
        int configCount;
        do {
            configCount = 0;
            batches = getOutgoingBatches(nodeId, true);
            List<OutgoingBatch> list = batches.getBatches();
            /*
             * Sort in reverse order so we don't get fk errors for batches that
             * are currently processing. We don't make the update transactional
             * to prevent contention in highly loaded systems
             */
            Collections.sort(list, new Comparator<OutgoingBatch>() {
                public int compare(OutgoingBatch o1, OutgoingBatch o2) {
                    return -new Long(o1.getBatchId()).compareTo(o2.getBatchId());
                }
            });

            for (OutgoingBatch outgoingBatch : list) {
                if (includeConfigChannel || !outgoingBatch.getChannelId().equals(Constants.CHANNEL_CONFIG)) {
                    outgoingBatch.setStatus(Status.OK);
                    outgoingBatch.setErrorFlag(false);
                    updateOutgoingBatch(outgoingBatch);
                } else {
                    configCount++;
                }
            }
        } while (batches.getBatches().size() > configCount);
    }

    public void markAllConfigAsSentForNode(String nodeId) {
        int updateCount;
        do {
            updateCount = 0;
            OutgoingBatches batches = getOutgoingBatches(nodeId, false);
            List<OutgoingBatch> list = batches.getBatches();
            for (OutgoingBatch outgoingBatch : list) {
                if (outgoingBatch.getChannelId().equals(Constants.CHANNEL_CONFIG)) {
                    outgoingBatch.setStatus(Status.OK);
                    outgoingBatch.setErrorFlag(false);
                    outgoingBatch.setIgnoreCount(1);
                    updateOutgoingBatch(outgoingBatch);
                    updateCount++;
                }
            }
        } while (updateCount > 0);
    }

    public void copyOutgoingBatches(String channelId, long startBatchId, String fromNodeId, String toNodeId) {
        log.info("Copying outgoing batches for channel '{}' from node '{}' to node '{}' starting at {}",
                new Object[] { channelId, fromNodeId, toNodeId, startBatchId });
        sqlTemplate.update(getSql("deleteOutgoingBatchesForNodeSql"), toNodeId, channelId, fromNodeId, channelId);
        int count = sqlTemplate.update(getSql("copyOutgoingBatchesSql"), toNodeId, fromNodeId, channelId,
                startBatchId);
        log.info("Copied {} outgoing batches for channel '{}' from node '{}' to node '{}'",
                new Object[] { count, channelId, fromNodeId, toNodeId });
    }

    public void updateAbandonedRoutingBatches() {
        int count = sqlTemplate.queryForInt(getSql("countOutgoingBatchesWithStatusSql"), Status.RT.name());
        if (count > 0) {
            log.info("Cleaning up {} batches that were abandoned by a failed or aborted attempt at routing", count);
            sqlTemplate.update(getSql("updateOutgoingBatchesStatusSql"), Status.OK.name(), Status.RT.name());
        }
    }

    public void updateOutgoingBatches(List<OutgoingBatch> outgoingBatches) {
        for (OutgoingBatch batch : outgoingBatches) {
            updateOutgoingBatch(batch);
        }
    }

    public void updateOutgoingBatch(OutgoingBatch outgoingBatch) {
        ISqlTransaction transaction = null;
        try {
            transaction = sqlTemplate.startSqlTransaction();
            updateOutgoingBatch(transaction, outgoingBatch);
            transaction.commit();
        } catch (Error ex) {
            if (transaction != null) {
                transaction.rollback();
            }
            throw ex;
        } catch (RuntimeException ex) {
            if (transaction != null) {
                transaction.rollback();
            }
            throw ex;
        } finally {
            close(transaction);
        }
    }

    public void updateOutgoingBatch(ISqlTransaction transaction, OutgoingBatch outgoingBatch) {
        outgoingBatch.setLastUpdatedTime(new Date());
        outgoingBatch.setLastUpdatedHostName(clusterService.getServerId());
        transaction.prepareAndExecute(getSql("updateOutgoingBatchSql"), new Object[] {
                outgoingBatch.getStatus().name(), outgoingBatch.getLoadId(),
                outgoingBatch.isExtractJobFlag() ? 1 : 0, outgoingBatch.isLoadFlag() ? 1 : 0,
                outgoingBatch.isErrorFlag() ? 1 : 0, outgoingBatch.getByteCount(), outgoingBatch.getExtractCount(),
                outgoingBatch.getSentCount(), outgoingBatch.getLoadCount(), outgoingBatch.getDataEventCount(),
                outgoingBatch.getReloadEventCount(), outgoingBatch.getInsertEventCount(),
                outgoingBatch.getUpdateEventCount(), outgoingBatch.getDeleteEventCount(),
                outgoingBatch.getOtherEventCount(), outgoingBatch.getIgnoreCount(), outgoingBatch.getRouterMillis(),
                outgoingBatch.getNetworkMillis(), outgoingBatch.getFilterMillis(), outgoingBatch.getLoadMillis(),
                outgoingBatch.getExtractMillis(), outgoingBatch.getSqlState(), outgoingBatch.getSqlCode(),
                FormatUtils.abbreviateForLogging(outgoingBatch.getSqlMessage()), outgoingBatch.getFailedDataId(),
                outgoingBatch.getLastUpdatedHostName(), outgoingBatch.getLastUpdatedTime(),
                outgoingBatch.getBatchId(), outgoingBatch.getNodeId() },
                new int[] { Types.CHAR, Types.BIGINT, Types.NUMERIC, Types.NUMERIC, Types.NUMERIC, Types.BIGINT,
                        Types.BIGINT, Types.BIGINT, Types.BIGINT, Types.BIGINT, Types.BIGINT, Types.BIGINT,
                        Types.BIGINT, Types.BIGINT, Types.BIGINT, Types.BIGINT, Types.BIGINT, Types.BIGINT,
                        Types.BIGINT, Types.BIGINT, Types.BIGINT, Types.VARCHAR, Types.NUMERIC, Types.VARCHAR,
                        Types.BIGINT, Types.VARCHAR, Types.TIMESTAMP, symmetricDialect.getSqlTypeForIds(),
                        Types.VARCHAR });
    }

    public void insertOutgoingBatch(final OutgoingBatch outgoingBatch) {
        ISqlTransaction transaction = null;
        try {
            transaction = sqlTemplate.startSqlTransaction();
            insertOutgoingBatch(transaction, outgoingBatch);
            transaction.commit();
        } catch (Error ex) {
            if (transaction != null) {
                transaction.rollback();
            }
            throw ex;
        } catch (RuntimeException ex) {
            if (transaction != null) {
                transaction.rollback();
            }
            throw ex;
        } finally {
            close(transaction);
        }
    }

    public void insertOutgoingBatch(ISqlTransaction transaction, OutgoingBatch outgoingBatch) {
        outgoingBatch.setLastUpdatedHostName(clusterService.getServerId());

        long batchId = outgoingBatch.getBatchId();
        if (batchId <= 0) {
            batchId = sequenceService.nextVal(transaction, Constants.SEQUENCE_OUTGOING_BATCH);
        }
        transaction.prepareAndExecute(getSql("insertOutgoingBatchSql"), batchId, outgoingBatch.getNodeId(),
                outgoingBatch.getChannelId(), outgoingBatch.getStatus().name(), outgoingBatch.getLoadId(),
                outgoingBatch.isExtractJobFlag() ? 1 : 0, outgoingBatch.isLoadFlag() ? 1 : 0,
                outgoingBatch.isCommonFlag() ? 1 : 0, outgoingBatch.getReloadEventCount(),
                outgoingBatch.getOtherEventCount(), outgoingBatch.getLastUpdatedHostName(),
                outgoingBatch.getCreateBy());
        outgoingBatch.setBatchId(batchId);
    }

    public OutgoingBatch findOutgoingBatch(long batchId, String nodeId) {
        List<OutgoingBatch> list = null;
        if (StringUtils.isNotBlank(nodeId)) {
            list = (List<OutgoingBatch>) sqlTemplate.query(
                    getSql("selectOutgoingBatchPrefixSql", "findOutgoingBatchSql"), new OutgoingBatchMapper(true),
                    new Object[] { batchId, nodeId },
                    new int[] { symmetricDialect.getSqlTypeForIds(), Types.VARCHAR });
        } else {
            /*
             * Pushing to an older version of symmetric might result in a batch
             * without the node id
             */
            list = (List<OutgoingBatch>) sqlTemplate.query(
                    getSql("selectOutgoingBatchPrefixSql", "findOutgoingBatchByIdOnlySql"),
                    new OutgoingBatchMapper(true), new Object[] { batchId },
                    new int[] { symmetricDialect.getSqlTypeForIds() });
        }
        if (list != null && list.size() > 0) {
            return list.get(0);
        } else {
            return null;
        }
    }

    public int countOutgoingBatchesInError() {
        return sqlTemplate.queryForInt(getSql("countOutgoingBatchesErrorsSql"));
    }

    public int countOutgoingBatchesInError(String channelId) {
        return sqlTemplate.queryForInt(getSql("countOutgoingBatchesErrorsOnChannelSql"), channelId);
    }

    public int countOutgoingBatchesUnsent() {
        return sqlTemplate.queryForInt(getSql("countOutgoingBatchesUnsentSql"));
    }

    public int countOutgoingBatchesUnsent(String channelId) {
        return sqlTemplate.queryForInt(getSql("countOutgoingBatchesUnsentOnChannelSql"), channelId);
    }

    public int countOutgoingBatches(List<String> nodeIds, List<String> channels,
            List<OutgoingBatch.Status> statuses) {
        Map<String, Object> params = new HashMap<String, Object>();
        params.put("NODES", nodeIds);
        params.put("CHANNELS", channels);
        params.put("STATUSES", toStringList(statuses));

        return sqlTemplate.queryForInt(
                getSql("selectCountBatchesPrefixSql", buildBatchWhere(nodeIds, channels, statuses)), params);
    }

    public List<OutgoingBatch> listOutgoingBatches(List<String> nodeIds, List<String> channels,
            List<OutgoingBatch.Status> statuses, long startAtBatchId, final int maxRowsToRetrieve,
            boolean ascending) {

        String where = buildBatchWhere(nodeIds, channels, statuses);
        Map<String, Object> params = new HashMap<String, Object>();
        params.put("NODES", nodeIds);
        params.put("CHANNELS", channels);
        params.put("STATUSES", toStringList(statuses));
        String startAtBatchIdSql = null;
        if (startAtBatchId > 0) {
            if (StringUtils.isBlank(where)) {
                where = " where 1=1 ";
            }
            params.put("BATCH_ID", startAtBatchId);
            startAtBatchIdSql = " and batch_id = :BATCH_ID ";
        }

        String sql = getSql("selectOutgoingBatchPrefixSql", where, startAtBatchIdSql,
                ascending ? " order by batch_id asc" : " order by batch_id desc");
        return sqlTemplate.query(sql, maxRowsToRetrieve, new OutgoingBatchMapper(true), params);

    }

    protected List<String> toStringList(List<OutgoingBatch.Status> statuses) {
        List<String> statusStrings = new ArrayList<String>(statuses.size());
        for (Status status : statuses) {
            statusStrings.add(status.name());
        }
        return statusStrings;

    }

    protected boolean containsOnlyStatus(OutgoingBatch.Status status, List<OutgoingBatch.Status> statuses) {
        return statuses.size() == 1 && statuses.get(0) == status;
    }

    /**
     * Select batches to process. Batches that are NOT in error will be returned
     * first. They will be ordered by batch id as the batches will have already
     * been created by {@link #buildOutgoingBatches(String)} in channel priority
     * order.
     */
    public OutgoingBatches getOutgoingBatches(String nodeId, boolean includeDisabledChannels) {
        long ts = System.currentTimeMillis();
        final int maxNumberOfBatchesToSelect = parameterService
                .getInt(ParameterConstants.OUTGOING_BATCH_MAX_BATCHES_TO_SELECT, 1000);
        List<OutgoingBatch> list = (List<OutgoingBatch>) sqlTemplate.query(
                getSql("selectOutgoingBatchPrefixSql", "selectOutgoingBatchSql"), maxNumberOfBatchesToSelect,
                new OutgoingBatchMapper(includeDisabledChannels),
                new Object[] { nodeId, OutgoingBatch.Status.RQ.name(), OutgoingBatch.Status.NE.name(),
                        OutgoingBatch.Status.QY.name(), OutgoingBatch.Status.SE.name(),
                        OutgoingBatch.Status.LD.name(), OutgoingBatch.Status.ER.name(),
                        OutgoingBatch.Status.IG.name() },
                null);

        OutgoingBatches batches = new OutgoingBatches(list);

        List<NodeChannel> channels = new ArrayList<NodeChannel>(configurationService.getNodeChannels(nodeId, true));
        batches.sortChannels(channels);

        List<IOutgoingBatchFilter> filters = extensionService.getExtensionPointList(IOutgoingBatchFilter.class);

        List<OutgoingBatch> keepers = new ArrayList<OutgoingBatch>();

        for (NodeChannel channel : channels) {
            List<OutgoingBatch> batchesForChannel = getBatchesForChannelWindows(batches.getBatches(), nodeId,
                    channel, configurationService.getNodeGroupChannelWindows(parameterService.getNodeGroupId(),
                            channel.getChannelId()));
            if (filters != null) {
                for (IOutgoingBatchFilter filter : filters) {
                    batchesForChannel = filter.filter(channel, batchesForChannel);
                }
            }
            if (parameterService.is(ParameterConstants.DATA_EXTRACTOR_ENABLED)
                    || channel.getChannelId().equals(Constants.CHANNEL_CONFIG)) {
                keepers.addAll(batchesForChannel);
            }
        }
        batches.setBatches(keepers);

        long executeTimeInMs = System.currentTimeMillis() - ts;
        if (executeTimeInMs > Constants.LONG_OPERATION_THRESHOLD) {
            log.info("Selecting {} outgoing batch rows for node {} took {} ms", list.size(), nodeId,
                    executeTimeInMs);
        }

        return batches;
    }

    public List<OutgoingBatch> getOutgoingBatchesForNodeChannel(String nodeId, NodeChannel nodeChannel) {
        if (parameterService.is(ParameterConstants.DATA_EXTRACTOR_ENABLED)
                || nodeChannel.getChannelId().equals(Constants.CHANNEL_CONFIG)) {
            long ts = System.currentTimeMillis();

            //final int maxNumberOfBatchesToSelect = parameterService.getInt(ParameterConstants.OUTGOING_BATCH_MAX_BATCHES_TO_SELECT, 1000);
            List<OutgoingBatch> list = (List<OutgoingBatch>) sqlTemplate.query(
                    getSql("selectOutgoingBatchPrefixSql", "selectOutgoingBatchForChannelSql"),
                    nodeChannel.getMaxBatchToSend(), new OutgoingBatchMapper(true),
                    new Object[] { nodeId, nodeChannel.getChannelId(), OutgoingBatch.Status.RQ.name(),
                            OutgoingBatch.Status.NE.name(), OutgoingBatch.Status.QY.name(),
                            OutgoingBatch.Status.SE.name(), OutgoingBatch.Status.LD.name(),
                            OutgoingBatch.Status.ER.name(), OutgoingBatch.Status.IG.name() },
                    null);

            long executeTimeInMs = System.currentTimeMillis() - ts;
            if (executeTimeInMs > Constants.LONG_OPERATION_THRESHOLD) {
                log.info("Selecting {} outgoing batch rows for node {} took {} ms", list.size(), nodeId,
                        executeTimeInMs);
            }

            List<IOutgoingBatchFilter> filters = extensionService.getExtensionPointList(IOutgoingBatchFilter.class);

            list = getBatchesForChannelWindows(list, nodeId, nodeChannel, configurationService
                    .getNodeGroupChannelWindows(parameterService.getNodeGroupId(), nodeChannel.getChannelId()));
            if (filters != null) {
                for (IOutgoingBatchFilter filter : filters) {
                    list = filter.filter(nodeChannel, list);
                }
            }
            return list;
        } else {
            return Collections.emptyList();
        }

    }

    public List<OutgoingBatch> getBatchesForChannelWindows(List<OutgoingBatch> current, String targetNodeId,
            NodeChannel channel, List<NodeGroupChannelWindow> windows) {
        List<OutgoingBatch> keeping = new ArrayList<OutgoingBatch>();
        if (current != null && current.size() > 0) {
            if (inTimeWindow(windows, targetNodeId)) {
                int maxBatchesToSend = channel.getMaxBatchToSend();
                for (OutgoingBatch outgoingBatch : current) {
                    if (channel.getChannelId().equals(outgoingBatch.getChannelId()) && maxBatchesToSend > 0) {
                        keeping.add(outgoingBatch);
                        maxBatchesToSend--;
                    }
                }
            }
        }
        return keeping;
    }

    /**
     * If {@link NodeGroupChannelWindow}s are defined for this channel, then
     * check to see if the time (according to the offset passed in) is within on
     * of the configured windows.
     */
    public boolean inTimeWindow(List<NodeGroupChannelWindow> windows, String targetNodeId) {
        if (windows != null && windows.size() > 0) {
            for (NodeGroupChannelWindow window : windows) {
                String timezoneOffset = null;
                List<NodeHost> hosts = nodeService.findNodeHosts(targetNodeId);
                if (hosts.size() > 0) {
                    timezoneOffset = hosts.get(0).getTimezoneOffset();
                } else {
                    timezoneOffset = AppUtils.getTimezoneOffset();
                }
                if (window.inTimeWindow(timezoneOffset)) {
                    return true;
                }
            }
            return false;
        } else {
            return true;
        }

    }

    public OutgoingBatches getOutgoingBatchRange(String nodeId, Date startDate, Date endDate, String... channels) {
        OutgoingBatches batches = new OutgoingBatches();
        List<OutgoingBatch> batchList = new ArrayList<OutgoingBatch>();
        for (String channel : channels) {
            batchList.addAll(
                    sqlTemplate.query(getSql("selectOutgoingBatchPrefixSql", "selectOutgoingBatchTimeRangeSql"),
                            new OutgoingBatchMapper(true), nodeId, channel, startDate, endDate));
        }
        batches.setBatches(batchList);
        return batches;
    }

    public OutgoingBatches getOutgoingBatchRange(long startBatchId, long endBatchId) {
        OutgoingBatches batches = new OutgoingBatches();
        batches.setBatches(sqlTemplate.query(getSql("selectOutgoingBatchPrefixSql", "selectOutgoingBatchRangeSql"),
                new OutgoingBatchMapper(true), startBatchId, endBatchId));
        return batches;
    }

    public OutgoingBatches getOutgoingBatchErrors(int maxRows) {
        OutgoingBatches batches = new OutgoingBatches();
        batches.setBatches(sqlTemplate.query(getSql("selectOutgoingBatchPrefixSql", "selectOutgoingBatchErrorsSql"),
                maxRows, new OutgoingBatchMapper(true), null, null));
        return batches;
    }

    public List<OutgoingBatchByNodeChannelCount> getOutgoingBatchByNodeChannelCount(int maxRows,
            NodeGroupLinkAction linkType, boolean readyToSendOnly) {
        List<OutgoingBatchByNodeChannelCount> forReturn = new ArrayList<OutgoingBatchByNodeChannelCount>();
        List<OutgoingBatchByNodeChannelCount> allNodeChannels = sqlTemplate.query(
                getSql("selectPendingOutgoingBatchByChannelCountSql"), maxRows,
                new OutgoingBatchByNodeChannelCountMapper(), new Object[] { linkType.name() });
        if (readyToSendOnly) {
            Set<String> nodeIdHasReloadsNotInError = new HashSet<String>();
            for (OutgoingBatchByNodeChannelCount outgoingBatchByNodeChannelCount : allNodeChannels) {
                Channel channel = configurationService.getChannel(outgoingBatchByNodeChannelCount.getChannelId());
                if (channel.isReloadFlag() && !outgoingBatchByNodeChannelCount.isInError()) {
                    nodeIdHasReloadsNotInError.add(outgoingBatchByNodeChannelCount.getNodeId());
                }
            }

            for (OutgoingBatchByNodeChannelCount outgoingBatchByNodeChannelCount : allNodeChannels) {
                Channel channel = configurationService.getChannel(outgoingBatchByNodeChannelCount.getChannelId());
                if (!nodeIdHasReloadsNotInError.contains(outgoingBatchByNodeChannelCount.getNodeId())
                        || channel.isReloadFlag()) {
                    forReturn.add(outgoingBatchByNodeChannelCount);
                }
            }

        } else {
            forReturn.addAll(allNodeChannels);
        }
        return forReturn;
    }

    public List<String> getNodesInError() {
        return sqlTemplate.query(getSql("selectNodesInErrorSql"), new StringMapper());
    }

    public List<OutgoingBatch> getNextOutgoingBatchForEachNode() {
        return sqlTemplate.query(getSql("getNextOutgoingBatchForEachNodeSql"), new OutgoingBatchMapper(true, true));
    }

    public boolean isInitialLoadComplete(String nodeId) {
        return areAllLoadBatchesComplete(nodeId) && !isUnsentDataOnChannelForNode(Constants.CHANNEL_CONFIG, nodeId);
    }

    public boolean areAllLoadBatchesComplete(String nodeId) {

        NodeSecurity security = nodeService.findNodeSecurity(nodeId);
        if (security == null || security.isInitialLoadEnabled()) {
            return false;
        }

        List<String> statuses = (List<String>) sqlTemplate.query(getSql("initialLoadStatusSql"), new StringMapper(),
                nodeId, 1);
        if (statuses == null || statuses.size() == 0) {
            throw new RuntimeException("The initial load has not been started for " + nodeId);
        }

        for (String status : statuses) {
            if (!Status.OK.name().equals(status)) {
                return false;
            }
        }
        return true;
    }

    public boolean isUnsentDataOnChannelForNode(String channelId, String nodeId) {
        int unsentCount = sqlTemplate.queryForInt(getSql("unsentBatchesForNodeIdChannelIdSql"),
                new Object[] { nodeId, channelId });
        if (unsentCount > 0) {
            return true;
        }

        // Do we need to check for unbatched data?
        return false;
    }

    public List<OutgoingBatchSummary> findOutgoingBatchSummary(Status... statuses) {
        Object[] args = new Object[statuses.length];
        StringBuilder inList = new StringBuilder();
        for (int i = 0; i < statuses.length; i++) {
            args[i] = statuses[i].name();
            inList.append("?,");
        }

        String sql = getSql("selectOutgoingBatchSummaryByStatusSql").replace(":STATUS_LIST",
                inList.substring(0, inList.length() - 1));

        return sqlTemplate.query(sql, new OutgoingBatchSummaryMapper(), args);
    }

    public List<OutgoingLoadSummary> getLoadSummaries(boolean activeOnly) {
        final Map<String, OutgoingLoadSummary> loadSummaries = new TreeMap<String, OutgoingLoadSummary>();
        sqlTemplate.query(getSql("getLoadSummariesSql"), new ISqlRowMapper<OutgoingLoadSummary>() {
            public OutgoingLoadSummary mapRow(Row rs) {
                long loadId = rs.getLong("load_id");
                String nodeId = rs.getString("node_id");
                String loadNodeId = String.format("%010d-%s", loadId, nodeId);
                OutgoingLoadSummary summary = loadSummaries.get(loadNodeId);
                if (summary == null) {
                    summary = new OutgoingLoadSummary();
                    summary.setLoadId(loadId);
                    summary.setNodeId(nodeId);
                    summary.setCreateBy(rs.getString("create_by"));
                    loadSummaries.put(loadNodeId, summary);
                }

                Status status = Status.valueOf(rs.getString("status"));
                int count = rs.getInt("cnt");

                Date lastUpdateTime = rs.getDateTime("last_update_time");
                if (summary.getLastUpdateTime() == null || summary.getLastUpdateTime().before(lastUpdateTime)) {
                    summary.setLastUpdateTime(lastUpdateTime);
                }

                Date createTime = rs.getDateTime("create_time");
                if (summary.getCreateTime() == null || summary.getCreateTime().after(createTime)) {
                    summary.setCreateTime(createTime);
                }

                summary.setReloadBatchCount(summary.getReloadBatchCount() + count);

                if (status == Status.OK || status == Status.IG) {
                    summary.setFinishedBatchCount(summary.getFinishedBatchCount() + count);
                } else {
                    summary.setPendingBatchCount(summary.getPendingBatchCount() + count);

                    boolean inError = rs.getBoolean("error_flag");
                    summary.setInError(inError || summary.isInError());

                    if (status != Status.NE && count == 1) {
                        summary.setCurrentBatchId(rs.getLong("current_batch_id"));
                        summary.setCurrentDataEventCount(rs.getLong("current_data_event_count"));
                    }

                }
                return null;
            }
        });

        List<OutgoingLoadSummary> loads = new ArrayList<OutgoingLoadSummary>(loadSummaries.values());
        Iterator<OutgoingLoadSummary> it = loads.iterator();
        while (it.hasNext()) {
            OutgoingLoadSummary loadSummary = it.next();
            if (activeOnly && !loadSummary.isActive()) {
                it.remove();
            }
        }

        return loads;
    }

    class OutgoingBatchByNodeChannelCountMapper implements ISqlRowMapper<OutgoingBatchByNodeChannelCount> {
        @Override
        public OutgoingBatchByNodeChannelCount mapRow(Row row) {
            OutgoingBatchByNodeChannelCount count = new OutgoingBatchByNodeChannelCount();
            count.setBatchCount(row.getInt("batch_count"));
            count.setChannelId(row.getString("channel_id"));
            count.setDataCount(row.getInt("data_event_count"));
            count.setEarliestCreateTime(row.getDateTime("earliest_create_time"));
            count.setInError(row.getBoolean("error_flag"));
            count.setLatestUpdateTime(row.getDateTime("latest_update_time"));
            count.setMaxSentCount(row.getInt("sent_count"));
            count.setNodeId(row.getString("node_id"));
            return count;
        }
    }

    class OutgoingBatchSummaryMapper implements ISqlRowMapper<OutgoingBatchSummary> {
        public OutgoingBatchSummary mapRow(Row rs) {
            OutgoingBatchSummary summary = new OutgoingBatchSummary();
            summary.setBatchCount(rs.getInt("batches"));
            summary.setDataCount(rs.getInt("data"));
            summary.setStatus(Status.valueOf(rs.getString("status")));
            summary.setNodeId(rs.getString("node_id"));
            summary.setOldestBatchCreateTime(rs.getDateTime("oldest_batch_time"));
            summary.setLatestUpdateTime(rs.getDateTime("latest_update_time"));
            return summary;
        }
    }

    class OutgoingBatchMapper implements ISqlRowMapper<OutgoingBatch> {

        private boolean statusOnly = false;
        private boolean includeDisabledChannels = false;
        private Map<String, Channel> channels;

        public OutgoingBatchMapper(boolean includeDisabledChannels, boolean statusOnly) {
            this.includeDisabledChannels = includeDisabledChannels;
            this.statusOnly = statusOnly;
            this.channels = configurationService.getChannels(false);
        }

        public OutgoingBatchMapper(boolean includeDisabledChannels) {
            this(includeDisabledChannels, false);
        }

        public OutgoingBatch mapRow(Row rs) {
            String channelId = rs.getString("channel_id");
            Channel channel = channels.get(channelId);
            if (channel != null && (includeDisabledChannels || channel.isEnabled())) {
                OutgoingBatch batch = new OutgoingBatch();
                batch.setNodeId(rs.getString("node_id"));
                batch.setStatus(rs.getString("status"));
                batch.setBatchId(rs.getLong("batch_id"));
                if (!statusOnly) {
                    batch.setChannelId(channelId);
                    batch.setByteCount(rs.getLong("byte_count"));
                    batch.setExtractCount(rs.getLong("extract_count"));
                    batch.setSentCount(rs.getLong("sent_count"));
                    batch.setLoadCount(rs.getLong("load_count"));
                    batch.setDataEventCount(rs.getLong("data_event_count"));
                    batch.setReloadEventCount(rs.getLong("reload_event_count"));
                    batch.setInsertEventCount(rs.getLong("insert_event_count"));
                    batch.setUpdateEventCount(rs.getLong("update_event_count"));
                    batch.setDeleteEventCount(rs.getLong("delete_event_count"));
                    batch.setOtherEventCount(rs.getLong("other_event_count"));
                    batch.setIgnoreCount(rs.getLong("ignore_count"));
                    batch.setRouterMillis(rs.getLong("router_millis"));
                    batch.setNetworkMillis(rs.getLong("network_millis"));
                    batch.setFilterMillis(rs.getLong("filter_millis"));
                    batch.setLoadMillis(rs.getLong("load_millis"));
                    batch.setExtractMillis(rs.getLong("extract_millis"));
                    batch.setSqlState(rs.getString("sql_state"));
                    batch.setSqlCode(rs.getInt("sql_code"));
                    batch.setSqlMessage(rs.getString("sql_message"));
                    batch.setFailedDataId(rs.getLong("failed_data_id"));
                    batch.setLastUpdatedHostName(rs.getString("last_update_hostname"));
                    batch.setLastUpdatedTime(rs.getDateTime("last_update_time"));
                    batch.setCreateTime(rs.getDateTime("create_time"));
                    batch.setLoadFlag(rs.getBoolean("load_flag"));
                    batch.setErrorFlag(rs.getBoolean("error_flag"));
                    batch.setCommonFlag(rs.getBoolean("common_flag"));
                    batch.setExtractJobFlag(rs.getBoolean("extract_job_flag"));
                    batch.setLoadId(rs.getLong("load_id"));
                    batch.setCreateBy(rs.getString("create_by"));
                }
                return batch;
            } else {
                return null;
            }
        }
    }

}