// This software is released into the Public Domain. See copying.txt for details. package org.openstreetmap.osmosis.pgsnapshot.v0_6.impl; import java.util.HashMap; import java.util.List; import java.util.Map; import org.openstreetmap.osmosis.core.database.FeaturePopulator; import org.openstreetmap.osmosis.core.database.SortingStoreRowMapperListener; import org.openstreetmap.osmosis.core.domain.v0_6.Entity; import org.openstreetmap.osmosis.core.lifecycle.ReleasableContainer; import org.openstreetmap.osmosis.core.lifecycle.ReleasableIterator; import org.openstreetmap.osmosis.core.sort.common.FileBasedSort; import org.openstreetmap.osmosis.core.sort.v0_6.EntityByTypeThenIdComparator; import org.openstreetmap.osmosis.core.sort.v0_6.EntitySubClassComparator; import org.openstreetmap.osmosis.core.store.SingleClassObjectSerializationFactory; import org.openstreetmap.osmosis.core.store.StoreReleasingIterator; import org.openstreetmap.osmosis.pgsnapshot.common.NoSuchRecordException; import org.openstreetmap.osmosis.pgsnapshot.common.RowMapperRowCallbackListener; import org.springframework.dao.EmptyResultDataAccessException; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; /** * Provides functionality common to all top level entity daos. * * @author Brett Henderson * @param <T> * The entity type to be supported. */ public abstract class EntityDao<T extends Entity> { private JdbcTemplate jdbcTemplate; private NamedParameterJdbcTemplate namedParameterJdbcTemplate; private ActionDao actionDao; private EntityMapper<T> entityMapper; /** * Creates a new instance. * * @param jdbcTemplate * Provides access to the database. * @param entityMapper * Provides entity type specific JDBC support. * @param actionDao * The dao to use for adding action records to the database. */ protected EntityDao(JdbcTemplate jdbcTemplate, EntityMapper<T> entityMapper, ActionDao actionDao) { this.jdbcTemplate = jdbcTemplate; this.namedParameterJdbcTemplate = new NamedParameterJdbcTemplate(jdbcTemplate); this.entityMapper = entityMapper; this.actionDao = actionDao; } /** * Gets the entity mapper implementation. * * @return The entity mapper. */ protected EntityMapper<T> getEntityMapper() { return entityMapper; } /** * Checks if the specified entity exists in the database. * * @param entityId * The unique identifier of the entity. * @return True if the entity exists in the database. */ public boolean exists(long entityId) { return jdbcTemplate.queryForObject(entityMapper.getSqlSelectCount(true), Integer.class, entityId) > 0; } /** * Loads the specified entity from the database. * * @param entityId * The unique identifier of the entity. * @return The loaded entity. */ public T getEntity(long entityId) { T entity; try { entity = jdbcTemplate.queryForObject(entityMapper.getSqlSelect(true, false), entityMapper.getRowMapper(), entityId); } catch (EmptyResultDataAccessException e) { throw new NoSuchRecordException(entityMapper.getEntityName() + " " + entityId + " doesn't exist.", e); } return entity; } /** * Adds the specified entity to the database. * * @param entity * The entity to add. */ public void addEntity(T entity) { Map<String, Object> args; args = new HashMap<String, Object>(); entityMapper.populateEntityParameters(args, entity); namedParameterJdbcTemplate.update(entityMapper.getSqlInsert(1), args); actionDao.addAction(entityMapper.getEntityType(), ChangesetAction.CREATE, entity.getId()); } /** * Updates the specified entity details in the database. * * @param entity * The entity to update. */ public void modifyEntity(T entity) { Map<String, Object> args; args = new HashMap<String, Object>(); entityMapper.populateEntityParameters(args, entity); namedParameterJdbcTemplate.update(entityMapper.getSqlUpdate(true), args); actionDao.addAction(entityMapper.getEntityType(), ChangesetAction.MODIFY, entity.getId()); } /** * Removes the specified entity from the database. * * @param entityId * The id of the entity to remove. */ public void removeEntity(long entityId) { Map<String, Object> args; args = new HashMap<String, Object>(); args.put("id", entityId); namedParameterJdbcTemplate.update(entityMapper.getSqlDelete(true), args); actionDao.addAction(entityMapper.getEntityType(), ChangesetAction.DELETE, entityId); } private ReleasableIterator<T> getFeaturelessEntity(String tablePrefix) { FileBasedSort<T> sortingStore; sortingStore = new FileBasedSort<T>( new SingleClassObjectSerializationFactory(entityMapper.getEntityClass()), new EntitySubClassComparator<T>(new EntityByTypeThenIdComparator()), true); try { String sql; SortingStoreRowMapperListener<T> storeListener; RowMapperRowCallbackListener<T> rowCallbackListener; ReleasableIterator<T> resultIterator; sql = entityMapper.getSqlSelect(tablePrefix, false, false); // Sends all received data into the object store. storeListener = new SortingStoreRowMapperListener<T>(sortingStore); // Converts result set rows into objects and passes them into the store. rowCallbackListener = new RowMapperRowCallbackListener<T>(entityMapper.getRowMapper(), storeListener); // Perform the query passing the row mapper chain to process rows in a streamy fashion. jdbcTemplate.query(sql, rowCallbackListener); // Open a iterator on the store that will release the store upon completion. resultIterator = new StoreReleasingIterator<T>(sortingStore.iterate(), sortingStore); // The store itself shouldn't be released now that it has been attached to the iterator. sortingStore = null; return resultIterator; } finally { if (sortingStore != null) { sortingStore.close(); } } } /** * Gets the feature populators for the entity type. * * @param tablePrefix * The prefix for the entity table name. This allows another table to be queried if * necessary such as a temporary results table. * @return The feature populators. */ protected abstract List<FeaturePopulator<T>> getFeaturePopulators(String tablePrefix); /** * Returns an iterator providing access to all entities in the database. * * @param tablePrefix * The prefix for the entity table name. This allows another table to be queried if * necessary such as a temporary results table. * @return The entity iterator. */ public ReleasableIterator<T> iterate(String tablePrefix) { try (ReleasableContainer releasableContainer = new ReleasableContainer()) { ReleasableIterator<T> entityIterator; List<FeaturePopulator<T>> featurePopulators; // Create the featureless entity iterator but also store it temporarily in the // releasable container so that it will get freed if we fail during retrieval of feature // populators. entityIterator = releasableContainer.add(getFeaturelessEntity(tablePrefix)); // Retrieve the feature populators also adding them to the temporary releasable container. featurePopulators = getFeaturePopulators(tablePrefix); for (FeaturePopulator<T> featurePopulator : featurePopulators) { releasableContainer.add(featurePopulator); } // Build an entity reader capable of merging all sources together. entityIterator = new EntityReader<T>(entityIterator, featurePopulators); // The sources are now all attached to the history reader so we don't want to release // them in the finally block. releasableContainer.clear(); return entityIterator; } } /** * Returns an iterator providing access to all entities in the database. * * @return The entity iterator. */ public ReleasableIterator<T> iterate() { return iterate(""); } }