IN PROGRESS - issue BATCH-7: Remove transaction synchronization and state management from input/output sources (formerly buffering)

http://jira.springframework.org/browse/BATCH-7

Remove Statistics* - use StreamContext instead.
This commit is contained in:
dsyer
2008-01-31 13:29:59 +00:00
parent 2b9add95b6
commit 8b6aad9d82
31 changed files with 372 additions and 698 deletions

View File

@@ -23,7 +23,6 @@ import java.sql.SQLWarning;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import javax.sql.DataSource;
@@ -35,7 +34,6 @@ import org.springframework.batch.item.ItemStream;
import org.springframework.batch.item.KeyedItemReader;
import org.springframework.batch.item.StreamContext;
import org.springframework.batch.item.stream.GenericStreamContext;
import org.springframework.batch.statistics.StatisticsProvider;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.dao.DataAccessException;
@@ -115,9 +113,8 @@ import org.springframework.util.StringUtils;
* @author Lucas Ward
* @author Peter Zozom
*/
public class JdbcCursorItemReader extends AbstractTransactionalIoSource
implements KeyedItemReader, DisposableBean,
InitializingBean, ItemStream, StatisticsProvider, Skippable {
public class JdbcCursorItemReader extends AbstractTransactionalIoSource implements KeyedItemReader, DisposableBean,
InitializingBean, ItemStream, Skippable {
private static Log log = LogFactory.getLog(JdbcCursorItemReader.class);
@@ -156,9 +153,9 @@ public class JdbcCursorItemReader extends AbstractTransactionalIoSource
private SQLExceptionTranslator exceptionTranslator;
/* Current count of processed records. */
private int currentProcessedRow = 0;
private long currentProcessedRow = 0;
private int lastCommittedRow = 0;
private long lastCommittedRow = 0;
private RowMapper mapper;
@@ -167,8 +164,8 @@ public class JdbcCursorItemReader extends AbstractTransactionalIoSource
/**
* Assert that mandatory properties are set.
*
* @throws IllegalArgumentException
* if either data source or sql properties not set.
* @throws IllegalArgumentException if either data source or sql properties
* not set.
*/
public void afterPropertiesSet() throws Exception {
Assert.notNull(dataSource, "DataSOurce must be provided");
@@ -193,8 +190,7 @@ public class JdbcCursorItemReader extends AbstractTransactionalIoSource
*
* @returns Object returned by RowMapper
* @throws DataAccessException
* @throws IllegalStateExceptino
* if mapper is null.
* @throws IllegalStateExceptino if mapper is null.
*/
public Object read() {
@@ -207,12 +203,12 @@ public class JdbcCursorItemReader extends AbstractTransactionalIoSource
try {
if (!rs.next()) {
return null;
} else {
}
else {
currentProcessedRow++;
if (!skippedRows.isEmpty()) {
// while is necessary to handle successive skips.
while (skippedRows
.contains(new Integer(currentProcessedRow))) {
while (skippedRows.contains(new Long(currentProcessedRow))) {
if (!rs.next()) {
return null;
}
@@ -220,20 +216,20 @@ public class JdbcCursorItemReader extends AbstractTransactionalIoSource
}
}
Object mappedResult = mapper.mapRow(rs, currentProcessedRow);
Object mappedResult = mapper.mapRow(rs, (int)currentProcessedRow);
verifyCursorPosition(currentProcessedRow);
return mappedResult;
}
} catch (SQLException se) {
throw getExceptionTranslator().translate(
"Trying to process next row", sql, se);
}
catch (SQLException se) {
throw getExceptionTranslator().translate("Trying to process next row", sql, se);
}
}
public int getCurrentProcessedRow() {
public long getCurrentProcessedRow() {
return currentProcessedRow;
}
@@ -255,15 +251,15 @@ public class JdbcCursorItemReader extends AbstractTransactionalIoSource
try {
currentProcessedRow = lastCommittedRow;
if (currentProcessedRow > 0) {
rs.absolute(currentProcessedRow);
} else {
rs.absolute((int)currentProcessedRow);
}
else {
rs.beforeFirst();
}
} catch (SQLException se) {
throw getExceptionTranslator().translate(
"Attempted to move ResultSet to last committed row", sql,
se);
}
catch (SQLException se) {
throw getExceptionTranslator().translate("Attempted to move ResultSet to last committed row", sql, se);
}
}
@@ -297,12 +293,10 @@ public class JdbcCursorItemReader extends AbstractTransactionalIoSource
// Check the result set is in synch with the currentRow attribute. This is
// important
// to ensure that the user hasn't modified the current row.
private void verifyCursorPosition(int expectedCurrentRow)
throws SQLException {
private void verifyCursorPosition(long expectedCurrentRow) throws SQLException {
if (verifyCursorPosition) {
if (expectedCurrentRow != this.rs.getRow()) {
throw new InvalidDataAccessResourceUsageException(
"Unexpected cursor position change.");
throw new InvalidDataAccessResourceUsageException("Unexpected cursor position change.");
}
}
}
@@ -320,17 +314,15 @@ public class JdbcCursorItemReader extends AbstractTransactionalIoSource
try {
this.con = dataSource.getConnection();
this.stmt = this.con.createStatement(
ResultSet.TYPE_SCROLL_INSENSITIVE,
ResultSet.CONCUR_READ_ONLY,
this.stmt = this.con.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY,
ResultSet.HOLD_CURSORS_OVER_COMMIT);
applyStatementSettings(this.stmt);
this.rs = this.stmt.executeQuery(sql);
handleWarnings(this.stmt.getWarnings());
} catch (SQLException se) {
}
catch (SQLException se) {
close();
throw getExceptionTranslator()
.translate("Executing query", sql, se);
throw getExceptionTranslator().translate("Executing query", sql, se);
}
super.registerSynchronization();
@@ -367,9 +359,9 @@ public class JdbcCursorItemReader extends AbstractTransactionalIoSource
protected SQLExceptionTranslator getExceptionTranslator() {
if (exceptionTranslator == null) {
if (dataSource != null) {
exceptionTranslator = new SQLErrorCodeSQLExceptionTranslator(
dataSource);
} else {
exceptionTranslator = new SQLErrorCodeSQLExceptionTranslator(dataSource);
}
else {
exceptionTranslator = new SQLStateSQLExceptionTranslator();
}
}
@@ -389,13 +381,12 @@ public class JdbcCursorItemReader extends AbstractTransactionalIoSource
if (ignoreWarnings) {
SQLWarning warningToLog = warnings;
while (warningToLog != null) {
log.debug("SQLWarning ignored: SQL state '"
+ warningToLog.getSQLState() + "', error code '"
+ warningToLog.getErrorCode() + "', message ["
+ warningToLog.getMessage() + "]");
log.debug("SQLWarning ignored: SQL state '" + warningToLog.getSQLState() + "', error code '"
+ warningToLog.getErrorCode() + "', message [" + warningToLog.getMessage() + "]");
warningToLog = warningToLog.getNextWarning();
}
} else if (warnings != null) {
}
else if (warnings != null) {
throw new SQLWarningException("Warning not ignored", warnings);
}
}
@@ -406,12 +397,12 @@ public class JdbcCursorItemReader extends AbstractTransactionalIoSource
* @see org.springframework.batch.restart.Restartable#getRestartData()
*/
public StreamContext getStreamContext() {
StreamContext streamContext = new StreamContext();
String skipped = skippedRows.toString();
Properties statistics = getStatistics();
statistics.setProperty(SKIPPED_ROWS, skipped.substring(1,skipped.length()-1));
return new GenericStreamContext(statistics);
StreamContext context = new GenericStreamContext();
context.putString(SKIPPED_ROWS, skipped.substring(1, skipped.length() - 1));
context.putLong(CURRENT_PROCESSED_ROW, currentProcessedRow);
context.putLong(SKIP_COUNT, skipCount);
return context;
}
/*
@@ -427,18 +418,17 @@ public class JdbcCursorItemReader extends AbstractTransactionalIoSource
open();
// Properties restartProperties = data.getProperties();
// Properties restartProperties = data.getProperties();
if (!data.containsKey(CURRENT_PROCESSED_ROW)) {
return;
}
try {
this.currentProcessedRow = new Long(data.getLong(CURRENT_PROCESSED_ROW)).intValue();
rs.absolute(currentProcessedRow);
} catch (SQLException se) {
throw getExceptionTranslator().translate(
"Attempted to move ResultSet to last committed row", sql,
se);
this.currentProcessedRow = data.getLong(CURRENT_PROCESSED_ROW);
rs.absolute((int)currentProcessedRow);
}
catch (SQLException se) {
throw getExceptionTranslator().translate("Attempted to move ResultSet to last committed row", sql, se);
}
if (!data.containsKey(SKIPPED_ROWS)) {
@@ -447,24 +437,10 @@ public class JdbcCursorItemReader extends AbstractTransactionalIoSource
String[] skipped = StringUtils.commaDelimitedListToStringArray(data.getString(SKIPPED_ROWS));
for (int i = 0; i < skipped.length; i++) {
this.skippedRows.add(new Integer(skipped[i]));
this.skippedRows.add(new Long(skipped[i]));
}
}
/*
* (non-Javadoc)
*
* @see org.springframework.batch.statistics.StatisticsProvider#getStatistics()
*/
public Properties getStatistics() {
Properties props = new Properties();
props.setProperty(CURRENT_PROCESSED_ROW, new Integer(
currentProcessedRow).toString());
props.setProperty(SKIP_COUNT, new Integer(skipCount).toString());
return props;
}
/**
* Skip the current row. If the transaction is rolled back, this row will
* not be represented to the RowMapper when read() is called. For example,
@@ -472,7 +448,7 @@ public class JdbcCursorItemReader extends AbstractTransactionalIoSource
* continue processing and find
*/
public void skip() {
skippedRows.add(new Integer(currentProcessedRow));
skippedRows.add(new Long(currentProcessedRow));
skipCount++;
}
@@ -482,8 +458,7 @@ public class JdbcCursorItemReader extends AbstractTransactionalIoSource
* <code>ResultSet</code> object. If the fetch size specified is zero, the
* JDBC driver ignores the value.
*
* @param fetchSize
* the number of rows to fetch
* @param fetchSize the number of rows to fetch
* @see ResultSet#setFetchSize(int)
*/
public void setFetchSize(int fetchSize) {
@@ -494,8 +469,7 @@ public class JdbcCursorItemReader extends AbstractTransactionalIoSource
* Sets the limit for the maximum number of rows that any
* <code>ResultSet</code> object can contain to the given number.
*
* @param maxRows
* the new max rows limit; zero means there is no limit
* @param maxRows the new max rows limit; zero means there is no limit
* @see Statement#setMaxRows(int)
*/
public void setMaxRows(int maxRows) {
@@ -508,9 +482,8 @@ public class JdbcCursorItemReader extends AbstractTransactionalIoSource
* seconds. If the limit is exceeded, an <code>SQLException</code> is
* thrown.
*
* @param queryTimeout
* seconds the new query timeout limit in seconds; zero means
* there is no limit
* @param queryTimeout seconds the new query timeout limit in seconds; zero
* means there is no limit
* @see Statement#setQueryTimeout(int)
*/
public void setQueryTimeout(int queryTimeout) {
@@ -521,8 +494,7 @@ public class JdbcCursorItemReader extends AbstractTransactionalIoSource
* Set whether SQLWarnings should be ignored (only logged) or exception
* should be thrown.
*
* @param ignoreWarnings
* if TRUE, warnings are ignored
* @param ignoreWarnings if TRUE, warnings are ignored
*/
public void setIgnoreWarnings(boolean ignoreWarnings) {
this.ignoreWarnings = ignoreWarnings;
@@ -532,8 +504,7 @@ public class JdbcCursorItemReader extends AbstractTransactionalIoSource
* Allow verification of cursor position after current row is processed by
* RowMapper or RowCallbackHandler. Default value is TRUE.
*
* @param verifyCursorPosition
* if true, cursor position is verified
* @param verifyCursorPosition if true, cursor position is verified
*/
public void setVerifyCursorPosition(boolean verifyCursorPosition) {
this.verifyCursorPosition = verifyCursorPosition;
@@ -565,7 +536,7 @@ public class JdbcCursorItemReader extends AbstractTransactionalIoSource
initialized = true;
}
/**
* Return the item itself (which is already a key).
* @see org.springframework.batch.item.ItemReader#getKey(java.lang.Object)
@@ -573,5 +544,5 @@ public class JdbcCursorItemReader extends AbstractTransactionalIoSource
public Object getKey(Object item) {
return item;
}
}

View File

@@ -37,14 +37,14 @@ import org.springframework.util.ClassUtils;
*/
public class ColumnMapStreamContextRowMapper extends ColumnMapRowMapper implements RestartDataRowMapper{
static final String KEY = ClassUtils.getQualifiedName(ColumnMapStreamContextRowMapper.class) + ".KEY.";
public static final String KEY_PREFIX = ClassUtils.getQualifiedName(ColumnMapStreamContextRowMapper.class) + ".KEY.";
public PreparedStatementSetter createSetter(StreamContext streamContext) {
ColumnMapRestartData columnData = new ColumnMapRestartData(streamContext.getProperties());
List columns = new ArrayList();
for (Iterator iterator = columnData.entrySet().iterator(); iterator.hasNext();) {
for (Iterator iterator = columnData.keys.entrySet().iterator(); iterator.hasNext();) {
Entry entry = (Entry)iterator.next();
Object column = entry.getValue();
columns.add(column);
@@ -53,27 +53,51 @@ public class ColumnMapStreamContextRowMapper extends ColumnMapRowMapper implemen
return new ArgPreparedStatementSetter(columns.toArray());
}
public StreamContext createRestartData(Object key) {
Assert.isInstanceOf(Map.class, key, "Key must be of type Map.");
public StreamContext createStreamContext(Object key) {
Assert.isInstanceOf(Map.class, key, "Input to create StreamContext must be of type Map.");
Map keys = (Map)key;
return new ColumnMapRestartData(keys);
}
private static class ColumnMapRestartData extends GenericStreamContext{
private static class ColumnMapRestartData extends GenericStreamContext {
private final Map keys;
public ColumnMapRestartData(Map keys) {
super();
for(Iterator it = keys.entrySet().iterator();it.hasNext();){
Entry entry = (Entry)it.next();
putString(entry.getKey().toString(), entry.getValue().toString());
}
this.keys = keys;
}
public ColumnMapRestartData(Properties props) {
super(props);
keys = CollectionFactory.createLinkedCaseInsensitiveMapIfPossible(props.size());
for(int counter = 0; counter < props.size(); counter++){
String key = KEY_PREFIX + counter;
String column = props.getProperty(key);
if(column != null){
keys.put(key, column);
}
else{
break;
}
}
}
public Properties getProperties() {
Properties props = new Properties();
int counter = 0;
for (Iterator iterator = keys.entrySet().iterator(); iterator.hasNext();) {
Entry entry = (Entry) iterator.next();
props.setProperty(KEY_PREFIX + counter, entry.getValue().toString());
counter++;
}
return props;
}
}

View File

@@ -98,7 +98,7 @@ public class MultipleColumnJdbcKeyGenerator implements
*/
public StreamContext getKeyAsStreamContext(Object key) {
Assert.state(keyMapper != null, "RestartDataConverter must not be null.");
return keyMapper.createRestartData(key);
return keyMapper.createStreamContext(key);
}
/**

View File

@@ -40,7 +40,7 @@ public interface RestartDataRowMapper extends RowMapper {
* @return ResartData representing the composite key.
* @throws IllegalArgumentException if key is null or of an unsupported type.
*/
public StreamContext createRestartData(Object key);
public StreamContext createStreamContext(Object key);
/**
* Given the provided restart data, return a PreparedStatementSeter that can

View File

@@ -26,9 +26,9 @@ import org.springframework.batch.io.Skippable;
import org.springframework.batch.io.file.separator.LineReader;
import org.springframework.batch.item.ItemStream;
import org.springframework.batch.item.StreamContext;
import org.springframework.batch.item.StreamException;
import org.springframework.batch.item.stream.GenericStreamContext;
import org.springframework.batch.repeat.synch.BatchTransactionSynchronizationManager;
import org.springframework.batch.statistics.StatisticsProvider;
import org.springframework.transaction.support.TransactionSynchronization;
import org.springframework.transaction.support.TransactionSynchronizationAdapter;
@@ -42,8 +42,7 @@ import org.springframework.transaction.support.TransactionSynchronizationAdapter
* @author Tomas Slanina
* @author Robert Kasanicky
*/
public class DefaultFlatFileItemReader extends SimpleFlatFileItemReader implements Skippable, ItemStream,
StatisticsProvider {
public class DefaultFlatFileItemReader extends SimpleFlatFileItemReader implements Skippable, ItemStream {
private static Log log = LogFactory.getLog(DefaultFlatFileItemReader.class);
@@ -113,8 +112,10 @@ public class DefaultFlatFileItemReader extends SimpleFlatFileItemReader implemen
* @see org.springframework.batch.statistics.StatisticsProvider#getStatistics()
*/
public Properties getStatistics() {
LineReader is = getReader();
statistics.setProperty(READ_STATISTICS_NAME, String.valueOf(is.getCurrentLineCount()));
if (reader==null) {
throw new StreamException("ItemStream not open or already closed.");
}
statistics.setProperty(READ_STATISTICS_NAME, String.valueOf(reader.getCurrentLineCount()));
return statistics;
}

View File

@@ -36,7 +36,6 @@ import org.springframework.batch.item.ItemWriter;
import org.springframework.batch.item.StreamContext;
import org.springframework.batch.item.stream.GenericStreamContext;
import org.springframework.batch.item.writer.ItemTransformer;
import org.springframework.batch.statistics.StatisticsProvider;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.core.io.Resource;
@@ -59,9 +58,8 @@ import org.springframework.util.Assert;
* @author Robert Kasanicky
* @author Dave Syer
*/
public class FlatFileItemWriter extends AbstractTransactionalIoSource implements
ItemWriter, ItemStream, StatisticsProvider, InitializingBean,
DisposableBean {
public class FlatFileItemWriter extends AbstractTransactionalIoSource implements ItemWriter, ItemStream,
InitializingBean, DisposableBean {
private static final String LINE_SEPARATOR = System.getProperty("line.separator");
@@ -88,7 +86,7 @@ public class FlatFileItemWriter extends AbstractTransactionalIoSource implements
private static class BooleanHolder {
public boolean value;
}
/**
* Assert that mandatory properties (resource) are set.
* @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet()
@@ -119,7 +117,7 @@ public class FlatFileItemWriter extends AbstractTransactionalIoSource implements
}
/**
* Commit the transaction.
* Commit the transaction.
*/
protected void transactionCommitted() {
getOutputState().mark();
@@ -159,7 +157,7 @@ public class FlatFileItemWriter extends AbstractTransactionalIoSource implements
* Convert the date to a format that can be output and then write it out.
* @param data
* @param converted
* @throws Exception
* @throws Exception
*/
private void transformAndWrite(Object data, BooleanHolder converted) throws Exception {
@@ -262,7 +260,7 @@ public class FlatFileItemWriter extends AbstractTransactionalIoSource implements
public StreamContext getStreamContext() {
final OutputState os = getOutputState();
streamContext.getProperties().setProperty(RESTART_DATA_NAME, String.valueOf(os.position()));
streamContext.putString(RESTART_DATA_NAME, String.valueOf(os.position()));
return streamContext;
}
@@ -453,7 +451,7 @@ public class FlatFileItemWriter extends AbstractTransactionalIoSource implements
}
}
String parent = file.getParent();
if (parent!=null) {
if (parent != null) {
new File(parent).mkdirs();
}
file.createNewFile();

View File

@@ -68,7 +68,7 @@ public class SimpleFlatFileItemReader extends AbstractItemReader implements Item
* Encapsulates the state of the input source. If it is null then we are
* uninitialized.
*/
private LineReader reader;
protected LineReader reader;
private RecordSeparatorPolicy recordSeparatorPolicy;

View File

@@ -23,7 +23,6 @@ import org.springframework.batch.item.StreamContext;
import org.springframework.batch.item.reader.AbstractItemReader;
import org.springframework.batch.item.stream.GenericStreamContext;
import org.springframework.batch.repeat.synch.BatchTransactionSynchronizationManager;
import org.springframework.batch.statistics.StatisticsProvider;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.core.io.Resource;
@@ -43,7 +42,7 @@ import org.springframework.util.Assert;
* @author Robert Kasanicky
*/
public class StaxEventItemReader extends AbstractItemReader implements ItemReader,
Skippable, ItemStream, StatisticsProvider, InitializingBean, DisposableBean {
Skippable, ItemStream, InitializingBean, DisposableBean {
public static final String READ_COUNT_STATISTICS_NAME = "StaxEventReaderItemReader.readCount";

View File

@@ -21,7 +21,6 @@ import org.springframework.batch.item.ItemWriter;
import org.springframework.batch.item.StreamContext;
import org.springframework.batch.item.stream.GenericStreamContext;
import org.springframework.batch.repeat.synch.BatchTransactionSynchronizationManager;
import org.springframework.batch.statistics.StatisticsProvider;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.core.io.Resource;
@@ -32,17 +31,16 @@ import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
/**
* An implementation of {@link ItemWriter} which uses
* StAX and {@link EventWriterSerializer} for serializing object to XML.
*
* This output source also provides restart, statistics and transaction
* features by implementing corresponding interfaces.
*
* An implementation of {@link ItemWriter} which uses StAX and
* {@link EventWriterSerializer} for serializing object to XML.
*
* This output source also provides restart, statistics and transaction features
* by implementing corresponding interfaces.
*
* @author Peter Zozom
*
*
*/
public class StaxEventItemWriter implements ItemWriter, ItemStream,
StatisticsProvider, InitializingBean, DisposableBean {
public class StaxEventItemWriter implements ItemWriter, ItemStream, InitializingBean, DisposableBean {
// default encoding
private static final String DEFAULT_ENCODING = "UTF-8";
@@ -83,13 +81,15 @@ public class StaxEventItemWriter implements ItemWriter, ItemStream,
// signalizes that marshalling was restarted
private boolean restarted = false;
// TRUE means, that output file will be overwritten if exists - default is TRUE
// TRUE means, that output file will be overwritten if exists - default is
// TRUE
private boolean overwriteOutput = true;
// file channel
private FileChannel channel;
// wrapper for XML event writer that swallows StartDocument and EndDocument events
// wrapper for XML event writer that swallows StartDocument and EndDocument
// events
private XMLEventWriter eventWriter;
// XML event writer
@@ -107,10 +107,9 @@ public class StaxEventItemWriter implements ItemWriter, ItemStream,
// current count of processed records
private long currentRecordCount = 0;
/**
* Set output file.
*
*
* @param resource the output file
*/
public void setResource(Resource resource) {
@@ -119,7 +118,7 @@ public class StaxEventItemWriter implements ItemWriter, ItemStream,
/**
* Set Object to XML serializer.
*
*
* @param serializer the Object to XML serializer
*/
public void setSerializer(EventWriterSerializer serializer) {
@@ -128,7 +127,7 @@ public class StaxEventItemWriter implements ItemWriter, ItemStream,
/**
* Get used encoding.
*
*
* @return the encoding used
*/
public String getEncoding() {
@@ -137,7 +136,7 @@ public class StaxEventItemWriter implements ItemWriter, ItemStream,
/**
* Set encoding to be used for output file.
*
*
* @param encoding the encoding to be used
*/
public void setEncoding(String encoding) {
@@ -146,7 +145,7 @@ public class StaxEventItemWriter implements ItemWriter, ItemStream,
/**
* Get XML version.
*
*
* @return the XML version used
*/
public String getVersion() {
@@ -155,7 +154,7 @@ public class StaxEventItemWriter implements ItemWriter, ItemStream,
/**
* Set XML version to be used for output XML.
*
*
* @param version the XML version to be used
*/
public void setVersion(String version) {
@@ -164,7 +163,7 @@ public class StaxEventItemWriter implements ItemWriter, ItemStream,
/**
* Get the tag name of the root element.
*
*
* @return the root element tag name
*/
public String getRootTagName() {
@@ -172,8 +171,9 @@ public class StaxEventItemWriter implements ItemWriter, ItemStream,
}
/**
* Set the tag name of the root element. If not set, default name is used ("root").
*
* Set the tag name of the root element. If not set, default name is used
* ("root").
*
* @param rootTagName the tag name to be used for the root element
*/
public void setRootTagName(String rootTagName) {
@@ -182,7 +182,7 @@ public class StaxEventItemWriter implements ItemWriter, ItemStream,
/**
* Get attributes of the root element.
*
*
* @return attributes of the root element
*/
public Map getRootElementAttributes() {
@@ -191,7 +191,7 @@ public class StaxEventItemWriter implements ItemWriter, ItemStream,
/**
* Set the root element attributes to be written.
*
*
* @param rootElementAttributes attributes of the root element
*/
public void setRootElementAttributes(Map rootElementAttributes) {
@@ -199,8 +199,9 @@ public class StaxEventItemWriter implements ItemWriter, ItemStream,
}
/**
* Set "overwrite" flag for the output file. Flag is ignored when output file processing is restarted.
*
* Set "overwrite" flag for the output file. Flag is ignored when output
* file processing is restarted.
*
* @param shouldDeleteIfExists
*/
public void setOverwriteOutput(boolean overwriteOutput) {
@@ -237,7 +238,7 @@ public class StaxEventItemWriter implements ItemWriter, ItemStream,
/**
* Open the output source
*
*
* @see org.springframework.batch.item.ResourceLifecycle#open()
*/
public void open() {
@@ -253,16 +254,16 @@ public class StaxEventItemWriter implements ItemWriter, ItemStream,
File file;
FileOutputStream os = null;
try {
file = resource.getFile();
FileUtils.setUpOutputFile(file, restarted, overwriteOutput);
os = new FileOutputStream(file, true);
channel = os.getChannel();
setPosition(position);
} catch (IOException ioe) {
throw new DataAccessResourceFailureException(
"Unable to write to file resource: [" + resource + "]", ioe);
}
catch (IOException ioe) {
throw new DataAccessResourceFailureException("Unable to write to file resource: [" + resource + "]", ioe);
}
XMLOutputFactory outputFactory = XMLOutputFactory.newInstance();
@@ -273,9 +274,9 @@ public class StaxEventItemWriter implements ItemWriter, ItemStream,
if (!restarted) {
startDocument(delegateEventWriter);
}
} catch (XMLStreamException xse) {
throw new DataAccessResourceFailureException(
"Unable to write to file resource: [" + resource + "]", xse);
}
catch (XMLStreamException xse) {
throw new DataAccessResourceFailureException("Unable to write to file resource: [" + resource + "]", xse);
}
initialized = true;
@@ -289,27 +290,26 @@ public class StaxEventItemWriter implements ItemWriter, ItemStream,
* </ul>
* If this is not sufficient for you, simply override this method. Encoding,
* version and root tag name can be retrieved with corresponding getters.
*
* @param writer
* XML event writer
*
* @param writer XML event writer
* @throws XMLStreamException
*/
protected void startDocument(XMLEventWriter writer) throws XMLStreamException {
XMLEventFactory factory = XMLEventFactory.newInstance();
//write start document
// write start document
writer.add(factory.createStartDocument(getEncoding(), getVersion()));
//write root tag
// write root tag
writer.add(factory.createStartElement("", "", getRootTagName()));
//write root tag attributes
// write root tag attributes
if (!CollectionUtils.isEmpty(getRootElementAttributes())) {
for (Iterator i = getRootElementAttributes().entrySet().iterator(); i.hasNext();) {
Map.Entry entry = (Map.Entry)i.next();
writer.add(factory.createAttribute((String)entry.getKey(), (String)entry.getValue()));
Map.Entry entry = (Map.Entry) i.next();
writer.add(factory.createAttribute((String) entry.getKey(), (String) entry.getValue()));
}
}
@@ -319,29 +319,27 @@ public class StaxEventItemWriter implements ItemWriter, ItemStream,
/**
* Finishes the XML document. It closes any start tag and writes
* corresponding end tags.
*
* @param writer
* XML event writer
*
* @param writer XML event writer
* @throws XMLStreamException
*/
protected void endDocument(XMLEventWriter writer)
throws XMLStreamException {
protected void endDocument(XMLEventWriter writer) throws XMLStreamException {
//writer.writeEndDocument(); <- this doesn't work after restart
//we need to write end tag of the root element manually
// writer.writeEndDocument(); <- this doesn't work after restart
// we need to write end tag of the root element manually
writer.flush();
ByteBuffer bbuf = ByteBuffer.wrap(("</" + getRootTagName() + ">").getBytes());
try {
getChannel().write(bbuf);
} catch (IOException ioe) {
throw new DataAccessResourceFailureException(
"Unable to close file resource: [" + resource + "]", ioe);
}
catch (IOException ioe) {
throw new DataAccessResourceFailureException("Unable to close file resource: [" + resource + "]", ioe);
}
}
/**
* Close the output source.
*
*
* @see org.springframework.batch.item.ResourceLifecycle#close()
*/
public void close() {
@@ -350,19 +348,18 @@ public class StaxEventItemWriter implements ItemWriter, ItemStream,
endDocument(delegateEventWriter);
eventWriter.close();
channel.close();
} catch (XMLStreamException xse) {
throw new DataAccessResourceFailureException(
"Unable to close file resource: [" + resource + "]", xse);
}
catch (XMLStreamException xse) {
throw new DataAccessResourceFailureException("Unable to close file resource: [" + resource + "]", xse);
}
catch (IOException ioe) {
throw new DataAccessResourceFailureException(
"Unable to close file resource: [" + resource + "]", ioe);
throw new DataAccessResourceFailureException("Unable to close file resource: [" + resource + "]", ioe);
}
}
/**
* Write the value object to XML stream.
*
*
* @param output the value object
* @see org.springframework.batch.item.ItemWriter#write(java.lang.Object)
*/
@@ -399,12 +396,10 @@ public class StaxEventItemWriter implements ItemWriter, ItemStream,
long startAtPosition = 0;
//if restart data is provided, restart from provided offset
//otherwise start from beginning
if (data != null && data.getProperties() != null
&& data.getProperties().getProperty(RESTART_DATA_NAME) != null) {
startAtPosition = Long.parseLong(data.getProperties().getProperty(
RESTART_DATA_NAME));
// if restart data is provided, restart from provided offset
// otherwise start from beginning
if (data != null && data.getProperties() != null && data.getProperties().getProperty(RESTART_DATA_NAME) != null) {
startAtPosition = Long.parseLong(data.getProperties().getProperty(RESTART_DATA_NAME));
restarted = true;
}
@@ -426,9 +421,9 @@ public class StaxEventItemWriter implements ItemWriter, ItemStream,
}
/*
* Get the actual position in file channel.
* This method flushes any buffered data before position is read.
*
* Get the actual position in file channel. This method flushes any buffered
* data before position is read.
*
* @return byte offset in file channel
*/
private long getPosition() {
@@ -438,9 +433,9 @@ public class StaxEventItemWriter implements ItemWriter, ItemStream,
try {
eventWriter.flush();
position = channel.position();
} catch (Exception e) {
throw new DataAccessResourceFailureException(
"Unable to write to file resource: [" + resource + "]", e);
}
catch (Exception e) {
throw new DataAccessResourceFailureException("Unable to write to file resource: [" + resource + "]", e);
}
return position;
@@ -448,7 +443,7 @@ public class StaxEventItemWriter implements ItemWriter, ItemStream,
/*
* Set the file channel position.
*
*
* @param newPosition new file channel position
*/
private void setPosition(long newPosition) {
@@ -458,24 +453,23 @@ public class StaxEventItemWriter implements ItemWriter, ItemStream,
"Current file size is smaller than size at last commit");
channel.truncate(newPosition);
channel.position(newPosition);
} catch (IOException e) {
throw new DataAccessResourceFailureException(
"Unable to write to file resource: [" + resource + "]", e);
}
catch (IOException e) {
throw new DataAccessResourceFailureException("Unable to write to file resource: [" + resource + "]", e);
}
}
/**
* Encapsulates transaction events for the StaxEventWriterOutputSource.
*/
private class StaxEventWriterItemWriterTransactionSychronization extends
TransactionSynchronizationAdapter {
private class StaxEventWriterItemWriterTransactionSychronization extends TransactionSynchronizationAdapter {
public void afterCompletion(int status) {
if (status == TransactionSynchronization.STATUS_COMMITTED) {
transactionComitted();
} else if (status == TransactionSynchronization.STATUS_ROLLED_BACK) {
}
else if (status == TransactionSynchronization.STATUS_ROLLED_BACK) {
transactionRolledback();
}
}
@@ -488,10 +482,11 @@ public class StaxEventItemWriter implements ItemWriter, ItemStream,
private void transactionRolledback() {
currentRecordCount = lastCommitPointRecordCount;
//close output
// close output
close();
//and reopen it - we do this because we need to reopen stream
//reader at specified position - calling setPosition() is not enough!
// and reopen it - we do this because we need to reopen stream
// reader at specified position - calling setPosition() is not
// enough!
restarted = true;
open(lastCommitPointPosition);
}
@@ -502,5 +497,4 @@ public class StaxEventItemWriter implements ItemWriter, ItemStream,
return synchronization;
}
}

View File

@@ -26,8 +26,8 @@ package org.springframework.batch.item;
* The state that is stored is represented as {@link StreamContext} which
* enforces a requirement that any restart data can be represented by a
* Properties object. In general, the contract is that {@link StreamContext}
* that is returned via the {@link #getStreamContext()} method will be given back
* to the {@link #restoreFrom(StreamContext)} method, exactly as it was
* that is returned via the {@link #getStreamContext()} method will be given
* back to the {@link #restoreFrom(StreamContext)} method, exactly as it was
* provided.
* </p>
*
@@ -51,7 +51,8 @@ public interface ItemStream extends StreamContextProvider {
/**
* If any resources are needed for the stream to operate they need to be
* destroyed here.
* destroyed here. Once this method has been called all other methods
* (except open) may throw an exception.
*/
void close() throws StreamException;
}

View File

@@ -24,102 +24,104 @@ import java.util.Set;
import java.util.Map.Entry;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
/**
* Value object representing a context for an {@link ItemStream}. It is
* essentially a thin wrapper for a map that allows for type safety
* on reads. It also allows for dirty checking by setting a 'dirty'
* flag whenever any put is called.
* Value object representing a context for an {@link ItemStream}. It is
* essentially a thin wrapper for a map that allows for type safety on reads. It
* also allows for dirty checking by setting a 'dirty' flag whenever any put is
* called.
*/
public class StreamContext {
private boolean dirty = false;
private final Map map;
public StreamContext(){
public StreamContext() {
map = new HashMap();
}
public void putString(String key, String value){
public void putString(String key, String value) {
Assert.notNull(value);
put(key, value);
}
public void putLong(String key, long value){
public void putLong(String key, long value) {
put(key, new Long(value));
}
public void putDouble(String key, double value){
public void putDouble(String key, double value) {
put(key, new Double(value));
}
public void put(String key, Object value){
public void put(String key, Object value) {
dirty = true;
map.put(key, value);
}
public boolean isDirty() {
return dirty;
}
public String getString(String key){
return (String)readAndValidate(key, String.class);
public String getString(String key) {
return (String) readAndValidate(key, String.class);
}
public long getLong(String key){
return ((Long)readAndValidate(key, Long.class)).longValue();
public long getLong(String key) {
return ((Long) readAndValidate(key, Long.class)).longValue();
}
public Object get(String key){
public Object get(String key) {
return map.get(key);
}
private Object readAndValidate(String key, Class type){
private Object readAndValidate(String key, Class type) {
Object value = map.get(key);
if(!type.isInstance(key)){
throw new ClassCastException("Value is not of type: [" + type + "]");
}
// if (!type.isInstance(key)) {
// throw new ClassCastException("Value for key=[" + key + "] is not of type: [" + ClassUtils.getShortName(type)
// + "], it is [" + (value == null ? null : ClassUtils.getShortName(value.getClass())) + "]");
// }
return value;
}
public boolean isEmpty(){
public boolean isEmpty() {
return map.isEmpty();
}
public void clearDirtyFlag(){
public void clearDirtyFlag() {
dirty = false;
}
public Set entrySet(){
public Set entrySet() {
return map.entrySet();
}
public boolean containsKey(String key){
public boolean containsKey(String key) {
return map.containsKey(key);
}
public boolean containsValue(Object value){
public boolean containsValue(Object value) {
return map.containsValue(value);
}
public Properties getProperties(){
public Properties getProperties() {
Properties props = new Properties();
for(Iterator it = map.entrySet().iterator();it.hasNext();){
Entry entry = (Entry)it.next();
for (Iterator it = map.entrySet().iterator(); it.hasNext();) {
Entry entry = (Entry) it.next();
props.setProperty(entry.getKey().toString(), entry.getValue().toString());
}
return props;
}
}

View File

@@ -23,4 +23,11 @@ import org.springframework.batch.io.exception.BatchCriticalException;
*/
public class StreamException extends BatchCriticalException {
/**
* @param message
*/
public StreamException(String message) {
super(message);
}
}

View File

@@ -28,11 +28,13 @@ public class GenericStreamContext extends StreamContext {
super();
}
public GenericStreamContext(Properties data){
public GenericStreamContext(Properties data) {
super();
for(Iterator it = data.entrySet().iterator();it.hasNext();){
Entry entry = (Entry)it.next();
putString(entry.getKey().toString(), entry.getValue().toString());
if (data != null) {
for (Iterator it = data.entrySet().iterator(); it.hasNext();) {
Entry entry = (Entry) it.next();
putString(entry.getKey().toString(), entry.getValue().toString());
}
}
}
}

View File

@@ -1,89 +0,0 @@
/*
* Copyright 2006-2007 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.batch.statistics;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
/**
* Simple {@link StatisticsService} that makes no attempt to aggregate or
* resolve conflicts between key names. All the contributions registered are
* simply polled and added "as is" to the aggregate properties.
*
* @author Dave Syer
*
*/
public class SimpleStatisticsService implements StatisticsService {
private Map registry = new HashMap();
/**
* Simple aggregate statistics provider for the contributions registered
* under the given key.
*
* @see org.springframework.batch.statistics.StatisticsService#getStatistics(java.lang.Object)
*/
public Properties getStatistics(Object key) {
Set set = new LinkedHashSet();
synchronized (registry) {
Collection collection = (Collection) registry.get(key);
if (collection != null) {
set = new LinkedHashSet(collection);
}
}
return aggregate(set);
}
/**
* @param list a list of {@link StatisticsProvider}s
* @return aggregated statistics
*/
private Properties aggregate(Collection list) {
Properties result = new Properties();
for (Iterator iterator = list.iterator(); iterator.hasNext();) {
StatisticsProvider provider = (StatisticsProvider) iterator.next();
Properties properties = provider.getStatistics();
if (properties != null) {
result.putAll(properties);
}
}
return result;
}
/**
* Register a {@link StatisticsProvider} as one of the interesting providers
* under the provided key.
*
* @see org.springframework.batch.statistics.StatisticsService#register(java.lang.Object,
* org.springframework.batch.statistics.StatisticsProvider)
*/
public void register(Object key, StatisticsProvider provider) {
synchronized (registry) {
Set set = (Set) registry.get(key);
if (set == null) {
set = new LinkedHashSet();
registry.put(key, set);
}
set.add(provider);
}
}
}

View File

@@ -1,31 +0,0 @@
/*
* Copyright 2006-2007 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.batch.statistics;
import java.util.Properties;
/**
* Provides statistics for a given module run. Any class that implements
* this interface is guaranteeing that it will provide Statistics.
*
* @author Lucas Ward
*
*/
public interface StatisticsProvider {
Properties getStatistics();
}

View File

@@ -1,50 +0,0 @@
/*
* Copyright 2006-2007 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.batch.statistics;
import java.util.Properties;
/**
* Generalised statistics aggregation strategy. Clients register
* {@link StatisticsProvider} instances under a well-known key, and then when
* they ask for statistics by that key, they receive an aggregate of all the
* values given by registered providers.
*
* @author Dave Syer
*
*/
public interface StatisticsService {
/**
* Register the {@link StatisticsProvider} instance as one of possibly
* several that are associated with the given key.
*
* @param key the key under which to add the provider
* @param provider a {@link StatisticsProvider}
*/
void register(Object key, StatisticsProvider provider);
/**
* Extract and aggregate the statistics from all providers under this key.
*
* @param key the key under which {@link StatisticsProvider} instances might
* have been registered.
* @return {@link Properties} summarising the statistics of all providers
* registered under this key, or empty otherwise.
*/
Properties getStatistics(Object key);
}

View File

@@ -1,7 +0,0 @@
<html>
<body>
<p>
Infrastructure implementations of statistics concerns.
</p>
</body>
</html>