/** * 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.Date; import java.util.HashMap; import java.util.List; import java.util.Map; 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.UniqueKeyException; import org.jumpmind.db.sql.mapper.DateMapper; import org.jumpmind.db.sql.mapper.StringMapper; import org.jumpmind.symmetric.common.ParameterConstants; import org.jumpmind.symmetric.db.ISymmetricDialect; import org.jumpmind.symmetric.model.BatchId; import org.jumpmind.symmetric.model.IncomingBatch; import org.jumpmind.symmetric.model.IncomingBatch.Status; import org.jumpmind.symmetric.service.IClusterService; import org.jumpmind.symmetric.service.IIncomingBatchService; import org.jumpmind.symmetric.service.IParameterService; import org.jumpmind.util.FormatUtils; /** * @see IIncomingBatchService */ public class IncomingBatchService extends AbstractService implements IIncomingBatchService { protected IClusterService clusterService; @Override public List<String> getNodesInError() { return sqlTemplate.query(getSql("selectNodesInErrorSql"), new StringMapper()); } public IncomingBatchService(IParameterService parameterService, ISymmetricDialect symmetricDialect, IClusterService clusterService) { super(parameterService, symmetricDialect); this.clusterService = clusterService; setSqlMap(new IncomingBatchServiceSqlMap(symmetricDialect.getPlatform(), createSqlReplacementTokens())); } public void refreshIncomingBatch(IncomingBatch batch) { sqlTemplate.queryForObject( getSql("selectIncomingBatchPrefixSql", "findIncomingBatchSql"), new IncomingBatchMapper(batch), batch.getBatchId(), batch.getNodeId()); } public IncomingBatch findIncomingBatch(long batchId, String nodeId) { if (nodeId != null) { return sqlTemplate.queryForObject( getSql("selectIncomingBatchPrefixSql", "findIncomingBatchSql"), new IncomingBatchMapper(), batchId, nodeId); } else { return sqlTemplate.queryForObject( getSql("selectIncomingBatchPrefixSql", "findIncomingBatchByBatchIdSql"), new IncomingBatchMapper(), batchId); } } public int countIncomingBatchesInError() { return sqlTemplate.queryForInt(getSql("countIncomingBatchesErrorsSql")); } public int countIncomingBatchesInError(String channelId) { return sqlTemplate.queryForInt(getSql("countIncomingBatchesErrorsOnChannelSql"), channelId); } public List<IncomingBatch> findIncomingBatchErrors(int maxRows) { return sqlTemplate.query( getSql("selectIncomingBatchPrefixSql", "findIncomingBatchErrorsSql"), maxRows, new IncomingBatchMapper()); } public void markIncomingBatchesOk(String nodeId) { List<IncomingBatch> batches = listIncomingBatchesInErrorFor(nodeId); for (IncomingBatch incomingBatch : batches) { if (isRecordOkBatchesEnabled()) { incomingBatch.setErrorFlag(false); incomingBatch.setStatus(Status.OK); updateIncomingBatch(incomingBatch); } else { deleteIncomingBatch(incomingBatch); } } } public void removingIncomingBatches(String nodeId) { sqlTemplate.update(getSql("deleteIncomingBatchByNodeSql"), nodeId); } public List<IncomingBatch> listIncomingBatchesInErrorFor(String nodeId) { return sqlTemplate.query( getSql("selectIncomingBatchPrefixSql", "listIncomingBatchesInErrorForNodeSql"), new IncomingBatchMapper(), nodeId); } @SuppressWarnings("deprecation") public boolean isRecordOkBatchesEnabled() { boolean enabled = true; if (!parameterService.is(ParameterConstants.INCOMING_BATCH_RECORD_OK_ENABLED, true)) { enabled = false; } if (parameterService.is(ParameterConstants.INCOMING_BATCH_DELETE_ON_LOAD, false)) { enabled = false; } return enabled; } public List<Date> listIncomingBatchTimes(List<String> nodeIds, List<String> channels, List<IncomingBatch.Status> statuses, boolean ascending) { String whereClause = 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 sql = getSql("selectCreateTimePrefixSql", whereClause, ascending ? " order by create_time" : " order by create_time desc"); return sqlTemplate.query(sql, new DateMapper(), params); } public List<IncomingBatch> listIncomingBatches(List<String> nodeIds, List<String> channels, List<IncomingBatch.Status> statuses, Date startAtCreateTime, final int maxRowsToRetrieve, boolean ascending) { Map<String, Object> params = new HashMap<String, Object>(); params.put("NODES", nodeIds); params.put("CHANNELS", channels); params.put("STATUSES", toStringList(statuses)); params.put("CREATE_TIME", startAtCreateTime); String where = buildBatchWhere(nodeIds, channels, statuses); String createTimeLimiter = ""; if (startAtCreateTime != null) { if (StringUtils.isBlank(where)) { where = " where 1=1 "; } createTimeLimiter = " and create_time " + (ascending ? ">=" : "<=") + " :CREATE_TIME"; } String sql = getSql("selectIncomingBatchPrefixSql", where, createTimeLimiter, ascending ? " order by create_time" : " order by create_time desc"); return sqlTemplate.query(sql, maxRowsToRetrieve, new IncomingBatchMapper(), params); } protected boolean containsOnlyErrorStatus(List<IncomingBatch.Status> statuses) { return statuses.size() == 1 && statuses.get(0) == IncomingBatch.Status.ER; } protected List<String> toStringList(List<IncomingBatch.Status> statuses) { List<String> statusStrings = new ArrayList<String>(statuses.size()); for (Status status : statuses) { statusStrings.add(status.name()); } return statusStrings; } public boolean acquireIncomingBatch(IncomingBatch batch) { boolean okayToProcess = true; if (batch.isPersistable()) { IncomingBatch existingBatch = null; if (isRecordOkBatchesEnabled()) { try { insertIncomingBatch(batch); } catch (UniqueKeyException e) { batch.setRetry(true); existingBatch = findIncomingBatch(batch.getBatchId(), batch.getNodeId()); } } else { existingBatch = findIncomingBatch(batch.getBatchId(), batch.getNodeId()); if (existingBatch != null) { batch.setRetry(true); } } if (batch.isRetry()) { if (existingBatch.getStatus() == Status.ER || existingBatch.getStatus() == Status.LD || !parameterService .is(ParameterConstants.INCOMING_BATCH_SKIP_DUPLICATE_BATCHES_ENABLED)) { okayToProcess = true; existingBatch.setStatus(Status.LD); log.info("Retrying batch {}", batch.getNodeBatchId()); } else if (existingBatch.getStatus() == Status.IG) { okayToProcess = false; batch.setStatus(Status.OK); batch.incrementIgnoreCount(); existingBatch.setStatus(Status.OK); existingBatch.incrementIgnoreCount(); log.info("Ignoring batch {}", batch.getNodeBatchId()); } else { okayToProcess = false; batch.setStatus(existingBatch.getStatus()); batch.setByteCount(existingBatch.getByteCount()); batch.setDatabaseMillis(existingBatch.getDatabaseMillis()); batch.setNetworkMillis(existingBatch.getNetworkMillis()); batch.setFilterMillis(existingBatch.getFilterMillis()); batch.setSkipCount(existingBatch.getSkipCount() + 1); batch.setStatementCount(existingBatch.getStatementCount()); existingBatch.setSkipCount(existingBatch.getSkipCount() + 1); log.info("Skipping batch {}", batch.getNodeBatchId()); } updateIncomingBatch(existingBatch); } } return okayToProcess; } public void insertIncomingBatch(ISqlTransaction transaction, IncomingBatch batch) { if (batch.isPersistable()) { batch.setLastUpdatedHostName(clusterService.getServerId()); batch.setLastUpdatedTime(new Date()); transaction.prepareAndExecute( getSql("insertIncomingBatchSql"), new Object[] { batch.getBatchId(), batch.getNodeId(), batch.getChannelId(), batch.getStatus().name(), batch.getNetworkMillis(), batch.getFilterMillis(), batch.getDatabaseMillis(), batch.getFailedRowNumber(), batch.getFailedLineNumber(), batch.getByteCount(), batch.getStatementCount(), batch.getFallbackInsertCount(), batch.getFallbackUpdateCount(), batch.getIgnoreCount(), batch.getMissingDeleteCount(), batch.getSkipCount(), batch.getSqlState(), batch.getSqlCode(), FormatUtils.abbreviateForLogging(batch.getSqlMessage()), batch.getLastUpdatedHostName(), batch.getLastUpdatedTime() }, new int[] { Types.NUMERIC, Types.VARCHAR, Types.VARCHAR, Types.CHAR, Types.NUMERIC, Types.NUMERIC, Types.NUMERIC, Types.NUMERIC, Types.NUMERIC, Types.NUMERIC, Types.NUMERIC, Types.NUMERIC, Types.NUMERIC, Types.NUMERIC, Types.NUMERIC, Types.NUMERIC, Types.VARCHAR, Types.NUMERIC, Types.VARCHAR, Types.VARCHAR, Types.TIMESTAMP }); } } public void insertIncomingBatch(IncomingBatch batch) { ISqlTransaction transaction = null; try { transaction = sqlTemplate.startSqlTransaction(); insertIncomingBatch(transaction, batch); 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 int deleteIncomingBatch(IncomingBatch batch) { return sqlTemplate.update(getSql("deleteIncomingBatchSql"), new Object[] { batch.getBatchId(), batch.getNodeId() }, new int[] { symmetricDialect.getSqlTypeForIds(), Types.VARCHAR }); } public int updateIncomingBatch(IncomingBatch batch) { ISqlTransaction transaction = null; try { transaction = sqlTemplate.startSqlTransaction(); int count = updateIncomingBatch(transaction, batch); transaction.commit(); return count; } catch (Error ex) { if (transaction != null) { transaction.rollback(); } throw ex; } catch (RuntimeException ex) { if (transaction != null) { transaction.rollback(); } throw ex; } finally { close(transaction); } } public int updateIncomingBatch(ISqlTransaction transaction , IncomingBatch batch) { int count = 0; if (batch.isPersistable()) { if (batch.getStatus() == IncomingBatch.Status.ER) { batch.setErrorFlag(true); } else if (batch.getStatus() == IncomingBatch.Status.OK) { batch.setErrorFlag(false); } batch.setLastUpdatedHostName(clusterService.getServerId()); batch.setLastUpdatedTime(new Date()); count = transaction.prepareAndExecute( getSql("updateIncomingBatchSql"), new Object[] { batch.getStatus().name(), batch.isErrorFlag() ? 1 : 0, batch.getNetworkMillis(), batch.getFilterMillis(), batch.getDatabaseMillis(), batch.getFailedRowNumber(), batch.getFailedLineNumber(), batch.getByteCount(), batch.getStatementCount(), batch.getFallbackInsertCount(), batch.getFallbackUpdateCount(), batch.getIgnoreCount(), batch.getMissingDeleteCount(), batch.getSkipCount(), batch.getSqlState(), batch.getSqlCode(), FormatUtils.abbreviateForLogging(batch.getSqlMessage()), batch.getLastUpdatedHostName(), batch.getLastUpdatedTime(), batch.getBatchId(), batch.getNodeId() }, new int[] { Types.CHAR, Types.SMALLINT, Types.NUMERIC, Types.NUMERIC, Types.NUMERIC, Types.NUMERIC, Types.NUMERIC, Types.NUMERIC, Types.NUMERIC, Types.NUMERIC, Types.NUMERIC, Types.NUMERIC, Types.NUMERIC, Types.NUMERIC, Types.VARCHAR, Types.NUMERIC, Types.VARCHAR, Types.VARCHAR, Types.TIMESTAMP, symmetricDialect.getSqlTypeForIds(), Types.VARCHAR }); } return count; } public Map<String, BatchId> findMaxBatchIdsByChannel() { Map<String, BatchId> ids = new HashMap<String, BatchId>(); sqlTemplate.query(getSql("maxBatchIdsSql"), new BatchIdMapper(ids), IncomingBatch.Status.OK.name()); return ids; } class BatchIdMapper implements ISqlRowMapper<BatchId> { Map<String, BatchId> ids; public BatchIdMapper(Map<String, BatchId> ids) { this.ids = ids; } public BatchId mapRow(Row rs) { BatchId batch = new BatchId(); batch.setBatchId(rs.getLong("batch_id")); batch.setNodeId(rs.getString("node_id")); ids.put(rs.getString("channel_id"), batch); return batch; } } class IncomingBatchMapper implements ISqlRowMapper<IncomingBatch> { IncomingBatch batchToRefresh = null; public IncomingBatchMapper(IncomingBatch batchToRefresh) { this.batchToRefresh = batchToRefresh; } public IncomingBatchMapper() { } public IncomingBatch mapRow(Row rs) { IncomingBatch batch = batchToRefresh != null ? batchToRefresh : new IncomingBatch(); batch.setBatchId(rs.getLong("batch_id")); batch.setNodeId(rs.getString("node_id")); batch.setChannelId(rs.getString("channel_id")); batch.setStatus(IncomingBatch.Status.valueOf(rs.getString("status"))); batch.setNetworkMillis(rs.getLong("network_millis")); batch.setFilterMillis(rs.getLong("filter_millis")); batch.setDatabaseMillis(rs.getLong("database_millis")); batch.setFailedRowNumber(rs.getLong("failed_row_number")); batch.setFailedLineNumber(rs.getLong("failed_line_number")); batch.setByteCount(rs.getLong("byte_count")); batch.setStatementCount(rs.getLong("statement_count")); batch.setFallbackInsertCount(rs.getLong("fallback_insert_count")); batch.setFallbackUpdateCount(rs.getLong("fallback_update_count")); batch.setIgnoreCount(rs.getLong("ignore_count")); batch.setMissingDeleteCount(rs.getLong("missing_delete_count")); batch.setSkipCount(rs.getLong("skip_count")); batch.setSqlState(rs.getString("sql_state")); batch.setSqlCode(rs.getInt("sql_code")); batch.setSqlMessage(rs.getString("sql_message")); batch.setLastUpdatedHostName(rs.getString("last_update_hostname")); batch.setLastUpdatedTime(rs.getDateTime("last_update_time")); batch.setCreateTime(rs.getDateTime("create_time")); batch.setErrorFlag(rs.getBoolean("error_flag")); return batch; } } }