/** * 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.io.data.writer; import org.apache.commons.lang.StringUtils; import org.jumpmind.symmetric.io.data.CsvData; import org.jumpmind.symmetric.io.data.DataEventType; import org.jumpmind.symmetric.io.data.writer.AbstractDatabaseWriter.LoadStatus; import org.jumpmind.symmetric.io.data.writer.Conflict.DetectConflict; import org.jumpmind.util.Statistics; import org.slf4j.Logger; import org.slf4j.LoggerFactory; abstract public class AbstractDatabaseWriterConflictResolver implements IDatabaseWriterConflictResolver { protected Logger log = LoggerFactory.getLogger(AbstractDatabaseWriterConflictResolver.class); public void needsResolved(AbstractDatabaseWriter writer, CsvData data, LoadStatus loadStatus) { DataEventType originalEventType = data.getDataEventType(); DatabaseWriterSettings writerSettings = writer.getWriterSettings(); Conflict conflict = writerSettings.pickConflict(writer.getTargetTable(), writer.getBatch()); Statistics statistics = writer.getStatistics().get(writer.getBatch()); long statementCount = statistics.get(DataWriterStatisticConstants.STATEMENTCOUNT); long lineNumber = statistics.get(DataWriterStatisticConstants.LINENUMBER); ResolvedData resolvedData = writerSettings.getResolvedData(statementCount); logConflictHappened(conflict, data, writer, resolvedData, lineNumber); switch (originalEventType) { case INSERT: if (resolvedData!=null) { attemptToResolve(resolvedData, data, writer, conflict); } else { switch (conflict.getResolveType()) { case FALLBACK: performFallbackToUpdate(writer, data, conflict, true); break; case NEWER_WINS: if ((conflict.getDetectType() == DetectConflict.USE_TIMESTAMP && isTimestampNewer( conflict, writer, data)) || (conflict.getDetectType() == DetectConflict.USE_VERSION && isVersionNewer( conflict, writer, data))) { performFallbackToUpdate(writer, data, conflict, true); } else { if (!conflict.isResolveRowOnly()) { throw new IgnoreBatchException(); } } break; case IGNORE: ignore(writer, conflict); break; case MANUAL: default: attemptToResolve(resolvedData, data, writer, conflict); break; } } break; case UPDATE: if (resolvedData!=null) { attemptToResolve(resolvedData, data, writer, conflict); } else { switch (conflict.getResolveType()) { case FALLBACK: if (conflict.getDetectType() == DetectConflict.USE_PK_DATA) { CsvData withoutOldData = data.copyWithoutOldData(); try { // we already tried to update using the pk performFallbackToInsert(writer, withoutOldData, conflict, true); } catch (ConflictException ex) { performFallbackToUpdate(writer, withoutOldData, conflict, true); } } else { try { performFallbackToUpdate(writer, data, conflict, true); } catch (ConflictException ex) { performFallbackToInsert(writer, data, conflict, true); } } break; case NEWER_WINS: if ((conflict.getDetectType() == DetectConflict.USE_TIMESTAMP && isTimestampNewer( conflict, writer, data)) || (conflict.getDetectType() == DetectConflict.USE_VERSION && isVersionNewer( conflict, writer, data))) { try { performFallbackToUpdate(writer, data, conflict, false); } catch (ConflictException ex) { performFallbackToInsert(writer, data, conflict, true); } } else { if (!conflict.isResolveRowOnly()) { throw new IgnoreBatchException(); } } break; case IGNORE: ignore(writer, conflict); break; case MANUAL: default: attemptToResolve(resolvedData, data, writer, conflict); break; } } break; case DELETE: switch (conflict.getResolveType()) { case FALLBACK: LoadStatus status = LoadStatus.CONFLICT; if (conflict.getDetectType() != DetectConflict.USE_PK_DATA) { status = writer.delete(data, false); } if (status == LoadStatus.CONFLICT) { writer.getStatistics().get(writer.getBatch()) .increment(DataWriterStatisticConstants.MISSINGDELETECOUNT); } break; case IGNORE: ignore(writer, conflict); break; case NEWER_WINS: // nothing to do ... break; case MANUAL: default: if (resolvedData != null) { if (!resolvedData.isIgnoreRow()) { writer.delete(data, false); } else { if (!conflict.isResolveRowOnly()) { throw new IgnoreBatchException(); } } } else { throw new ConflictException(data, writer.getTargetTable(), false, conflict, (Exception) writer.getContext().get(AbstractDatabaseWriter.CONFLICT_ERROR)); } break; } break; default: break; } } protected void beforeResolutionAttempt(Conflict conflict) { } protected void afterResolutionAttempt(Conflict conflict) { } protected void logConflictHappened(Conflict conflict, CsvData data, AbstractDatabaseWriter writer, ResolvedData resolvedData, long lineNumber) { if (log.isDebugEnabled()) { log.debug("Conflict detected: {} in batch {} at line {} for table {}", new Object[] { conflict.getConflictId() == null ? "default" : conflict.getConflictId(), writer.getBatch().getBatchId(), lineNumber, writer.getTargetTable().getFullyQualifiedTableName() }); String csvData = data.getCsvData(CsvData.ROW_DATA); if (StringUtils.isNotBlank(csvData)) { log.debug("Row data: {}", csvData); } csvData = data.getCsvData(CsvData.OLD_DATA); if (StringUtils.isNotBlank(csvData)) { log.debug("Old data: {}", csvData); } csvData = resolvedData != null ? resolvedData.getResolvedData() : null; if (StringUtils.isNotBlank(csvData)) { log.debug("Resolve data: {}", csvData); } } } protected void ignore(AbstractDatabaseWriter writer, Conflict conflict) { if (conflict.isResolveRowOnly()) { writer.getStatistics().get(writer.getBatch()) .increment(DataWriterStatisticConstants.IGNORECOUNT); } else { throw new IgnoreBatchException(); } } protected void attemptToResolve(ResolvedData resolvedData, CsvData data, AbstractDatabaseWriter writer, Conflict conflict) { if (resolvedData != null) { if (!resolvedData.isIgnoreRow()) { data.putCsvData(CsvData.ROW_DATA, resolvedData.getResolvedData()); try { performFallbackToUpdate(writer, data, conflict, true); } catch (ConflictException ex) { performFallbackToInsert(writer, data, conflict, true); } } } else { throw new ConflictException(data, writer.getTargetTable(), false, conflict, (Exception) writer.getContext().get(AbstractDatabaseWriter.CONFLICT_ERROR)); } } abstract protected boolean isTimestampNewer(Conflict conflict, AbstractDatabaseWriter writer, CsvData data); abstract protected boolean isVersionNewer(Conflict conflict, AbstractDatabaseWriter writer, CsvData data); protected void performFallbackToUpdate(AbstractDatabaseWriter writer, CsvData data, Conflict conflict, boolean retransform) { try { beforeResolutionAttempt(conflict); LoadStatus loadStatus = writer.update(data, conflict.isResolveChangesOnly(), false); if (loadStatus != LoadStatus.SUCCESS) { throw new ConflictException(data, writer.getTargetTable(), true, conflict, (Exception) writer.getContext().get(AbstractDatabaseWriter.CONFLICT_ERROR)); } else { writer.getStatistics().get(writer.getBatch()) .increment(DataWriterStatisticConstants.FALLBACKUPDATECOUNT); } } finally { afterResolutionAttempt(conflict); } } protected void performFallbackToInsert(AbstractDatabaseWriter writer, CsvData csvData, Conflict conflict, boolean retransform) { try { beforeResolutionAttempt(conflict); LoadStatus loadStatus = writer.insert(csvData); if (loadStatus != LoadStatus.SUCCESS) { throw new ConflictException(csvData, writer.getTargetTable(), true, conflict, (Exception) writer.getContext().get(AbstractDatabaseWriter.CONFLICT_ERROR)); } else { writer.getStatistics().get(writer.getBatch()) .increment(DataWriterStatisticConstants.FALLBACKINSERTCOUNT); } } finally { afterResolutionAttempt(conflict); } } }