// This software is released into the Public Domain. See copying.txt for details. package org.openstreetmap.osmosis.apidb.v0_6.impl; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.util.Iterator; import java.util.LinkedHashSet; import java.util.Set; import org.openstreetmap.osmosis.apidb.common.DatabaseContext; import org.openstreetmap.osmosis.core.OsmosisConstants; import org.openstreetmap.osmosis.core.OsmosisRuntimeException; import org.openstreetmap.osmosis.core.database.ReleasableStatementContainer; import org.openstreetmap.osmosis.core.domain.v0_6.OsmUser; import org.openstreetmap.osmosis.core.lifecycle.Closeable; import org.openstreetmap.osmosis.core.lifecycle.ReleasableContainer; import org.openstreetmap.osmosis.core.util.FixedPrecisionCoordinateConvertor; /** * Creates and maintains changesets for a database conversation. This will create a separate * changeset for each user id making changes. * * @author Brett Henderson */ public class ChangesetManager implements Closeable { private static final int MAX_CHANGESET_ID_CACHE_SIZE = 32768; private static final String SQL_INSERT_CHANGESET = "INSERT INTO changesets" + " (id, user_id, created_at, min_lat, max_lat, min_lon, max_lon, closed_at, num_changes)" + " VALUES" + " (?, ?, NOW(), " + FixedPrecisionCoordinateConvertor.convertToFixed(-90) + ", " + FixedPrecisionCoordinateConvertor.convertToFixed(90) + ", " + FixedPrecisionCoordinateConvertor.convertToFixed(-180) + ", " + FixedPrecisionCoordinateConvertor.convertToFixed(180) + ", NOW(), 0)"; private static final String SQL_INSERT_CHANGESET_TAG = "INSERT INTO changeset_tags (changeset_id, k, v)" + " VALUES (?, 'created_by', 'Osmosis " + OsmosisConstants.VERSION + "'), (?, 'replication', 'true')"; private static final String SQL_SELECT_CHANGESET_COUNT = "SELECT Count(*) AS changesetCount FROM changesets WHERE id = ?"; private final DatabaseContext dbCtx; private final ReleasableContainer releasableContainer; private final ReleasableStatementContainer statementContainer; private PreparedStatement insertStatement; private PreparedStatement insertTagStatement; private PreparedStatement selectCountStatement; private Set<Long> knownChangesetIds; /** * Creates a new instance. * * @param dbCtx The database context to use for all database access. */ public ChangesetManager(DatabaseContext dbCtx) { this.dbCtx = dbCtx; releasableContainer = new ReleasableContainer(); statementContainer = new ReleasableStatementContainer(); releasableContainer.add(statementContainer); knownChangesetIds = new LinkedHashSet<Long>(32768); } private int readChangesetCount(ResultSet countSet) { try (ResultSet resultSet = countSet) { resultSet.next(); return resultSet.getInt("changesetCount"); } catch (SQLException e) { throw new OsmosisRuntimeException("Unable to read the changeset count.", e); } } private boolean doesChangesetExist(long changesetId) { if (knownChangesetIds.contains(changesetId)) { return true; } if (selectCountStatement == null) { selectCountStatement = statementContainer.add(dbCtx .prepareStatementForStreaming(SQL_SELECT_CHANGESET_COUNT)); } try { int prmIndex; boolean changesetExists; // Check if the changeset exists. prmIndex = 1; selectCountStatement.setLong(prmIndex++, changesetId); changesetExists = readChangesetCount(selectCountStatement.executeQuery()) > 0; return changesetExists; } catch (SQLException e) { throw new OsmosisRuntimeException("Unable to check if a changeset " + changesetId + " exists.", e); } } private void addChangeset(long changesetId, long userId) { if (insertStatement == null) { insertStatement = statementContainer.add(dbCtx.prepareStatement(SQL_INSERT_CHANGESET)); insertTagStatement = statementContainer.add(dbCtx.prepareStatement(SQL_INSERT_CHANGESET_TAG)); } try { int prmIndex; // Insert the new changeset record. prmIndex = 1; insertStatement.setLong(prmIndex++, changesetId); insertStatement.setLong(prmIndex++, userId); insertStatement.executeUpdate(); // Insert the changeset tags. prmIndex = 1; insertTagStatement.setLong(prmIndex++, changesetId); insertTagStatement.setLong(prmIndex++, changesetId); insertTagStatement.executeUpdate(); // Add the changeset to the cache, and trim the cache if required. knownChangesetIds.add(changesetId); if (knownChangesetIds.size() > MAX_CHANGESET_ID_CACHE_SIZE) { Iterator<Long> i = knownChangesetIds.iterator(); i.next(); i.remove(); } } catch (SQLException e) { throw new OsmosisRuntimeException("Unable to insert a new changeset for user with id " + userId + ".", e); } } /** * Checks to see if the changeset already exists and adds it if it doesn't. * * @param changesetId * The changeset identifier. * @param user * The user who created the changeset. */ public void addChangesetIfRequired(long changesetId, OsmUser user) { if (!doesChangesetExist(changesetId)) { addChangeset(changesetId, user.getId()); } } /** * {@inheritDoc} */ @Override public void close() { releasableContainer.close(); } }