// This software is released into the Public Domain. See copying.txt for details. package org.openstreetmap.osmosis.replication.v0_6; import java.io.File; import java.util.Map; import java.util.logging.Level; import java.util.logging.Logger; import org.openstreetmap.osmosis.core.OsmosisRuntimeException; import org.openstreetmap.osmosis.core.container.v0_6.ChangeContainer; import org.openstreetmap.osmosis.core.task.v0_6.ChangeSink; import org.openstreetmap.osmosis.core.util.FileBasedLock; import org.openstreetmap.osmosis.core.util.PropertiesPersister; import org.openstreetmap.osmosis.replication.common.ReplicationState; /** * This class manages persistence of state for replication streams into a state * properties file. If used alone, it will store the state for a replication * pipeline, and will discard the output. It can be used within a larger task * performing processing on the replication data. It supports the initialize and * complete method being called multiple times to signify multiple replication * intervals being called within a single pipeline run. * * @author Brett Henderson */ public class ReplicationStateWriter implements ChangeSink { private static final Logger LOG = Logger.getLogger(ReplicationStateWriter.class.getName()); private static final String LOCK_FILE = "replicate.lock"; private static final String STATE_FILE = "state.txt"; private FileBasedLock fileLock; private boolean lockObtained; private PropertiesPersister statePersistor; private ReplicationState state; /** * Creates a new instance. * * @param workingDirectory * The directory containing configuration and tracking files. */ public ReplicationStateWriter(File workingDirectory) { // Create the lock object used to ensure only a single process attempts // to write to the data directory. fileLock = new FileBasedLock(new File(workingDirectory, LOCK_FILE)); // Create the object used to persist current state. statePersistor = new PropertiesPersister(new File(workingDirectory, STATE_FILE)); } @Override public void initialize(Map<String, Object> metaData) { if (lockObtained) { throw new OsmosisRuntimeException("initialize has already been called"); } // Lock the working directory. fileLock.lock(); lockObtained = true; // Get the replication state from the upstream task. if (!metaData.containsKey(ReplicationState.META_DATA_KEY)) { throw new OsmosisRuntimeException("No replication state has been provided in metadata key " + ReplicationState.META_DATA_KEY + "."); } state = (ReplicationState) metaData.get(ReplicationState.META_DATA_KEY); // Populate the state from the existing state if it exists. if (statePersistor.exists()) { state.load(statePersistor.loadMap()); // The current sequence number must now be incremented. state.setSequenceNumber(state.getSequenceNumber() + 1); if (LOG.isLoggable(Level.FINER)) { LOG.finer("Replication sequence number is " + state.getSequenceNumber() + "."); } } } @Override public void process(ChangeContainer change) { if (!lockObtained) { throw new OsmosisRuntimeException("initialize has not been called"); } if (state.getSequenceNumber() == 0) { throw new OsmosisRuntimeException("No changes can be included for replication sequence 0."); } } @Override public void complete() { if (!lockObtained) { throw new OsmosisRuntimeException("initialize has not been called"); } // Write the global state file. statePersistor.store(state.store()); state = null; // Release the lock. fileLock.unlock(); lockObtained = false; } @Override public void close() { state = null; fileLock.close(); lockObtained = false; } }