// This software is released into the Public Domain. See copying.txt for details. package org.openstreetmap.osmosis.replicationhttp.v0_6; 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.task.v0_6.ChangeSinkChangeSource; import org.openstreetmap.osmosis.replication.common.ReplicationState; import org.openstreetmap.osmosis.replicationhttp.v0_6.impl.SequenceNumberServerChannelPipelineFactory; import org.openstreetmap.osmosis.replicationhttp.v0_6.impl.SequenceServer; /** * This task creates a HTTP server that sends updated replication sequence * numbers to clients. It is notified of updated sequence numbers as they occur * by being inserted into the middle of a replication pipeline. * * @author Brett Henderson */ public class ReplicationSequenceServer implements ChangeSinkChangeSource { private static final Logger LOG = Logger.getLogger(ReplicationSequenceServer.class.getName()); private ChangeSink changeSink; private ReplicationState state; private long sequenceNumber; private SequenceServer server; private boolean serverStarted; /** * Creates a new instance. * * @param port * The port to listen on. */ public ReplicationSequenceServer(int port) { server = new SequenceServer(port, new SequenceNumberServerChannelPipelineFactory()); serverStarted = false; } @Override public void setChangeSink(ChangeSink changeSink) { this.changeSink = changeSink; } /** * Returns the port that is being used to listen for new connections. * * @return The port number. */ public int getPort() { return server.getPort(); } @Override public void initialize(Map<String, Object> metaData) { // 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); // Call the downstream initialize which will among other things // initialise the state. changeSink.initialize(metaData); // We must only read from the state object during initialize and // complete because it may be updated by other threads at other times. sequenceNumber = state.getSequenceNumber(); // If the sequence id is still 0 then replication hasn't been fully // initialized and we can't start the server yet. if (sequenceNumber > 0 && !serverStarted) { // We can start the server now. We give it the previous sequence // number because the current one is still in progress. server.start(sequenceNumber - 1); serverStarted = true; } } @Override public void process(ChangeContainer change) { changeSink.process(change); } @Override public void complete() { changeSink.complete(); // The sink has completed persisting the replication so now we must // notify the server which will notify the listening clients. if (!serverStarted) { server.start(sequenceNumber); serverStarted = true; } else { server.update(sequenceNumber); } } @Override public void close() { changeSink.close(); if (serverStarted) { try { server.stop(); } catch (RuntimeException e) { LOG.log(Level.WARNING, "Replication sequence server stop failed.", e); } serverStarted = false; } } }