package org.springframework.social.importer.config;
import org.springframework.batch.core.configuration.support.JobRegistryBeanPostProcessor;
import org.springframework.batch.core.configuration.support.MapJobRegistry;
import org.springframework.batch.core.launch.support.SimpleJobLauncher;
import org.springframework.batch.core.repository.JobRepository;
import org.springframework.batch.core.repository.support.JobRepositoryFactoryBean;
import org.springframework.batch.item.ItemWriter;
import org.springframework.batch.item.database.ItemSqlParameterSourceProvider;
import org.springframework.batch.item.database.JdbcBatchItemWriter;
import org.springframework.batch.item.database.JdbcCursorItemReader;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.*;
import org.springframework.core.env.Environment;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.task.TaskExecutor;
import org.springframework.http.converter.BufferedImageHttpMessageConverter;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
import org.springframework.jdbc.core.namedparam.SqlParameterSource;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.jdbc.datasource.SimpleDriverDataSource;
import org.springframework.jdbc.datasource.init.DataSourceInitializer;
import org.springframework.jdbc.datasource.init.ResourceDatabasePopulator;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.scheduling.concurrent.ConcurrentTaskExecutor;
import org.springframework.scheduling.concurrent.ConcurrentTaskScheduler;
import org.springframework.social.flickr.api.impl.FlickrTemplate;
import org.springframework.social.importer.*;
import org.springframework.transaction.PlatformTransactionManager;
import javax.inject.Inject;
import javax.sql.DataSource;
import java.io.File;
import java.sql.Driver;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;
import java.util.HashMap;
import java.util.Map;
@Configuration
@Scope(proxyMode = ScopedProxyMode.TARGET_CLASS)
@ImportResource("/batch.xml")
public class BatchConfiguration {
private boolean shouldCreateTables = false;
@Bean
public TaskExecutor taskExecutor() {
return new ConcurrentTaskExecutor();
}
@Bean
public TaskScheduler taskScheduler() {
return new ConcurrentTaskScheduler();
}
@Bean
@Inject
public DataSourceInitializer dataSourceInitializer(DataSource dataSource) {
DataSourceInitializer dsi = new DataSourceInitializer();
dsi.setDataSource(dataSource);
ResourceDatabasePopulator resourceDatabasePopulator = new ResourceDatabasePopulator();
String[] scripts = "/batch_%s.sql,/photos_%s.sql".split(",");
String scriptSuffix = "pg";
for (String s : scripts) {
ClassPathResource classPathResource = new ClassPathResource(String.format(s, scriptSuffix));
resourceDatabasePopulator.addScript(classPathResource);
}
dsi.setDatabasePopulator(resourceDatabasePopulator);
dsi.setEnabled(this.shouldCreateTables);
return dsi;
}
@Bean
public JobRegistryBeanPostProcessor jobRegistryBeanPostProcessor() throws Exception {
JobRegistryBeanPostProcessor jobRegistryBeanPostProcessor = new JobRegistryBeanPostProcessor();
jobRegistryBeanPostProcessor.setJobRegistry(this.mapJobRegistry());
return jobRegistryBeanPostProcessor;
}
@Bean
@Inject
public PlatformTransactionManager transactionManager(DataSource ds) {
return new DataSourceTransactionManager(ds);
}
@Bean
public MapJobRegistry mapJobRegistry() throws Exception {
return new MapJobRegistry();
}
@Bean
@Inject
public DataSource dataSource(Environment environment) {
int port = environment.getProperty("dataSource.port", Integer.class);
String pw = environment.getProperty("dataSource.password"),
user = environment.getProperty("dataSource.user"),
host = environment.getProperty("dataSource.host"),
db = environment.getProperty("dataSource.db"),
url = String.format("jdbc:postgresql://%s:%s/%s", host, port, db);
Class<Driver> classOfDs = environment.getPropertyAsClass("dataSource.driverClassName", Driver.class);
SimpleDriverDataSource dataSource = new SimpleDriverDataSource();
dataSource.setPassword(pw);
dataSource.setUrl(url);
dataSource.setUsername(user);
dataSource.setDriverClass(classOfDs);
return dataSource;
}
@Bean
@Inject
public JobRepositoryFactoryBean jobRepository(DataSource ds, PlatformTransactionManager tx) throws Exception {
JobRepositoryFactoryBean jobRepositoryFactoryBean = new JobRepositoryFactoryBean();
jobRepositoryFactoryBean.setDataSource(ds);
jobRepositoryFactoryBean.setTransactionManager(tx);
return jobRepositoryFactoryBean;
}
@Bean
@Inject
public SimpleJobLauncher jobLauncher(TaskExecutor te, JobRepository jobRepository) throws Exception {
SimpleJobLauncher simpleJobLauncher = new SimpleJobLauncher();
simpleJobLauncher.setJobRepository(jobRepository);
simpleJobLauncher.setTaskExecutor(te);
return simpleJobLauncher;
}
// ===================================================
// STEP #1
// ===================================================
@Bean(name = "photoAlbumItemReader")
@Inject
@Scope(value = "step")
public FlickrServicePhotoAlbumItemReader photoAlbumItemReader(
@Value("#{jobParameters['accessToken']}") String at,
@Value("#{jobParameters['accessTokenSecret']}") String atSecret,
@Value("#{jobParameters['consumerKey']}") String consumerKey,
@Value("#{jobParameters['consumerSecret']}") String consumerSecret
) throws Throwable {
FlickrTemplate flickrTemplate = new FlickrTemplate(consumerKey, consumerSecret, at, atSecret);
return new FlickrServicePhotoAlbumItemReader(flickrTemplate);
}
/**
* basically loads the photo albums from the Flickr service
* and loads them into the database
* <p/>
* <CODE>insert into photo_albums(title, user_id, description, album_id, url) values( :t, :ui, :d, :a, :u )</CODE>
*/
@Bean(name = "photoAlbumItemWriter")
@Inject
public JdbcBatchItemWriter<PhotoSet> writer(DataSource ds) {
String upsertPhotoAlbumsSql =
"with new_values (title, user_id, description, album_id, url, count_photos, count_videos) as ( " +
" values ( :t, :ui, :d, :a, :u, :cp, :cv ) ), " +
"upsert as " +
"( update photo_albums p set count_photos=nv.count_photos,count_videos=nv.count_videos, title = nv.title, user_id = nv.user_id, description = nv.description, album_id = nv.album_id, url = nv.url " +
" FROM new_values nv WHERE p.album_id = nv.album_id RETURNING p.* ) " +
"INSERT INTO photo_albums (title, user_id, description, album_id, url, count_photos, count_videos) " +
"SELECT nv.* FROM new_values nv " +
"WHERE NOT EXISTS (SELECT 1 FROM upsert up WHERE up.album_id = nv.album_id )";
JdbcBatchItemWriter<PhotoSet> jdbcBatchItemWriter = new JdbcBatchItemWriter<PhotoSet>();
jdbcBatchItemWriter.setSql(upsertPhotoAlbumsSql);
jdbcBatchItemWriter.setDataSource(ds);
jdbcBatchItemWriter.setAssertUpdates(false); // were doing upserts, so these writes may never take effect
jdbcBatchItemWriter.setItemSqlParameterSourceProvider(new ItemSqlParameterSourceProvider<PhotoSet>() {
@Override
public SqlParameterSource createSqlParameterSource(PhotoSet item) {
return new MapSqlParameterSource()
.addValue("t", item.getTitle())
.addValue("d", item.getDescription())
.addValue("ui", item.getUserId())
.addValue("a", item.getId())
.addValue("cv", item.getCountOfVideos(), Types.INTEGER)
.addValue("cp", item.getCountOfPhotos(), Types.INTEGER)
.addValue("u", item.getUrl());
}
});
return jdbcBatchItemWriter;
}
// ===================================================
// STEP #2
// ===================================================
/**
* this will return all the photo_albums from the database, but
* it's not going to be registered in a step, instead it'll be delegated to
* from another {@link org.springframework.batch.item.ItemReader}
* which in turn will load the <em>photos</em> for each album
* and <em>that</em> is what's written by the step's {@link org.springframework.batch.item.ItemWriter}.
*
* @param dataSource datasource
* @return an item reader
*/
@Bean(name = "photoSetJdbcCursorItemReader")
@Inject
public JdbcCursorItemReader<PhotoSet> readPhotoAlbumsFromDatabaseItemReader(DataSource dataSource) {
JdbcCursorItemReader<PhotoSet> photoSetJdbcCursorItemReader = new JdbcCursorItemReader<PhotoSet>();
photoSetJdbcCursorItemReader.setRowMapper(new RowMapper<PhotoSet>() {
@Override
public PhotoSet mapRow(ResultSet resultSet, int i) throws SQLException {
return new PhotoSet(
resultSet.getInt("count_videos"),
resultSet.getInt("count_photos"),
resultSet.getString("url"),
resultSet.getString("title"),
resultSet.getString("description"),
resultSet.getString("album_id"),
resultSet.getString("user_id"));
}
});
photoSetJdbcCursorItemReader.setSql("select * from photo_albums");
photoSetJdbcCursorItemReader.setDataSource(dataSource);
return photoSetJdbcCursorItemReader;
}
@Bean(name = "delegatingFlickrPhotoAlbumPhotoItemReader")
@Inject
@Scope(value = "step")
public DelegatingFlickrPhotoAlbumPhotoItemReader delegatingFlickrPhotoAlbumPhotoItemReader(
@Qualifier("photoSetJdbcCursorItemReader") JdbcCursorItemReader<PhotoSet> photoSetJdbcCursorItemReader,
@Value("#{jobParameters['accessToken']}") String accessToken,
@Value("#{jobParameters['accessTokenSecret']}") String atSecret,
@Value("#{jobParameters['consumerKey']}") String consumerKey,
@Value("#{jobParameters['consumerSecret']}") String consumerSecret) {
FlickrTemplate flickrTemplate = new FlickrTemplate(consumerKey, consumerSecret, accessToken, atSecret);
return new DelegatingFlickrPhotoAlbumPhotoItemReader(flickrTemplate, photoSetJdbcCursorItemReader);
}
@Bean(name = "photoDetailItemWriter")
@Inject
public JdbcBatchItemWriter<Photo> photoDetailItemWriter(DataSource ds) {
String upsertSql =
"with new_values (photo_id, url, comments, album_id) as ( " +
" values ( :p, :u, :c, :a) ), " +
"upsert as " +
"( update photos p set photo_id=nv.photo_id, url=nv.url, comments= nv.comments, album_id= nv.album_id " +
" FROM new_values nv WHERE p.photo_id = nv.photo_id RETURNING p.* ) " +
" insert into photos( photo_id,url,comments,album_id) " +
"SELECT nv.* FROM new_values nv " +
"WHERE NOT EXISTS (SELECT 1 FROM upsert up WHERE up.photo_id = nv.photo_id )";
JdbcBatchItemWriter<Photo> photoDetailJdbcBatchItemWriter = new JdbcBatchItemWriter<Photo>();
photoDetailJdbcBatchItemWriter.setDataSource(ds);
photoDetailJdbcBatchItemWriter.setAssertUpdates(false);
photoDetailJdbcBatchItemWriter.setSql(upsertSql);
photoDetailJdbcBatchItemWriter.setItemSqlParameterSourceProvider(new ItemSqlParameterSourceProvider<Photo>() {
@Override
public SqlParameterSource createSqlParameterSource(Photo item) {
Map<String, Object> params = new HashMap<String, Object>();
params.put("p", item.getId());
params.put("u", item.getUrl());
params.put("c", item.getComments());
params.put("a", item.getAlbumId());
return new MapSqlParameterSource(params);
}
});
return photoDetailJdbcBatchItemWriter;
}
// ===================================================
// STEP #3
// ===================================================
/**
* the final step is to read the database records, then download the file
*/
@Bean
@Inject
public JdbcCursorItemReader<Photo> photoDetailItemReader(DataSource dataSource) {
JdbcCursorItemReader<Photo> photoSetJdbcCursorItemReader = new JdbcCursorItemReader<Photo>();
photoSetJdbcCursorItemReader.setRowMapper(new RowMapper<Photo>() {
@Override
public Photo mapRow(ResultSet resultSet, int i) throws SQLException {
return new Photo(
resultSet.getString("photo_id"),
resultSet.getString("url"),
resultSet.getString("title"),
resultSet.getString("comments"),
resultSet.getString("album_id")
);
}
});
photoSetJdbcCursorItemReader.setSql("select * from photos ");
photoSetJdbcCursorItemReader.setDataSource(dataSource);
return photoSetJdbcCursorItemReader;
}
@Inject
@Scope("step")
@Bean(name = "photoDownloadingItemWriter")
public ItemWriter<Photo> photoDownloadingItemWriter(
@Value("#{jobParameters['output']}") String outputPath,
@Value("#{jobParameters['accessToken']}") String accessToken,
@Value("#{jobParameters['accessTokenSecret']}") String atSecret,
@Value("#{jobParameters['consumerKey']}") String consumerKey,
@Value("#{jobParameters['consumerSecret']}") String consumerSecret) {
FlickrTemplate flickrTemplate = new FlickrTemplate(consumerKey, consumerSecret, accessToken, atSecret);
flickrTemplate.getRestTemplate().getMessageConverters().add(new BufferedImageHttpMessageConverter());
return new PhotoDownloadingItemWriter(flickrTemplate, new File(outputPath));
}
}