Change svn:eol-style to LF

This commit is contained in:
dsyer
2008-03-06 21:59:13 +00:00
parent ee5746a860
commit f5e85d5f63
372 changed files with 33472 additions and 33470 deletions

View File

@@ -1,77 +1,77 @@
/*
* 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.common;
import java.util.HashMap;
import java.util.Map;
/**
* A {@link ExceptionClassifier} that has only two classes of exception.
* Provides convenient methods for setting up and querying the classification
* with boolean return type.
*
* @author Dave Syer
*
*/
public class BinaryExceptionClassifier extends ExceptionClassifierSupport {
/**
* The classifier result for a non-default exception.
*/
public static final String NON_DEFAULT = "NON_DEFAULT";
private SubclassExceptionClassifier delegate = new SubclassExceptionClassifier();
/**
* Set the special exceptions. Any exception on the list, or subclasses
* thereof, will be classified as non-default.
*
* @param exceptionClasses defaults to {@link Exception}.
*/
public final void setExceptionClasses(Class[] exceptionClasses) {
Map temp = new HashMap();
for (int i = 0; i < exceptionClasses.length; i++) {
temp.put(exceptionClasses[i], NON_DEFAULT);
}
this.delegate.setTypeMap(temp);
}
/**
* Convenience method to return boolean if the throwable is classified as
* default.
*
* @param throwable the Throwable to classify
* @return true if it is default classified (i.e. not on the list provided
* in {@link #setExceptionClasses(Class[])}.
*/
public boolean isDefault(Throwable throwable) {
return classify(throwable).equals(DEFAULT);
}
/**
* Returns either {@link ExceptionClassifierSupport#DEFAULT} or
* {@link #NON_DEFAULT} depending on the type of the throwable. If the type
* of the throwable or one of its ancestors is on the exception class list
* the classification is as {@link #NON_DEFAULT}.
*
* @see #setExceptionClasses(Class[])
* @see ExceptionClassifierSupport#classify(Throwable)
*/
public Object classify(Throwable throwable) {
return delegate.classify(throwable);
}
}
/*
* 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.common;
import java.util.HashMap;
import java.util.Map;
/**
* A {@link ExceptionClassifier} that has only two classes of exception.
* Provides convenient methods for setting up and querying the classification
* with boolean return type.
*
* @author Dave Syer
*
*/
public class BinaryExceptionClassifier extends ExceptionClassifierSupport {
/**
* The classifier result for a non-default exception.
*/
public static final String NON_DEFAULT = "NON_DEFAULT";
private SubclassExceptionClassifier delegate = new SubclassExceptionClassifier();
/**
* Set the special exceptions. Any exception on the list, or subclasses
* thereof, will be classified as non-default.
*
* @param exceptionClasses defaults to {@link Exception}.
*/
public final void setExceptionClasses(Class[] exceptionClasses) {
Map temp = new HashMap();
for (int i = 0; i < exceptionClasses.length; i++) {
temp.put(exceptionClasses[i], NON_DEFAULT);
}
this.delegate.setTypeMap(temp);
}
/**
* Convenience method to return boolean if the throwable is classified as
* default.
*
* @param throwable the Throwable to classify
* @return true if it is default classified (i.e. not on the list provided
* in {@link #setExceptionClasses(Class[])}.
*/
public boolean isDefault(Throwable throwable) {
return classify(throwable).equals(DEFAULT);
}
/**
* Returns either {@link ExceptionClassifierSupport#DEFAULT} or
* {@link #NON_DEFAULT} depending on the type of the throwable. If the type
* of the throwable or one of its ancestors is on the exception class list
* the classification is as {@link #NON_DEFAULT}.
*
* @see #setExceptionClasses(Class[])
* @see ExceptionClassifierSupport#classify(Throwable)
*/
public Object classify(Throwable throwable) {
return delegate.classify(throwable);
}
}

View File

@@ -1,113 +1,113 @@
/*
* 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.common;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import org.springframework.util.Assert;
/**
*
* @author Dave Syer
*
*/
public class SubclassExceptionClassifier extends ExceptionClassifierSupport {
private Map classified = new HashMap();
/**
* Map of Throwable class types to keys for the classifier. Any subclass of
* the type provided will be classified as of the type given by the
* correspinding map entry value.
*
* @param typeMap the typeMap to set
*/
public final void setTypeMap(Map typeMap) {
Map map = new HashMap();
for (Iterator iter = typeMap.entrySet().iterator(); iter.hasNext();) {
Map.Entry entry = (Map.Entry) iter.next();
addRetryableExceptionClass(entry.getKey(), entry.getValue(), map);
}
this.classified = map;
}
/**
* Return the value from the type map whose key is the class of the given
* Throwable, or its nearest ancestor if a subclass.
*
* @see org.springframework.batch.common.ExceptionClassifierSupport#classify(java.lang.Throwable)
*/
public Object classify(Throwable throwable) {
if (throwable == null) {
return super.classify(throwable);
}
Class exceptionClass = throwable.getClass();
if (classified.containsKey(exceptionClass)) {
return classified.get(exceptionClass);
}
// check for subclasses
Set classes = new TreeSet(new ClassComparator());
classes.addAll(classified.keySet());
for (Iterator iterator = classes.iterator(); iterator.hasNext();) {
Class cls = (Class) iterator.next();
if (cls.isAssignableFrom(exceptionClass)) {
Object value = classified.get(cls);
addRetryableExceptionClass(exceptionClass, value, this.classified);
return value;
}
}
return super.classify(throwable);
}
private void addRetryableExceptionClass(Object candidateClass, Object classifiedAs, Map map) {
Assert.isAssignable(Class.class, candidateClass.getClass());
Class exceptionClass = (Class) candidateClass;
Assert.isAssignable(Throwable.class, exceptionClass);
map.put(exceptionClass, classifiedAs);
}
/**
* Comparator for classes to order by inheritance.
*
* @author Dave Syer
*
*/
private class ClassComparator implements Comparator {
/**
* @return 1 if arg0 is assignable from arg1
* @return -1 otherwise
* @see java.util.Comparator#compare(java.lang.Object, java.lang.Object)
*/
public int compare(Object arg0, Object arg1) {
Class cls0 = (Class) arg0;
Class cls1 = (Class) arg1;
if (cls0.isAssignableFrom(cls1)) {
return 1;
}
return -1;
}
}
}
/*
* 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.common;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import org.springframework.util.Assert;
/**
*
* @author Dave Syer
*
*/
public class SubclassExceptionClassifier extends ExceptionClassifierSupport {
private Map classified = new HashMap();
/**
* Map of Throwable class types to keys for the classifier. Any subclass of
* the type provided will be classified as of the type given by the
* correspinding map entry value.
*
* @param typeMap the typeMap to set
*/
public final void setTypeMap(Map typeMap) {
Map map = new HashMap();
for (Iterator iter = typeMap.entrySet().iterator(); iter.hasNext();) {
Map.Entry entry = (Map.Entry) iter.next();
addRetryableExceptionClass(entry.getKey(), entry.getValue(), map);
}
this.classified = map;
}
/**
* Return the value from the type map whose key is the class of the given
* Throwable, or its nearest ancestor if a subclass.
*
* @see org.springframework.batch.common.ExceptionClassifierSupport#classify(java.lang.Throwable)
*/
public Object classify(Throwable throwable) {
if (throwable == null) {
return super.classify(throwable);
}
Class exceptionClass = throwable.getClass();
if (classified.containsKey(exceptionClass)) {
return classified.get(exceptionClass);
}
// check for subclasses
Set classes = new TreeSet(new ClassComparator());
classes.addAll(classified.keySet());
for (Iterator iterator = classes.iterator(); iterator.hasNext();) {
Class cls = (Class) iterator.next();
if (cls.isAssignableFrom(exceptionClass)) {
Object value = classified.get(cls);
addRetryableExceptionClass(exceptionClass, value, this.classified);
return value;
}
}
return super.classify(throwable);
}
private void addRetryableExceptionClass(Object candidateClass, Object classifiedAs, Map map) {
Assert.isAssignable(Class.class, candidateClass.getClass());
Class exceptionClass = (Class) candidateClass;
Assert.isAssignable(Throwable.class, exceptionClass);
map.put(exceptionClass, classifiedAs);
}
/**
* Comparator for classes to order by inheritance.
*
* @author Dave Syer
*
*/
private class ClassComparator implements Comparator {
/**
* @return 1 if arg0 is assignable from arg1
* @return -1 otherwise
* @see java.util.Comparator#compare(java.lang.Object, java.lang.Object)
*/
public int compare(Object arg0, Object arg1) {
Class cls0 = (Class) arg0;
Class cls1 = (Class) arg1;
if (cls0.isAssignableFrom(cls1)) {
return 1;
}
return -1;
}
}
}

View File

@@ -1,242 +1,242 @@
/*
* 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.io.cursor;
import java.util.ArrayList;
import java.util.List;
import org.hibernate.ScrollableResults;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.StatelessSession;
import org.springframework.batch.io.Skippable;
import org.springframework.batch.item.ExecutionContext;
import org.springframework.batch.item.ExecutionContextUserSupport;
import org.springframework.batch.item.ItemReader;
import org.springframework.batch.item.ItemStream;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/**
* {@link ItemReader} for reading database records built on top of Hibernate.
*
* It executes the HQL {@link #queryString} when initialized and iterates over
* the result set as {@link #read()} method is called, returning an object
* corresponding to current row.
*
* Input source can be configured to use either {@link StatelessSession}
* sufficient for simple mappings without the need to cascade to associated
* objects or standard hibernate {@link Session} for more advanced mappings or
* when caching is desired.
*
* When stateful session is used it will be cleared after successful commit
* without being flushed (no inserts or updates are expected).
*
* @author Robert Kasanicky
* @author Dave Syer
*/
public class HibernateCursorItemReader extends ExecutionContextUserSupport implements ItemReader, ItemStream, Skippable, InitializingBean {
private static final String RESTART_DATA_ROW_NUMBER_KEY = "row.number";
private static final String SKIPPED_ROWS = "skipped.rows";
private SessionFactory sessionFactory;
private StatelessSession statelessSession;
private Session statefulSession;
private ScrollableResults cursor;
private String queryString;
private boolean useStatelessSession = true;
private int lastCommitRowNumber = 0;
private final List skippedRows = new ArrayList();
private int skipCount = 0;
/* Current count of processed records. */
private int currentProcessedRow = 0;
private boolean initialized = false;
private boolean saveState = false;
public HibernateCursorItemReader() {
setName(HibernateCursorItemReader.class.getSimpleName());
}
public Object read() {
if (!initialized) {
open(new ExecutionContext());
}
if (cursor.next()) {
currentProcessedRow++;
if (!skippedRows.isEmpty()) {
// while is necessary to handle successive skips.
while (skippedRows.contains(new Integer(currentProcessedRow))) {
if (!cursor.next()) {
return null;
}
currentProcessedRow++;
}
}
Object data = cursor.get(0);
return data;
}
return null;
}
/**
* Closes the result set cursor and hibernate session.
*/
public void close(ExecutionContext executionContext) {
initialized = false;
cursor.close();
currentProcessedRow = 0;
skippedRows.clear();
skipCount = 0;
if (useStatelessSession) {
statelessSession.close();
}
else {
statefulSession.close();
}
}
/**
* Creates cursor for the query.
*/
public void open(ExecutionContext executionContext) {
Assert.state(!initialized, "Cannot open an already opened ItemReader, call close first");
if (useStatelessSession) {
statelessSession = sessionFactory.openStatelessSession();
cursor = statelessSession.createQuery(queryString).scroll();
}
else {
statefulSession = sessionFactory.openSession();
cursor = statefulSession.createQuery(queryString).scroll();
}
initialized = true;
if (executionContext.containsKey(getKey(RESTART_DATA_ROW_NUMBER_KEY))) {
currentProcessedRow = Integer.parseInt(executionContext.getString(getKey(RESTART_DATA_ROW_NUMBER_KEY)));
cursor.setRowNumber(currentProcessedRow - 1);
}
if (executionContext.containsKey(getKey(SKIPPED_ROWS))) {
String[] skipped = StringUtils.commaDelimitedListToStringArray(executionContext.getString(getKey(SKIPPED_ROWS)));
for (int i = 0; i < skipped.length; i++) {
this.skippedRows.add(new Integer(skipped[i]));
}
}
}
/**
* @param sessionFactory hibernate session factory
*/
public void setSessionFactory(SessionFactory sessionFactory) {
this.sessionFactory = sessionFactory;
}
public void afterPropertiesSet() throws Exception {
Assert.notNull(sessionFactory);
Assert.hasLength(queryString);
}
/**
* @param queryString HQL query string
*/
public void setQueryString(String queryString) {
this.queryString = queryString;
}
/**
* Can be set only in uninitialized state.
*
* @param useStatelessSession <code>true</code> to use
* {@link StatelessSession} <code>false</code> to use standard hibernate
* {@link Session}
*/
public void setUseStatelessSession(boolean useStatelessSession) {
Assert.state(!initialized);
this.useStatelessSession = useStatelessSession;
}
/**
*/
public void update(ExecutionContext executionContext) {
if(saveState){
Assert.notNull(executionContext, "ExecutionContext must not be null");
executionContext.putString(getKey(RESTART_DATA_ROW_NUMBER_KEY), "" + currentProcessedRow);
String skipped = skippedRows.toString();
executionContext.putString(getKey(SKIPPED_ROWS), skipped.substring(1, skipped.length() - 1));
}
}
/**
* Skip the current row. If the transaction is rolled back, this row will
* not be represented when read() is called. For example, if you read in row
* 2, find the data to be bad, and call skip(), then continue processing and
* find
*/
public void skip() {
skippedRows.add(new Integer(currentProcessedRow));
skipCount++;
}
/**
* Mark is supported as long as this {@link ItemStream} is used in a
* single-threaded environment. The state backing the mark is a single
* counter, keeping track of the current position, so multiple threads
* cannot be accommodated.
*
* @see org.springframework.batch.item.ItemReader#mark()
*/
public void mark() {
lastCommitRowNumber = currentProcessedRow;
if (!useStatelessSession) {
statefulSession.clear();
}
}
/*
* (non-Javadoc)
* @see org.springframework.batch.item.stream.ItemStreamAdapter#reset(org.springframework.batch.item.ExecutionContext)
*/
public void reset() {
currentProcessedRow = lastCommitRowNumber;
if (lastCommitRowNumber == 0) {
cursor.beforeFirst();
}
else {
// Set the cursor so that next time it is advanced it will
// come back to the committed row.
cursor.setRowNumber(lastCommitRowNumber - 1);
}
}
public void setSaveState(boolean saveState) {
this.saveState = saveState;
}
}
/*
* 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.io.cursor;
import java.util.ArrayList;
import java.util.List;
import org.hibernate.ScrollableResults;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.StatelessSession;
import org.springframework.batch.io.Skippable;
import org.springframework.batch.item.ExecutionContext;
import org.springframework.batch.item.ExecutionContextUserSupport;
import org.springframework.batch.item.ItemReader;
import org.springframework.batch.item.ItemStream;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/**
* {@link ItemReader} for reading database records built on top of Hibernate.
*
* It executes the HQL {@link #queryString} when initialized and iterates over
* the result set as {@link #read()} method is called, returning an object
* corresponding to current row.
*
* Input source can be configured to use either {@link StatelessSession}
* sufficient for simple mappings without the need to cascade to associated
* objects or standard hibernate {@link Session} for more advanced mappings or
* when caching is desired.
*
* When stateful session is used it will be cleared after successful commit
* without being flushed (no inserts or updates are expected).
*
* @author Robert Kasanicky
* @author Dave Syer
*/
public class HibernateCursorItemReader extends ExecutionContextUserSupport implements ItemReader, ItemStream, Skippable, InitializingBean {
private static final String RESTART_DATA_ROW_NUMBER_KEY = "row.number";
private static final String SKIPPED_ROWS = "skipped.rows";
private SessionFactory sessionFactory;
private StatelessSession statelessSession;
private Session statefulSession;
private ScrollableResults cursor;
private String queryString;
private boolean useStatelessSession = true;
private int lastCommitRowNumber = 0;
private final List skippedRows = new ArrayList();
private int skipCount = 0;
/* Current count of processed records. */
private int currentProcessedRow = 0;
private boolean initialized = false;
private boolean saveState = false;
public HibernateCursorItemReader() {
setName(HibernateCursorItemReader.class.getSimpleName());
}
public Object read() {
if (!initialized) {
open(new ExecutionContext());
}
if (cursor.next()) {
currentProcessedRow++;
if (!skippedRows.isEmpty()) {
// while is necessary to handle successive skips.
while (skippedRows.contains(new Integer(currentProcessedRow))) {
if (!cursor.next()) {
return null;
}
currentProcessedRow++;
}
}
Object data = cursor.get(0);
return data;
}
return null;
}
/**
* Closes the result set cursor and hibernate session.
*/
public void close(ExecutionContext executionContext) {
initialized = false;
cursor.close();
currentProcessedRow = 0;
skippedRows.clear();
skipCount = 0;
if (useStatelessSession) {
statelessSession.close();
}
else {
statefulSession.close();
}
}
/**
* Creates cursor for the query.
*/
public void open(ExecutionContext executionContext) {
Assert.state(!initialized, "Cannot open an already opened ItemReader, call close first");
if (useStatelessSession) {
statelessSession = sessionFactory.openStatelessSession();
cursor = statelessSession.createQuery(queryString).scroll();
}
else {
statefulSession = sessionFactory.openSession();
cursor = statefulSession.createQuery(queryString).scroll();
}
initialized = true;
if (executionContext.containsKey(getKey(RESTART_DATA_ROW_NUMBER_KEY))) {
currentProcessedRow = Integer.parseInt(executionContext.getString(getKey(RESTART_DATA_ROW_NUMBER_KEY)));
cursor.setRowNumber(currentProcessedRow - 1);
}
if (executionContext.containsKey(getKey(SKIPPED_ROWS))) {
String[] skipped = StringUtils.commaDelimitedListToStringArray(executionContext.getString(getKey(SKIPPED_ROWS)));
for (int i = 0; i < skipped.length; i++) {
this.skippedRows.add(new Integer(skipped[i]));
}
}
}
/**
* @param sessionFactory hibernate session factory
*/
public void setSessionFactory(SessionFactory sessionFactory) {
this.sessionFactory = sessionFactory;
}
public void afterPropertiesSet() throws Exception {
Assert.notNull(sessionFactory);
Assert.hasLength(queryString);
}
/**
* @param queryString HQL query string
*/
public void setQueryString(String queryString) {
this.queryString = queryString;
}
/**
* Can be set only in uninitialized state.
*
* @param useStatelessSession <code>true</code> to use
* {@link StatelessSession} <code>false</code> to use standard hibernate
* {@link Session}
*/
public void setUseStatelessSession(boolean useStatelessSession) {
Assert.state(!initialized);
this.useStatelessSession = useStatelessSession;
}
/**
*/
public void update(ExecutionContext executionContext) {
if(saveState){
Assert.notNull(executionContext, "ExecutionContext must not be null");
executionContext.putString(getKey(RESTART_DATA_ROW_NUMBER_KEY), "" + currentProcessedRow);
String skipped = skippedRows.toString();
executionContext.putString(getKey(SKIPPED_ROWS), skipped.substring(1, skipped.length() - 1));
}
}
/**
* Skip the current row. If the transaction is rolled back, this row will
* not be represented when read() is called. For example, if you read in row
* 2, find the data to be bad, and call skip(), then continue processing and
* find
*/
public void skip() {
skippedRows.add(new Integer(currentProcessedRow));
skipCount++;
}
/**
* Mark is supported as long as this {@link ItemStream} is used in a
* single-threaded environment. The state backing the mark is a single
* counter, keeping track of the current position, so multiple threads
* cannot be accommodated.
*
* @see org.springframework.batch.item.ItemReader#mark()
*/
public void mark() {
lastCommitRowNumber = currentProcessedRow;
if (!useStatelessSession) {
statefulSession.clear();
}
}
/*
* (non-Javadoc)
* @see org.springframework.batch.item.stream.ItemStreamAdapter#reset(org.springframework.batch.item.ExecutionContext)
*/
public void reset() {
currentProcessedRow = lastCommitRowNumber;
if (lastCommitRowNumber == 0) {
cursor.beforeFirst();
}
else {
// Set the cursor so that next time it is advanced it will
// come back to the committed row.
cursor.setRowNumber(lastCommitRowNumber - 1);
}
}
public void setSaveState(boolean saveState) {
this.saveState = saveState;
}
}

View File

@@ -1,204 +1,204 @@
/*
* 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.io.driving;
import java.util.Iterator;
import java.util.List;
import org.springframework.batch.item.ExecutionContext;
import org.springframework.batch.item.ItemReader;
import org.springframework.batch.item.ItemStream;
import org.springframework.batch.item.exception.NoWorkFoundException;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.util.Assert;
/**
* <p>
* Convenience class for driving query input sources. Input Sources of this type
* use a 'driving query' to return back a list of keys. Upon each call to read,
* a new key is returned. To use the input source, inject a key generation
* strategy.
* </p>
*
* <p>
* Mutability: Because this base class cannot guarantee that the keys returned
* by subclasses are immutable, care should be taken to not modify a key value.
* Doing so would cause issues if a rollback occurs. For example, if a call to
* read() is made, and the returned key is modified, a rollback will cause the
* next call to read() to return the same object that was originally returned,
* since there is no way to create a defensive copy, and re-querying the
* database for all the keys would be too resource intensive.
* </p>
*
*
* @author Lucas Ward
* @since 1.0
*/
public class DrivingQueryItemReader implements ItemReader, InitializingBean,
ItemStream {
private boolean initialized = false;
private List keys;
private Iterator keysIterator;
private int currentIndex = 0;
private int lastCommitIndex = 0;
private KeyGenerator keyGenerator;
private boolean saveState = false;
public DrivingQueryItemReader() {
}
/**
* Initialize the input source with the provided keys list.
*
* @param keys
*/
public DrivingQueryItemReader(List keys) {
this.keys = keys;
this.keysIterator = keys.iterator();
}
/**
* Return the next key in the List. If the ItemReader has not been
* initialized yet, then {@link AbstractDrivingQueryItemReader.open()} will
* be called.
*
* @return next key in the list if not index is not at the last element,
* null otherwise.
*/
public Object read() {
if (!initialized) {
open(new ExecutionContext());
}
if (keysIterator.hasNext()) {
currentIndex++;
return keysIterator.next();
}
return null;
}
/**
* Get the current key. This method will return the same object returned by
* the last read() method. If the ItemReader hasn't been initialized yet,
* then null will be returned.
*
* @return the current key.
*/
protected Object getCurrentKey() {
if (initialized) {
return keys.get(currentIndex - 1);
}
return null;
}
/**
* Close the resource by setting the list of keys to null, allowing them to
* be garbage collected.
*/
public void close(ExecutionContext executionContext) {
initialized = false;
currentIndex = 0;
lastCommitIndex = 0;
keys = null;
keysIterator = null;
}
/**
* Initialize the input source by delegating to the subclass in order to
* retrieve the keys. The input source will also be registered with the
* {@link BatchTransactionSynchronizationManager} in order to ensure it is
* notified about commits and rollbacks.
*
* @throws IllegalStateException if the keys list is null or initialized is
* true.
*/
public void open(ExecutionContext executionContext) {
Assert.state(keys == null && !initialized, "Cannot open an already opened input source"
+ ", call close() first.");
keys = keyGenerator.retrieveKeys(executionContext);
if(keys == null || keys.size() == 0){
throw new NoWorkFoundException("KeyGenerator must return at least 1 key");
}
keysIterator = keys.listIterator();
initialized = true;
}
public void update(ExecutionContext executionContext) {
if(saveState){
Assert.notNull(executionContext, "ExecutionContext must not be null");
if(getCurrentKey() != null){
keyGenerator.saveState(getCurrentKey(), executionContext);
}
}
}
public void afterPropertiesSet() throws Exception {
Assert.notNull(keyGenerator, "The KeyGenerator must not be null.");
}
/**
* Set the key generation strategy to use for this input source.
*
* @param keyGenerator
*/
public void setKeyGenerator(KeyGenerator keyGenerator) {
this.keyGenerator = keyGenerator;
}
protected void transactionCommitted() {
mark();
}
protected void transactionRolledBack() {
reset();
}
/**
* Mark is supported as long as this {@link ItemStream} is used in a
* single-threaded environment. The state backing the mark is a single
* counter, keeping track of the current position, so multiple threads
* cannot be accommodated.
*
* @see org.springframework.batch.item.ItemReader#mark()
*/
public void mark() {
lastCommitIndex = currentIndex;
}
/*
* (non-Javadoc)
*
* @see org.springframework.batch.io.support.AbstractTransactionalIoSource#reset(org.springframework.batch.item.ExecutionContext)
*/
public void reset() {
keysIterator = keys.listIterator(lastCommitIndex);
}
public void setSaveState(boolean saveState) {
this.saveState = saveState;
}
}
/*
* 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.io.driving;
import java.util.Iterator;
import java.util.List;
import org.springframework.batch.item.ExecutionContext;
import org.springframework.batch.item.ItemReader;
import org.springframework.batch.item.ItemStream;
import org.springframework.batch.item.exception.NoWorkFoundException;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.util.Assert;
/**
* <p>
* Convenience class for driving query input sources. Input Sources of this type
* use a 'driving query' to return back a list of keys. Upon each call to read,
* a new key is returned. To use the input source, inject a key generation
* strategy.
* </p>
*
* <p>
* Mutability: Because this base class cannot guarantee that the keys returned
* by subclasses are immutable, care should be taken to not modify a key value.
* Doing so would cause issues if a rollback occurs. For example, if a call to
* read() is made, and the returned key is modified, a rollback will cause the
* next call to read() to return the same object that was originally returned,
* since there is no way to create a defensive copy, and re-querying the
* database for all the keys would be too resource intensive.
* </p>
*
*
* @author Lucas Ward
* @since 1.0
*/
public class DrivingQueryItemReader implements ItemReader, InitializingBean,
ItemStream {
private boolean initialized = false;
private List keys;
private Iterator keysIterator;
private int currentIndex = 0;
private int lastCommitIndex = 0;
private KeyGenerator keyGenerator;
private boolean saveState = false;
public DrivingQueryItemReader() {
}
/**
* Initialize the input source with the provided keys list.
*
* @param keys
*/
public DrivingQueryItemReader(List keys) {
this.keys = keys;
this.keysIterator = keys.iterator();
}
/**
* Return the next key in the List. If the ItemReader has not been
* initialized yet, then {@link AbstractDrivingQueryItemReader.open()} will
* be called.
*
* @return next key in the list if not index is not at the last element,
* null otherwise.
*/
public Object read() {
if (!initialized) {
open(new ExecutionContext());
}
if (keysIterator.hasNext()) {
currentIndex++;
return keysIterator.next();
}
return null;
}
/**
* Get the current key. This method will return the same object returned by
* the last read() method. If the ItemReader hasn't been initialized yet,
* then null will be returned.
*
* @return the current key.
*/
protected Object getCurrentKey() {
if (initialized) {
return keys.get(currentIndex - 1);
}
return null;
}
/**
* Close the resource by setting the list of keys to null, allowing them to
* be garbage collected.
*/
public void close(ExecutionContext executionContext) {
initialized = false;
currentIndex = 0;
lastCommitIndex = 0;
keys = null;
keysIterator = null;
}
/**
* Initialize the input source by delegating to the subclass in order to
* retrieve the keys. The input source will also be registered with the
* {@link BatchTransactionSynchronizationManager} in order to ensure it is
* notified about commits and rollbacks.
*
* @throws IllegalStateException if the keys list is null or initialized is
* true.
*/
public void open(ExecutionContext executionContext) {
Assert.state(keys == null && !initialized, "Cannot open an already opened input source"
+ ", call close() first.");
keys = keyGenerator.retrieveKeys(executionContext);
if(keys == null || keys.size() == 0){
throw new NoWorkFoundException("KeyGenerator must return at least 1 key");
}
keysIterator = keys.listIterator();
initialized = true;
}
public void update(ExecutionContext executionContext) {
if(saveState){
Assert.notNull(executionContext, "ExecutionContext must not be null");
if(getCurrentKey() != null){
keyGenerator.saveState(getCurrentKey(), executionContext);
}
}
}
public void afterPropertiesSet() throws Exception {
Assert.notNull(keyGenerator, "The KeyGenerator must not be null.");
}
/**
* Set the key generation strategy to use for this input source.
*
* @param keyGenerator
*/
public void setKeyGenerator(KeyGenerator keyGenerator) {
this.keyGenerator = keyGenerator;
}
protected void transactionCommitted() {
mark();
}
protected void transactionRolledBack() {
reset();
}
/**
* Mark is supported as long as this {@link ItemStream} is used in a
* single-threaded environment. The state backing the mark is a single
* counter, keeping track of the current position, so multiple threads
* cannot be accommodated.
*
* @see org.springframework.batch.item.ItemReader#mark()
*/
public void mark() {
lastCommitIndex = currentIndex;
}
/*
* (non-Javadoc)
*
* @see org.springframework.batch.io.support.AbstractTransactionalIoSource#reset(org.springframework.batch.item.ExecutionContext)
*/
public void reset() {
keysIterator = keys.listIterator(lastCommitIndex);
}
public void setSaveState(boolean saveState) {
this.saveState = saveState;
}
}

View File

@@ -1,67 +1,67 @@
/*
* 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.io.driving;
import org.springframework.batch.io.driving.support.IbatisKeyGenerator;
import org.springframework.orm.ibatis.SqlMapClientTemplate;
import com.ibatis.sqlmap.client.SqlMapClient;
/**
* Extension of {@link DrivingQueryItemReader} that maps keys to
* objects. An iBatis query id must be set to map and return each 'detail record'.
*
* @author Lucas Ward
* @see IbatisKeyGenerator
*/
public class IbatisDrivingQueryItemReader extends DrivingQueryItemReader {
private String detailsQueryId;
private SqlMapClientTemplate sqlMapClientTemplate;
/**
* Overridden read() that uses the returned key as arguments to the details query.
*
* @see org.springframework.batch.io.driving.DrivingQueryItemReader#read()
*/
public Object read() {
Object key = super.read();
if (key==null) {
return null;
}
return sqlMapClientTemplate.queryForObject(detailsQueryId, key);
}
/**
* @param detailsQueryId id of the iBATIS select statement that will used
* to retrieve an object for a single primary key from the list
* returned by driving query
*/
public void setDetailsQueryId(String detailsQueryId) {
this.detailsQueryId = detailsQueryId;
}
/**
* Set the {@link SqlMapClientTemplate} to use for this input source.
*
* @param sqlMapClientTemplate
*/
public void setSqlMapClient(
SqlMapClient sqlMapClient) {
this.sqlMapClientTemplate = new SqlMapClientTemplate(sqlMapClient);
}
}
/*
* 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.io.driving;
import org.springframework.batch.io.driving.support.IbatisKeyGenerator;
import org.springframework.orm.ibatis.SqlMapClientTemplate;
import com.ibatis.sqlmap.client.SqlMapClient;
/**
* Extension of {@link DrivingQueryItemReader} that maps keys to
* objects. An iBatis query id must be set to map and return each 'detail record'.
*
* @author Lucas Ward
* @see IbatisKeyGenerator
*/
public class IbatisDrivingQueryItemReader extends DrivingQueryItemReader {
private String detailsQueryId;
private SqlMapClientTemplate sqlMapClientTemplate;
/**
* Overridden read() that uses the returned key as arguments to the details query.
*
* @see org.springframework.batch.io.driving.DrivingQueryItemReader#read()
*/
public Object read() {
Object key = super.read();
if (key==null) {
return null;
}
return sqlMapClientTemplate.queryForObject(detailsQueryId, key);
}
/**
* @param detailsQueryId id of the iBATIS select statement that will used
* to retrieve an object for a single primary key from the list
* returned by driving query
*/
public void setDetailsQueryId(String detailsQueryId) {
this.detailsQueryId = detailsQueryId;
}
/**
* Set the {@link SqlMapClientTemplate} to use for this input source.
*
* @param sqlMapClientTemplate
*/
public void setSqlMapClient(
SqlMapClient sqlMapClient) {
this.sqlMapClientTemplate = new SqlMapClientTemplate(sqlMapClient);
}
}

View File

@@ -1,53 +1,53 @@
/*
* 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.io.driving.support;
import org.springframework.batch.item.ExecutionContext;
import org.springframework.jdbc.core.PreparedStatementSetter;
import org.springframework.jdbc.core.RowMapper;
/**
* {@link ExecutionContextRowMapper} extends the standard {@link RowMapper} interface to provide for
* converting an object returned from a RowMapper to {@link ExecutionContext} and back again. One
* of the most common use cases for this type of functionality is the DrivingQuery approach
* to sql processing. Using a {@link ExecutionContextRowMapper}, developers can create each unique key
* to suite their specific needs, and also describe how such a key would be converted to
* {@link ExecutionContext}, so that it can be serialized and stored.
*
* @author Lucas Ward
* @see RowMapper
* @since 1.0
*/
public interface ExecutionContextRowMapper extends RowMapper {
/**
* Given the provided composite key, return a {@link ExecutionContext} representation.
*
* @param key
* @return ExecutionContext representing the composite key.
* @throws IllegalArgumentException if key is null or of an unsupported type.
*/
public void mapKeys(Object key, ExecutionContext executionContext);
/**
* Given the provided restart data, return a PreparedStatementSeter that can
* be used as parameters to a JdbcTemplate.
*
* @param executionContext
* @return an array of objects that can be used as arguments to a JdbcTemplate.
*/
public PreparedStatementSetter createSetter(ExecutionContext executionContext);
}
/*
* 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.io.driving.support;
import org.springframework.batch.item.ExecutionContext;
import org.springframework.jdbc.core.PreparedStatementSetter;
import org.springframework.jdbc.core.RowMapper;
/**
* {@link ExecutionContextRowMapper} extends the standard {@link RowMapper} interface to provide for
* converting an object returned from a RowMapper to {@link ExecutionContext} and back again. One
* of the most common use cases for this type of functionality is the DrivingQuery approach
* to sql processing. Using a {@link ExecutionContextRowMapper}, developers can create each unique key
* to suite their specific needs, and also describe how such a key would be converted to
* {@link ExecutionContext}, so that it can be serialized and stored.
*
* @author Lucas Ward
* @see RowMapper
* @since 1.0
*/
public interface ExecutionContextRowMapper extends RowMapper {
/**
* Given the provided composite key, return a {@link ExecutionContext} representation.
*
* @param key
* @return ExecutionContext representing the composite key.
* @throws IllegalArgumentException if key is null or of an unsupported type.
*/
public void mapKeys(Object key, ExecutionContext executionContext);
/**
* Given the provided restart data, return a PreparedStatementSeter that can
* be used as parameters to a JdbcTemplate.
*
* @param executionContext
* @return an array of objects that can be used as arguments to a JdbcTemplate.
*/
public PreparedStatementSetter createSetter(ExecutionContext executionContext);
}

View File

@@ -1,101 +1,101 @@
package org.springframework.batch.io.driving.support;
import java.util.List;
import org.springframework.batch.io.driving.DrivingQueryItemReader;
import org.springframework.batch.io.driving.KeyGenerator;
import org.springframework.batch.item.ExecutionContext;
import org.springframework.batch.item.ExecutionContextUserSupport;
import org.springframework.orm.ibatis.SqlMapClientTemplate;
import org.springframework.util.Assert;
import com.ibatis.sqlmap.client.SqlMapClient;
/**
* {@link KeyGenerator} based on iBATIS ORM framework. It is functionally
* similar to {@link SingleColumnJdbcKeyGenerator} but does not make assumptions
* about the primary key structure.
*
* @author Robert Kasanicky
* @author Lucas Ward
* @see DrivingQueryItemReader
*/
public class IbatisKeyGenerator extends ExecutionContextUserSupport implements KeyGenerator {
private static final String RESTART_KEY = "key.index";
private SqlMapClientTemplate sqlMapClientTemplate;
private String drivingQuery;
private String restartQueryId;
public IbatisKeyGenerator() {
setName(IbatisKeyGenerator.class.getSimpleName());
}
/*
* Retrieve the keys using the provided driving query id.
*
* @see org.springframework.batch.io.support.AbstractDrivingQueryItemReader#retrieveKeys()
*/
public List retrieveKeys(ExecutionContext executionContext) {
if (executionContext.containsKey(getKey(RESTART_KEY))) {
Object key = executionContext.getString(getKey(RESTART_KEY));
return sqlMapClientTemplate.queryForList(restartQueryId, key);
}
else {
return sqlMapClientTemplate.queryForList(drivingQuery);
}
}
/*
* (non-Javadoc)
* @see org.springframework.batch.io.driving.KeyGenerator#getKeyAsExecutionContext(java.lang.Object)
*/
public void saveState(Object key, ExecutionContext executionContext) {
Assert.notNull(key, "Key must not be null");
Assert.notNull(executionContext, "ExecutionContext must be null");
executionContext.putString(getKey(RESTART_KEY), key.toString());
}
/*
* (non-Javadoc)
* @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet()
*/
public void afterPropertiesSet() throws Exception {
Assert.notNull(sqlMapClientTemplate, "SqlMaperClientTemplate must not be null.");
Assert.hasText(drivingQuery, "The DrivingQuery must not be null or empty.");
}
/**
* @param sqlMapClient configured iBATIS client
*/
public void setSqlMapClient(SqlMapClient sqlMapClient) {
this.sqlMapClientTemplate = new SqlMapClientTemplate();
this.sqlMapClientTemplate.setSqlMapClient(sqlMapClient);
}
/**
* @param drivingQueryId id of the iBATIS select statement that will be used
* to retrieve the list of primary keys
*/
public void setDrivingQueryId(String drivingQueryId) {
this.drivingQuery = drivingQueryId;
}
/**
* Set the id of the restart query.
*
* @param restartQueryId id of the iBatis select statement that will be used
* to retrieve the list of primary keys after a restart.
*/
public void setRestartQueryId(String restartQueryId) {
this.restartQueryId = restartQueryId;
}
public final SqlMapClientTemplate getSqlMapClientTemplate() {
return sqlMapClientTemplate;
}
}
package org.springframework.batch.io.driving.support;
import java.util.List;
import org.springframework.batch.io.driving.DrivingQueryItemReader;
import org.springframework.batch.io.driving.KeyGenerator;
import org.springframework.batch.item.ExecutionContext;
import org.springframework.batch.item.ExecutionContextUserSupport;
import org.springframework.orm.ibatis.SqlMapClientTemplate;
import org.springframework.util.Assert;
import com.ibatis.sqlmap.client.SqlMapClient;
/**
* {@link KeyGenerator} based on iBATIS ORM framework. It is functionally
* similar to {@link SingleColumnJdbcKeyGenerator} but does not make assumptions
* about the primary key structure.
*
* @author Robert Kasanicky
* @author Lucas Ward
* @see DrivingQueryItemReader
*/
public class IbatisKeyGenerator extends ExecutionContextUserSupport implements KeyGenerator {
private static final String RESTART_KEY = "key.index";
private SqlMapClientTemplate sqlMapClientTemplate;
private String drivingQuery;
private String restartQueryId;
public IbatisKeyGenerator() {
setName(IbatisKeyGenerator.class.getSimpleName());
}
/*
* Retrieve the keys using the provided driving query id.
*
* @see org.springframework.batch.io.support.AbstractDrivingQueryItemReader#retrieveKeys()
*/
public List retrieveKeys(ExecutionContext executionContext) {
if (executionContext.containsKey(getKey(RESTART_KEY))) {
Object key = executionContext.getString(getKey(RESTART_KEY));
return sqlMapClientTemplate.queryForList(restartQueryId, key);
}
else {
return sqlMapClientTemplate.queryForList(drivingQuery);
}
}
/*
* (non-Javadoc)
* @see org.springframework.batch.io.driving.KeyGenerator#getKeyAsExecutionContext(java.lang.Object)
*/
public void saveState(Object key, ExecutionContext executionContext) {
Assert.notNull(key, "Key must not be null");
Assert.notNull(executionContext, "ExecutionContext must be null");
executionContext.putString(getKey(RESTART_KEY), key.toString());
}
/*
* (non-Javadoc)
* @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet()
*/
public void afterPropertiesSet() throws Exception {
Assert.notNull(sqlMapClientTemplate, "SqlMaperClientTemplate must not be null.");
Assert.hasText(drivingQuery, "The DrivingQuery must not be null or empty.");
}
/**
* @param sqlMapClient configured iBATIS client
*/
public void setSqlMapClient(SqlMapClient sqlMapClient) {
this.sqlMapClientTemplate = new SqlMapClientTemplate();
this.sqlMapClientTemplate.setSqlMapClient(sqlMapClient);
}
/**
* @param drivingQueryId id of the iBATIS select statement that will be used
* to retrieve the list of primary keys
*/
public void setDrivingQueryId(String drivingQueryId) {
this.drivingQuery = drivingQueryId;
}
/**
* Set the id of the restart query.
*
* @param restartQueryId id of the iBatis select statement that will be used
* to retrieve the list of primary keys after a restart.
*/
public void setRestartQueryId(String restartQueryId) {
this.restartQueryId = restartQueryId;
}
public final SqlMapClientTemplate getSqlMapClientTemplate() {
return sqlMapClientTemplate;
}
}

View File

@@ -1,138 +1,138 @@
/*
* 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.io.driving.support;
import java.util.List;
import org.springframework.batch.io.driving.DrivingQueryItemReader;
import org.springframework.batch.io.driving.KeyGenerator;
import org.springframework.batch.item.ExecutionContext;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/**
* <p>
* Jdbc implementation of the {@link KeyGenerator} interface that works for
* composite keys. (i.e. keys represented by multiple columns) A sql query to be
* used to return the keys and a {@link ExecutionContextRowMapper} to map each
* row in the resultset to an Object must be set in order to work correctly.
* </p>
*
* @author Lucas Ward
* @see DrivingQueryItemReader
* @since 1.0
*/
public class MultipleColumnJdbcKeyGenerator implements KeyGenerator {
private JdbcTemplate jdbcTemplate;
private ExecutionContextRowMapper keyMapper = new ColumnMapExecutionContextRowMapper();
private String sql;
private String restartSql;
public MultipleColumnJdbcKeyGenerator() {
}
/**
* Construct a new ItemReader.
*
* @param jdbcTemplate
* @param sql - Sql statement that returns all keys to process.
* @param keyMapper - RowMapper that maps each row of the ResultSet to an
* object.
*/
public MultipleColumnJdbcKeyGenerator(JdbcTemplate jdbcTemplate, String sql) {
this();
Assert.notNull(jdbcTemplate, "The JdbcTemplate must not be null.");
Assert.hasText(sql, "The DrivingQuery must not be null or empty.");
Assert.notNull(keyMapper, "The key RowMapper must not be null.");
this.jdbcTemplate = jdbcTemplate;
this.sql = sql;
}
/*
* (non-Javadoc)
* @see org.springframework.batch.io.sql.scratch.AbstractDrivingQueryItemReader#retrieveKeys()
*/
public List retrieveKeys(ExecutionContext executionContext) {
Assert.state(keyMapper != null, "KeyMapper must not be null.");
Assert.state(StringUtils.hasText(restartSql), "The RestartQuery must not be null or empty"
+ " in order to restart.");
if (executionContext.size() > 0) {
return jdbcTemplate.query(restartSql, keyMapper.createSetter(executionContext), keyMapper);
}
else {
return jdbcTemplate.query(sql, keyMapper);
}
}
/*
* (non-Javadoc)
* @see org.springframework.batch.io.driving.KeyGenerator#getKeyAsExecutionContext(java.lang.Object)
*/
public void saveState(Object key, ExecutionContext executionContext) {
Assert.state(keyMapper != null, "Key mapper must not be null.");
keyMapper.mapKeys(key, executionContext);
}
/**
* Set the query to use to retrieve keys in order to restore the previous
* state for restart.
*
* @param restartQuery
*/
public void setRestartSql(String restartQuery) {
this.restartSql = restartQuery;
}
/*
* (non-Javadoc)
* @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet()
*/
public void afterPropertiesSet() throws Exception {
Assert.notNull(jdbcTemplate, "The JdbcTemplate must not be null.");
Assert.hasText(sql, "The DrivingQuery must not be null or empty.");
Assert.notNull(keyMapper, "The key RowMapper must not be null.");
}
/**
* Set the {@link ExecutionContextRowMapper} to be used to map a resultset
* to keys.
*
* @param keyMapper
*/
public void setKeyMapper(ExecutionContextRowMapper keyMapper) {
this.keyMapper = keyMapper;
}
/**
* Set the sql statement used to generate the keys list.
*
* @param sql
*/
public void setSql(String sql) {
this.sql = sql;
}
public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
}
/*
* 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.io.driving.support;
import java.util.List;
import org.springframework.batch.io.driving.DrivingQueryItemReader;
import org.springframework.batch.io.driving.KeyGenerator;
import org.springframework.batch.item.ExecutionContext;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/**
* <p>
* Jdbc implementation of the {@link KeyGenerator} interface that works for
* composite keys. (i.e. keys represented by multiple columns) A sql query to be
* used to return the keys and a {@link ExecutionContextRowMapper} to map each
* row in the resultset to an Object must be set in order to work correctly.
* </p>
*
* @author Lucas Ward
* @see DrivingQueryItemReader
* @since 1.0
*/
public class MultipleColumnJdbcKeyGenerator implements KeyGenerator {
private JdbcTemplate jdbcTemplate;
private ExecutionContextRowMapper keyMapper = new ColumnMapExecutionContextRowMapper();
private String sql;
private String restartSql;
public MultipleColumnJdbcKeyGenerator() {
}
/**
* Construct a new ItemReader.
*
* @param jdbcTemplate
* @param sql - Sql statement that returns all keys to process.
* @param keyMapper - RowMapper that maps each row of the ResultSet to an
* object.
*/
public MultipleColumnJdbcKeyGenerator(JdbcTemplate jdbcTemplate, String sql) {
this();
Assert.notNull(jdbcTemplate, "The JdbcTemplate must not be null.");
Assert.hasText(sql, "The DrivingQuery must not be null or empty.");
Assert.notNull(keyMapper, "The key RowMapper must not be null.");
this.jdbcTemplate = jdbcTemplate;
this.sql = sql;
}
/*
* (non-Javadoc)
* @see org.springframework.batch.io.sql.scratch.AbstractDrivingQueryItemReader#retrieveKeys()
*/
public List retrieveKeys(ExecutionContext executionContext) {
Assert.state(keyMapper != null, "KeyMapper must not be null.");
Assert.state(StringUtils.hasText(restartSql), "The RestartQuery must not be null or empty"
+ " in order to restart.");
if (executionContext.size() > 0) {
return jdbcTemplate.query(restartSql, keyMapper.createSetter(executionContext), keyMapper);
}
else {
return jdbcTemplate.query(sql, keyMapper);
}
}
/*
* (non-Javadoc)
* @see org.springframework.batch.io.driving.KeyGenerator#getKeyAsExecutionContext(java.lang.Object)
*/
public void saveState(Object key, ExecutionContext executionContext) {
Assert.state(keyMapper != null, "Key mapper must not be null.");
keyMapper.mapKeys(key, executionContext);
}
/**
* Set the query to use to retrieve keys in order to restore the previous
* state for restart.
*
* @param restartQuery
*/
public void setRestartSql(String restartQuery) {
this.restartSql = restartQuery;
}
/*
* (non-Javadoc)
* @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet()
*/
public void afterPropertiesSet() throws Exception {
Assert.notNull(jdbcTemplate, "The JdbcTemplate must not be null.");
Assert.hasText(sql, "The DrivingQuery must not be null or empty.");
Assert.notNull(keyMapper, "The key RowMapper must not be null.");
}
/**
* Set the {@link ExecutionContextRowMapper} to be used to map a resultset
* to keys.
*
* @param keyMapper
*/
public void setKeyMapper(ExecutionContextRowMapper keyMapper) {
this.keyMapper = keyMapper;
}
/**
* Set the sql statement used to generate the keys list.
*
* @param sql
*/
public void setSql(String sql) {
this.sql = sql;
}
public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
}

View File

@@ -1,152 +1,152 @@
/*
* 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.io.driving.support;
import java.util.List;
import org.springframework.batch.io.driving.KeyGenerator;
import org.springframework.batch.item.ExecutionContext;
import org.springframework.batch.item.ExecutionContextUserSupport;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.SingleColumnRowMapper;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/**
* <p>
* Jdbc {@link KeyGenerator} implementation that only works for a single column
* key. A sql query must be passed in which will be used to return a list of
* keys. Each key will be mapped by a {@link RowMapper} that returns a mapped
* key. By default, the {@link SingleColumnRowMapper} is used, and will convert
* keys into well known types at runtime. It is extremely important to note that
* only one column should be mapped to an object and returned as a key. If
* multiple columns are returned as a key in this strategy, then restart will
* not function properly. Instead a strategy that supports keys comprised of
* multiple columns should be used.
*
* <p>
* Restartability: Because the key is only one column, restart is made much more
* simple. Before each commit, the last processed key is returned to be stored
* as restart data. Upon restart, that same key is given back to restore from,
* using a separate 'RestartQuery'. This means that only the keys remaining to
* be processed are returned, rather than returning the original list of keys
* and iterating forward to that last committed point.
* </p>
*
* @author Lucas Ward
* @since 1.0
*/
public class SingleColumnJdbcKeyGenerator extends ExecutionContextUserSupport implements KeyGenerator {
private static final String RESTART_KEY = "key";
private JdbcTemplate jdbcTemplate;
private String sql;
private String restartSql;
private RowMapper keyMapper = new SingleColumnRowMapper();
public SingleColumnJdbcKeyGenerator() {
setName(SingleColumnJdbcKeyGenerator.class.getSimpleName());
}
/**
* Constructs a new instance using the provided jdbcTemplate and string
* representing the sql statement that should be used to retrieve keys.
*
* @param jdbcTemplate
* @param sql
* @throws IllegalArgumentException if jdbcTemplate is null.
* @throws IllegalArgumentException if sql string is empty or null.
*/
public SingleColumnJdbcKeyGenerator(JdbcTemplate jdbcTemplate, String sql) {
this();
Assert.notNull(jdbcTemplate, "JdbcTemplate must not be null.");
Assert.hasText(sql, "The sql statement must not be null or empty.");
this.jdbcTemplate = jdbcTemplate;
this.sql = sql;
}
/*
* (non-Javadoc)
* @see org.springframework.batch.io.driving.KeyGenerationStrategy#retrieveKeys()
*/
public List retrieveKeys(ExecutionContext executionContext) {
Assert.notNull(executionContext, "The restart data must not be null.");
if (executionContext.containsKey(RESTART_KEY)) {
Assert.state(StringUtils.hasText(restartSql), "The RestartQuery must not be null or empty"
+ " in order to restart.");
return jdbcTemplate.query(restartSql, new Object[] { executionContext.getString(RESTART_KEY) }, keyMapper);
}
else{
return jdbcTemplate.query(sql, keyMapper);
}
}
/**
* Get the restart data representing the last processed key.
*
* @see KeyGenerator#saveState(Object)
* @throws IllegalArgumentException if key is null.
*/
public void saveState(Object key, ExecutionContext executionContext) {
Assert.notNull(key, "The key must not be null.");
executionContext.putString(RESTART_KEY, key.toString());
}
/*
* (non-Javadoc)
* @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet()
*/
public void afterPropertiesSet() throws Exception {
Assert.notNull(jdbcTemplate, "JdbcTemplate must not be null.");
Assert.hasText(sql, "The DrivingQuery must not be null or empty.");
}
/**
* Set the {@link RowMapper} to be used to map each key to an object.
*
* @param keyMapper
*/
public void setKeyMapper(RowMapper keyMapper) {
this.keyMapper = keyMapper;
}
/**
* Set the SQL query to be used to return the remaining keys to be
* processed.
*
* @param restartSql
*/
public void setRestartSql(String restartSql) {
this.restartSql = restartSql;
}
/**
* Set the SQL statement to be used to return the keys to be processed.
*
* @param sql
*/
public void setSql(String sql) {
this.sql = sql;
}
}
/*
* 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.io.driving.support;
import java.util.List;
import org.springframework.batch.io.driving.KeyGenerator;
import org.springframework.batch.item.ExecutionContext;
import org.springframework.batch.item.ExecutionContextUserSupport;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.SingleColumnRowMapper;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/**
* <p>
* Jdbc {@link KeyGenerator} implementation that only works for a single column
* key. A sql query must be passed in which will be used to return a list of
* keys. Each key will be mapped by a {@link RowMapper} that returns a mapped
* key. By default, the {@link SingleColumnRowMapper} is used, and will convert
* keys into well known types at runtime. It is extremely important to note that
* only one column should be mapped to an object and returned as a key. If
* multiple columns are returned as a key in this strategy, then restart will
* not function properly. Instead a strategy that supports keys comprised of
* multiple columns should be used.
*
* <p>
* Restartability: Because the key is only one column, restart is made much more
* simple. Before each commit, the last processed key is returned to be stored
* as restart data. Upon restart, that same key is given back to restore from,
* using a separate 'RestartQuery'. This means that only the keys remaining to
* be processed are returned, rather than returning the original list of keys
* and iterating forward to that last committed point.
* </p>
*
* @author Lucas Ward
* @since 1.0
*/
public class SingleColumnJdbcKeyGenerator extends ExecutionContextUserSupport implements KeyGenerator {
private static final String RESTART_KEY = "key";
private JdbcTemplate jdbcTemplate;
private String sql;
private String restartSql;
private RowMapper keyMapper = new SingleColumnRowMapper();
public SingleColumnJdbcKeyGenerator() {
setName(SingleColumnJdbcKeyGenerator.class.getSimpleName());
}
/**
* Constructs a new instance using the provided jdbcTemplate and string
* representing the sql statement that should be used to retrieve keys.
*
* @param jdbcTemplate
* @param sql
* @throws IllegalArgumentException if jdbcTemplate is null.
* @throws IllegalArgumentException if sql string is empty or null.
*/
public SingleColumnJdbcKeyGenerator(JdbcTemplate jdbcTemplate, String sql) {
this();
Assert.notNull(jdbcTemplate, "JdbcTemplate must not be null.");
Assert.hasText(sql, "The sql statement must not be null or empty.");
this.jdbcTemplate = jdbcTemplate;
this.sql = sql;
}
/*
* (non-Javadoc)
* @see org.springframework.batch.io.driving.KeyGenerationStrategy#retrieveKeys()
*/
public List retrieveKeys(ExecutionContext executionContext) {
Assert.notNull(executionContext, "The restart data must not be null.");
if (executionContext.containsKey(RESTART_KEY)) {
Assert.state(StringUtils.hasText(restartSql), "The RestartQuery must not be null or empty"
+ " in order to restart.");
return jdbcTemplate.query(restartSql, new Object[] { executionContext.getString(RESTART_KEY) }, keyMapper);
}
else{
return jdbcTemplate.query(sql, keyMapper);
}
}
/**
* Get the restart data representing the last processed key.
*
* @see KeyGenerator#saveState(Object)
* @throws IllegalArgumentException if key is null.
*/
public void saveState(Object key, ExecutionContext executionContext) {
Assert.notNull(key, "The key must not be null.");
executionContext.putString(RESTART_KEY, key.toString());
}
/*
* (non-Javadoc)
* @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet()
*/
public void afterPropertiesSet() throws Exception {
Assert.notNull(jdbcTemplate, "JdbcTemplate must not be null.");
Assert.hasText(sql, "The DrivingQuery must not be null or empty.");
}
/**
* Set the {@link RowMapper} to be used to map each key to an object.
*
* @param keyMapper
*/
public void setKeyMapper(RowMapper keyMapper) {
this.keyMapper = keyMapper;
}
/**
* Set the SQL query to be used to return the remaining keys to be
* processed.
*
* @param restartSql
*/
public void setRestartSql(String restartSql) {
this.restartSql = restartSql;
}
/**
* Set the SQL statement to be used to return the keys to be processed.
*
* @param sql
*/
public void setSql(String sql) {
this.sql = sql;
}
}

View File

@@ -1,23 +1,23 @@
package org.springframework.batch.io.exception;
import org.springframework.util.MethodInvoker;
/**
* Indicates an error has been encountered
* while trying to dynamically call a method e.g. using {@link MethodInvoker}.
*
* @author Robert Kasanicky
*/
public class DynamicMethodInvocationException extends RuntimeException {
//generated value
private static final long serialVersionUID = -6056786139731564040L;
public DynamicMethodInvocationException(Throwable cause){
super(cause);
}
public DynamicMethodInvocationException(String message, Throwable cause) {
super(message, cause);
}
}
package org.springframework.batch.io.exception;
import org.springframework.util.MethodInvoker;
/**
* Indicates an error has been encountered
* while trying to dynamically call a method e.g. using {@link MethodInvoker}.
*
* @author Robert Kasanicky
*/
public class DynamicMethodInvocationException extends RuntimeException {
//generated value
private static final long serialVersionUID = -6056786139731564040L;
public DynamicMethodInvocationException(Throwable cause){
super(cause);
}
public DynamicMethodInvocationException(String message, Throwable cause) {
super(message, cause);
}
}

View File

@@ -1,64 +1,64 @@
/*
* 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.io.exception;
/**
* Exception thrown when errors are encountered
* parsing flat files. The original input, typically
* a line, can be passed in, so that latter catches
* can write out the original input to a log, or
* an error table.
*
* @author Lucas Ward
*
*/
public class FlatFileParsingException extends ParsingException {
private static final long serialVersionUID = 2529197834044942724L;
private String input;
private int lineNumber;
public FlatFileParsingException(String message, String input) {
super(message);
this.input = input;
}
public FlatFileParsingException(String message, String input, int lineNumber) {
super(message);
this.input = input;
this.lineNumber = lineNumber;
}
public FlatFileParsingException(String message, Throwable cause, String input, int lineNumber) {
super(message, cause);
this.input = input;
this.lineNumber = lineNumber;
}
public FlatFileParsingException(Throwable cause, String input) {
super(cause);
this.input = input;
}
public String getInput() {
return input;
}
public int getLineNumber() {
return lineNumber;
}
}
/*
* 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.io.exception;
/**
* Exception thrown when errors are encountered
* parsing flat files. The original input, typically
* a line, can be passed in, so that latter catches
* can write out the original input to a log, or
* an error table.
*
* @author Lucas Ward
*
*/
public class FlatFileParsingException extends ParsingException {
private static final long serialVersionUID = 2529197834044942724L;
private String input;
private int lineNumber;
public FlatFileParsingException(String message, String input) {
super(message);
this.input = input;
}
public FlatFileParsingException(String message, String input, int lineNumber) {
super(message);
this.input = input;
this.lineNumber = lineNumber;
}
public FlatFileParsingException(String message, Throwable cause, String input, int lineNumber) {
super(message, cause);
this.input = input;
this.lineNumber = lineNumber;
}
public FlatFileParsingException(Throwable cause, String input) {
super(cause);
this.input = input;
}
public String getInput() {
return input;
}
public int getLineNumber() {
return lineNumber;
}
}

View File

@@ -1,40 +1,40 @@
/*
* 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.io.exception;
/**
* Exception indicating that an error has been encountered
* parsing io, typically from a file.
*
* @author Lucas Ward
*
*/
public class ParsingException extends RuntimeException {
private static final long serialVersionUID = 2953386084409312312L;
public ParsingException(String message) {
super(message);
}
public ParsingException(String message, Throwable cause) {
super(message, cause);
}
public ParsingException(Throwable cause) {
super(cause);
}
}
/*
* 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.io.exception;
/**
* Exception indicating that an error has been encountered
* parsing io, typically from a file.
*
* @author Lucas Ward
*
*/
public class ParsingException extends RuntimeException {
private static final long serialVersionUID = 2953386084409312312L;
public ParsingException(String message) {
super(message);
}
public ParsingException(String message, Throwable cause) {
super(message, cause);
}
public ParsingException(Throwable cause) {
super(cause);
}
}

View File

@@ -1,489 +1,489 @@
/*
* 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.io.file;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.channels.Channels;
import java.nio.channels.FileChannel;
import java.nio.charset.UnsupportedCharsetException;
import org.springframework.batch.io.exception.ConfigurationException;
import org.springframework.batch.io.exception.InfrastructureException;
import org.springframework.batch.io.file.mapping.FieldSet;
import org.springframework.batch.io.file.mapping.FieldSetCreator;
import org.springframework.batch.io.file.transform.DelimitedLineAggregator;
import org.springframework.batch.io.file.transform.LineAggregator;
import org.springframework.batch.item.ExecutionContext;
import org.springframework.batch.item.ExecutionContextUserSupport;
import org.springframework.batch.item.ItemStream;
import org.springframework.batch.item.ItemWriter;
import org.springframework.batch.item.exception.ClearFailedException;
import org.springframework.batch.item.exception.FlushFailedException;
import org.springframework.batch.item.exception.ResetFailedException;
import org.springframework.batch.item.exception.StreamException;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.core.io.Resource;
import org.springframework.dao.DataAccessResourceFailureException;
import org.springframework.util.Assert;
/**
* This class is an output target that writes data to a file or stream. The
* writer also provides restart, statistics and transaction features by
* implementing corresponding interfaces where possible (with a file). The
* location of the file is defined by a {@link Resource} and must represent a
* writable file.<br/>
*
* Uses buffered writer to improve performance.<br/>
*
* Use {@link #write(String)} method to output a line to an item writer.
*
* <p>
* This class will be updated in the future to use a buffering approach to
* handling transactions, rather than outputting directly to the file and
* truncating on rollback
* </p>
*
* @author Waseem Malik
* @author Tomas Slanina
* @author Robert Kasanicky
* @author Dave Syer
*/
public class FlatFileItemWriter extends ExecutionContextUserSupport implements ItemWriter, ItemStream,
InitializingBean {
private static final String LINE_SEPARATOR = System.getProperty("line.separator");
private static final String WRITTEN_STATISTICS_NAME = "written";
private static final String RESTART_COUNT_STATISTICS_NAME = "restart.count";
private static final String RESTART_DATA_NAME = "current.count";
private Resource resource;
private OutputState state = null;
private LineAggregator lineAggregator = new DelimitedLineAggregator();
private FieldSetCreator fieldSetCreator;
public FlatFileItemWriter() {
setName(FlatFileItemWriter.class.getSimpleName());
}
/**
* Assert that mandatory properties (resource) are set.
* @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet()
*/
public void afterPropertiesSet() throws Exception {
Assert.notNull(resource, "The resource must be set");
Assert.notNull(fieldSetCreator, "A FieldSetUnmapper must be provided.");
File file = resource.getFile();
Assert.state(!file.exists() || file.canWrite(), "Resource is not writable: [" + resource + "]");
}
/**
* Public setter for the {@link LineAggregator}. This will be used to
* translate a {@link FieldSet} into a line for output.
*
* @param lineAggregator the {@link LineAggregator} to set
*/
public void setLineAggregator(LineAggregator lineAggregator) {
this.lineAggregator = lineAggregator;
}
/**
* Public setter for the {@link FieldSetCreator}. This will be used to
* transform the item into a {@link FieldSet} before it is aggregated by the
* {@link LineAggregator}.
*
* @param fieldSetCreator the {@link FieldSetCreator} to set
*/
public void setFieldSetUnmapper(FieldSetCreator fieldSetCreator) {
this.fieldSetCreator = fieldSetCreator;
}
/**
* Setter for resource. Represents a file that can be written.
*
* @param resource
*/
public void setResource(Resource resource) {
this.resource = resource;
}
/**
* Writes out a string followed by a "new line", where the format of the new
* line separator is determined by the underlying operating system. If the
* input is not a String and a converter is available the converter will be
* applied and then this method recursively called with the result. If the
* input is an array or collection each value will be written to a separate
* line (recursively calling this method for each value). If no converter is
* supplied the input object's toString method will be used.<br/>
*
* @param data Object (a String or Object that can be converted) to be
* written to output stream
* @throws Exception if the transformer or file output fail
*/
public void write(Object data) throws Exception {
FieldSet fieldSet = fieldSetCreator.mapItem(data);
getOutputState().write(lineAggregator.aggregate(fieldSet) + LINE_SEPARATOR);
}
/**
* @see ResourceLifecycle#close(ExecutionContext)
*/
public void close(ExecutionContext executionContext) {
if (state != null) {
getOutputState().close();
state = null;
}
}
/**
* Sets encoding for output template.
*/
public void setEncoding(String newEncoding) {
getOutputState().setEncoding(newEncoding);
}
/**
* Sets buffer size for output template
*/
public void setBufferSize(int newSize) {
getOutputState().setBufferSize(newSize);
}
/**
* @param shouldDeleteIfExists the shouldDeleteIfExists to set
*/
public void setShouldDeleteIfExists(boolean shouldDeleteIfExists) {
getOutputState().setShouldDeleteIfExists(shouldDeleteIfExists);
}
/**
* Initialize the Output Template.
* @see ResourceLifecycle#open()
*/
public void open(ExecutionContext executionContext) {
OutputState outputState = getOutputState();
if(executionContext.containsKey(getKey(RESTART_DATA_NAME))){
outputState.restoreFrom(executionContext);
}
}
/**
* @see ItemStream#update(ExecutionContext)
*/
public void update(ExecutionContext executionContext) {
if (state == null) {
throw new StreamException("ItemStream not open or already closed.");
}
Assert.notNull(executionContext, "ExecutionContext must not be null");
executionContext.putLong(getKey(RESTART_DATA_NAME), state.position());
executionContext.putLong(getKey(WRITTEN_STATISTICS_NAME), state.linesWritten);
executionContext.putLong(getKey(RESTART_COUNT_STATISTICS_NAME), state.restartCount);
}
// Returns object representing state.
private OutputState getOutputState() {
if (state == null) {
state = new OutputState();
}
return (OutputState) state;
}
/**
* Encapsulates the runtime state of the writer. All state changing
* operations on the writer go through this class.
*/
private class OutputState {
// default encoding for writing to output files - set to UTF-8.
private static final String DEFAULT_CHARSET = "UTF-8";
private static final int DEFAULT_BUFFER_SIZE = 2048;
// The bufferedWriter over the file channel that is actually written
BufferedWriter outputBufferedWriter;
FileChannel fileChannel;
// this represents the charset encoding (if any is needed) for the
// output file
String encoding = DEFAULT_CHARSET;
// Optional write buffer size
int bufferSize = DEFAULT_BUFFER_SIZE;
boolean restarted = false;
boolean initialized = false;
long lastMarkedByteOffsetPosition = 0;
long linesWritten = 0;
long restartCount = 0;
boolean shouldDeleteIfExists = true;
/**
* Return the byte offset position of the cursor in the output file as a
* long integer.
*/
public long position() {
long pos = 0;
if (fileChannel == null) {
return 0;
}
try {
outputBufferedWriter.flush();
pos = fileChannel.position();
}
catch (IOException e) {
throw new InfrastructureException("An Error occured while trying to get filechannel position", e);
}
return pos;
}
/**
* @param properties
*/
public void restoreFrom(ExecutionContext executionContext) {
lastMarkedByteOffsetPosition = executionContext.getLong(getKey(RESTART_DATA_NAME));
restarted = true;
}
/**
* @param shouldDeleteIfExists2
*/
public void setShouldDeleteIfExists(boolean shouldDeleteIfExists) {
this.shouldDeleteIfExists = shouldDeleteIfExists;
}
/**
* @param newSize
*/
public void setBufferSize(int newSize) {
bufferSize = newSize;
}
/**
* @param newEncoding
*/
public void setEncoding(String newEncoding) {
encoding = newEncoding;
}
/**
* Close the open resource and reset counters.
*/
public void close() {
initialized = false;
restarted = false;
try {
if (outputBufferedWriter == null) {
return;
}
outputBufferedWriter.close();
fileChannel.close();
}
catch (IOException ioe) {
throw new StreamException("Unable to close the the ItemWriter", ioe);
}
}
/**
* @param data
* @param offset
* @param length
*/
public void write(String line) {
if (!initialized) {
initializeBufferedWriter();
}
try {
outputBufferedWriter.write(line);
outputBufferedWriter.flush();
linesWritten++;
}
catch (IOException e) {
throw new InfrastructureException("An Error occured while trying to write to FlatFileItemWriter", e);
}
}
/**
* Truncate the output at the last known good point.
*/
public void truncate() {
try {
fileChannel.truncate(lastMarkedByteOffsetPosition);
fileChannel.position(lastMarkedByteOffsetPosition);
}
catch (IOException e) {
throw new InfrastructureException("An Error occured while truncating output file", e);
}
}
/**
* Mark the current position.
*/
public void mark() {
lastMarkedByteOffsetPosition = this.position();
}
/**
* Creates the buffered writer for the output file channel based on
* configuration information.
*/
private void initializeBufferedWriter() {
File file;
try {
file = resource.getFile();
// If the output source was restarted, keep existing file.
// If the output source was not restarted, check following:
// - if the file should be deleted, delete it if it was exiting
// and create blank file,
// - if the file should not be deleted, if it already exists,
// throw an exception,
// - if the file was not existing, create new.
if (!restarted) {
if (file.exists()) {
if (shouldDeleteIfExists) {
file.delete();
}
else {
throw new StreamException("Resource already exists: " + resource);
}
}
String parent = file.getParent();
if (parent != null) {
new File(parent).mkdirs();
}
file.createNewFile();
}
}
catch (IOException ioe) {
throw new DataAccessResourceFailureException("Unable to write to file resource: [" + resource + "]",
ioe);
}
try {
fileChannel = (new FileOutputStream(file.getAbsolutePath(), true)).getChannel();
}
catch (FileNotFoundException fnfe) {
throw new ConfigurationException("Bad filename property parameter " + file, fnfe);
}
outputBufferedWriter = getBufferedWriter(fileChannel, encoding, bufferSize);
// in case of restarting reset position to last commited point
if (restarted) {
this.reset();
}
initialized = true;
linesWritten = 0;
}
/**
* Returns the buffered writer opened to the beginning of the file
* specified by the absolute path name contained in absoluteFileName.
*/
private BufferedWriter getBufferedWriter(FileChannel fileChannel, String encoding, int bufferSize) {
try {
BufferedWriter outputBufferedWriter = null;
// If a buffer was requested, allocate.
if (bufferSize > 0) {
outputBufferedWriter = new BufferedWriter(Channels.newWriter(fileChannel, encoding), bufferSize);
}
else {
outputBufferedWriter = new BufferedWriter(Channels.newWriter(fileChannel, encoding));
}
return outputBufferedWriter;
}
catch (UnsupportedCharsetException ucse) {
throw new StreamException("Bad encoding configuration for output file " + fileChannel, ucse);
}
}
/**
* Resets the file writer's current position to the point stored in the
* last marked byte offset position variable. It first checks to make
* sure the current size of the file is not less than the byte position
* to be moved to (if it is, throws an environment exception), then it
* truncates the file to that reset position, and set the cursor to
* start writing at that point.
*/
public void reset() throws InfrastructureException {
checkFileSize();
getOutputState().truncate();
}
/**
* Checks (on setState) to make sure that the current output file's size
* is not smaller than the last saved commit point. If it is, then the
* file has been damaged in some way and whole task must be started over
* again from the beginning.
*/
private void checkFileSize() {
long size = -1;
try {
outputBufferedWriter.flush();
size = fileChannel.size();
}
catch (IOException e) {
throw new InfrastructureException("An Error occured while checking file size", e);
}
if (size < lastMarkedByteOffsetPosition) {
throw new InfrastructureException("Current file size is smaller than size at last commit");
}
}
}
public void clear() throws ClearFailedException {
try {
getOutputState().reset();
}
catch (InfrastructureException e) {
throw new ResetFailedException(e);
}
}
public void flush() throws FlushFailedException {
getOutputState().mark();
}
}
/*
* 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.io.file;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.channels.Channels;
import java.nio.channels.FileChannel;
import java.nio.charset.UnsupportedCharsetException;
import org.springframework.batch.io.exception.ConfigurationException;
import org.springframework.batch.io.exception.InfrastructureException;
import org.springframework.batch.io.file.mapping.FieldSet;
import org.springframework.batch.io.file.mapping.FieldSetCreator;
import org.springframework.batch.io.file.transform.DelimitedLineAggregator;
import org.springframework.batch.io.file.transform.LineAggregator;
import org.springframework.batch.item.ExecutionContext;
import org.springframework.batch.item.ExecutionContextUserSupport;
import org.springframework.batch.item.ItemStream;
import org.springframework.batch.item.ItemWriter;
import org.springframework.batch.item.exception.ClearFailedException;
import org.springframework.batch.item.exception.FlushFailedException;
import org.springframework.batch.item.exception.ResetFailedException;
import org.springframework.batch.item.exception.StreamException;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.core.io.Resource;
import org.springframework.dao.DataAccessResourceFailureException;
import org.springframework.util.Assert;
/**
* This class is an output target that writes data to a file or stream. The
* writer also provides restart, statistics and transaction features by
* implementing corresponding interfaces where possible (with a file). The
* location of the file is defined by a {@link Resource} and must represent a
* writable file.<br/>
*
* Uses buffered writer to improve performance.<br/>
*
* Use {@link #write(String)} method to output a line to an item writer.
*
* <p>
* This class will be updated in the future to use a buffering approach to
* handling transactions, rather than outputting directly to the file and
* truncating on rollback
* </p>
*
* @author Waseem Malik
* @author Tomas Slanina
* @author Robert Kasanicky
* @author Dave Syer
*/
public class FlatFileItemWriter extends ExecutionContextUserSupport implements ItemWriter, ItemStream,
InitializingBean {
private static final String LINE_SEPARATOR = System.getProperty("line.separator");
private static final String WRITTEN_STATISTICS_NAME = "written";
private static final String RESTART_COUNT_STATISTICS_NAME = "restart.count";
private static final String RESTART_DATA_NAME = "current.count";
private Resource resource;
private OutputState state = null;
private LineAggregator lineAggregator = new DelimitedLineAggregator();
private FieldSetCreator fieldSetCreator;
public FlatFileItemWriter() {
setName(FlatFileItemWriter.class.getSimpleName());
}
/**
* Assert that mandatory properties (resource) are set.
* @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet()
*/
public void afterPropertiesSet() throws Exception {
Assert.notNull(resource, "The resource must be set");
Assert.notNull(fieldSetCreator, "A FieldSetUnmapper must be provided.");
File file = resource.getFile();
Assert.state(!file.exists() || file.canWrite(), "Resource is not writable: [" + resource + "]");
}
/**
* Public setter for the {@link LineAggregator}. This will be used to
* translate a {@link FieldSet} into a line for output.
*
* @param lineAggregator the {@link LineAggregator} to set
*/
public void setLineAggregator(LineAggregator lineAggregator) {
this.lineAggregator = lineAggregator;
}
/**
* Public setter for the {@link FieldSetCreator}. This will be used to
* transform the item into a {@link FieldSet} before it is aggregated by the
* {@link LineAggregator}.
*
* @param fieldSetCreator the {@link FieldSetCreator} to set
*/
public void setFieldSetUnmapper(FieldSetCreator fieldSetCreator) {
this.fieldSetCreator = fieldSetCreator;
}
/**
* Setter for resource. Represents a file that can be written.
*
* @param resource
*/
public void setResource(Resource resource) {
this.resource = resource;
}
/**
* Writes out a string followed by a "new line", where the format of the new
* line separator is determined by the underlying operating system. If the
* input is not a String and a converter is available the converter will be
* applied and then this method recursively called with the result. If the
* input is an array or collection each value will be written to a separate
* line (recursively calling this method for each value). If no converter is
* supplied the input object's toString method will be used.<br/>
*
* @param data Object (a String or Object that can be converted) to be
* written to output stream
* @throws Exception if the transformer or file output fail
*/
public void write(Object data) throws Exception {
FieldSet fieldSet = fieldSetCreator.mapItem(data);
getOutputState().write(lineAggregator.aggregate(fieldSet) + LINE_SEPARATOR);
}
/**
* @see ResourceLifecycle#close(ExecutionContext)
*/
public void close(ExecutionContext executionContext) {
if (state != null) {
getOutputState().close();
state = null;
}
}
/**
* Sets encoding for output template.
*/
public void setEncoding(String newEncoding) {
getOutputState().setEncoding(newEncoding);
}
/**
* Sets buffer size for output template
*/
public void setBufferSize(int newSize) {
getOutputState().setBufferSize(newSize);
}
/**
* @param shouldDeleteIfExists the shouldDeleteIfExists to set
*/
public void setShouldDeleteIfExists(boolean shouldDeleteIfExists) {
getOutputState().setShouldDeleteIfExists(shouldDeleteIfExists);
}
/**
* Initialize the Output Template.
* @see ResourceLifecycle#open()
*/
public void open(ExecutionContext executionContext) {
OutputState outputState = getOutputState();
if(executionContext.containsKey(getKey(RESTART_DATA_NAME))){
outputState.restoreFrom(executionContext);
}
}
/**
* @see ItemStream#update(ExecutionContext)
*/
public void update(ExecutionContext executionContext) {
if (state == null) {
throw new StreamException("ItemStream not open or already closed.");
}
Assert.notNull(executionContext, "ExecutionContext must not be null");
executionContext.putLong(getKey(RESTART_DATA_NAME), state.position());
executionContext.putLong(getKey(WRITTEN_STATISTICS_NAME), state.linesWritten);
executionContext.putLong(getKey(RESTART_COUNT_STATISTICS_NAME), state.restartCount);
}
// Returns object representing state.
private OutputState getOutputState() {
if (state == null) {
state = new OutputState();
}
return (OutputState) state;
}
/**
* Encapsulates the runtime state of the writer. All state changing
* operations on the writer go through this class.
*/
private class OutputState {
// default encoding for writing to output files - set to UTF-8.
private static final String DEFAULT_CHARSET = "UTF-8";
private static final int DEFAULT_BUFFER_SIZE = 2048;
// The bufferedWriter over the file channel that is actually written
BufferedWriter outputBufferedWriter;
FileChannel fileChannel;
// this represents the charset encoding (if any is needed) for the
// output file
String encoding = DEFAULT_CHARSET;
// Optional write buffer size
int bufferSize = DEFAULT_BUFFER_SIZE;
boolean restarted = false;
boolean initialized = false;
long lastMarkedByteOffsetPosition = 0;
long linesWritten = 0;
long restartCount = 0;
boolean shouldDeleteIfExists = true;
/**
* Return the byte offset position of the cursor in the output file as a
* long integer.
*/
public long position() {
long pos = 0;
if (fileChannel == null) {
return 0;
}
try {
outputBufferedWriter.flush();
pos = fileChannel.position();
}
catch (IOException e) {
throw new InfrastructureException("An Error occured while trying to get filechannel position", e);
}
return pos;
}
/**
* @param properties
*/
public void restoreFrom(ExecutionContext executionContext) {
lastMarkedByteOffsetPosition = executionContext.getLong(getKey(RESTART_DATA_NAME));
restarted = true;
}
/**
* @param shouldDeleteIfExists2
*/
public void setShouldDeleteIfExists(boolean shouldDeleteIfExists) {
this.shouldDeleteIfExists = shouldDeleteIfExists;
}
/**
* @param newSize
*/
public void setBufferSize(int newSize) {
bufferSize = newSize;
}
/**
* @param newEncoding
*/
public void setEncoding(String newEncoding) {
encoding = newEncoding;
}
/**
* Close the open resource and reset counters.
*/
public void close() {
initialized = false;
restarted = false;
try {
if (outputBufferedWriter == null) {
return;
}
outputBufferedWriter.close();
fileChannel.close();
}
catch (IOException ioe) {
throw new StreamException("Unable to close the the ItemWriter", ioe);
}
}
/**
* @param data
* @param offset
* @param length
*/
public void write(String line) {
if (!initialized) {
initializeBufferedWriter();
}
try {
outputBufferedWriter.write(line);
outputBufferedWriter.flush();
linesWritten++;
}
catch (IOException e) {
throw new InfrastructureException("An Error occured while trying to write to FlatFileItemWriter", e);
}
}
/**
* Truncate the output at the last known good point.
*/
public void truncate() {
try {
fileChannel.truncate(lastMarkedByteOffsetPosition);
fileChannel.position(lastMarkedByteOffsetPosition);
}
catch (IOException e) {
throw new InfrastructureException("An Error occured while truncating output file", e);
}
}
/**
* Mark the current position.
*/
public void mark() {
lastMarkedByteOffsetPosition = this.position();
}
/**
* Creates the buffered writer for the output file channel based on
* configuration information.
*/
private void initializeBufferedWriter() {
File file;
try {
file = resource.getFile();
// If the output source was restarted, keep existing file.
// If the output source was not restarted, check following:
// - if the file should be deleted, delete it if it was exiting
// and create blank file,
// - if the file should not be deleted, if it already exists,
// throw an exception,
// - if the file was not existing, create new.
if (!restarted) {
if (file.exists()) {
if (shouldDeleteIfExists) {
file.delete();
}
else {
throw new StreamException("Resource already exists: " + resource);
}
}
String parent = file.getParent();
if (parent != null) {
new File(parent).mkdirs();
}
file.createNewFile();
}
}
catch (IOException ioe) {
throw new DataAccessResourceFailureException("Unable to write to file resource: [" + resource + "]",
ioe);
}
try {
fileChannel = (new FileOutputStream(file.getAbsolutePath(), true)).getChannel();
}
catch (FileNotFoundException fnfe) {
throw new ConfigurationException("Bad filename property parameter " + file, fnfe);
}
outputBufferedWriter = getBufferedWriter(fileChannel, encoding, bufferSize);
// in case of restarting reset position to last commited point
if (restarted) {
this.reset();
}
initialized = true;
linesWritten = 0;
}
/**
* Returns the buffered writer opened to the beginning of the file
* specified by the absolute path name contained in absoluteFileName.
*/
private BufferedWriter getBufferedWriter(FileChannel fileChannel, String encoding, int bufferSize) {
try {
BufferedWriter outputBufferedWriter = null;
// If a buffer was requested, allocate.
if (bufferSize > 0) {
outputBufferedWriter = new BufferedWriter(Channels.newWriter(fileChannel, encoding), bufferSize);
}
else {
outputBufferedWriter = new BufferedWriter(Channels.newWriter(fileChannel, encoding));
}
return outputBufferedWriter;
}
catch (UnsupportedCharsetException ucse) {
throw new StreamException("Bad encoding configuration for output file " + fileChannel, ucse);
}
}
/**
* Resets the file writer's current position to the point stored in the
* last marked byte offset position variable. It first checks to make
* sure the current size of the file is not less than the byte position
* to be moved to (if it is, throws an environment exception), then it
* truncates the file to that reset position, and set the cursor to
* start writing at that point.
*/
public void reset() throws InfrastructureException {
checkFileSize();
getOutputState().truncate();
}
/**
* Checks (on setState) to make sure that the current output file's size
* is not smaller than the last saved commit point. If it is, then the
* file has been damaged in some way and whole task must be started over
* again from the beginning.
*/
private void checkFileSize() {
long size = -1;
try {
outputBufferedWriter.flush();
size = fileChannel.size();
}
catch (IOException e) {
throw new InfrastructureException("An Error occured while checking file size", e);
}
if (size < lastMarkedByteOffsetPosition) {
throw new InfrastructureException("Current file size is smaller than size at last commit");
}
}
}
public void clear() throws ClearFailedException {
try {
getOutputState().reset();
}
catch (InfrastructureException e) {
throw new ResetFailedException(e);
}
}
public void flush() throws FlushFailedException {
getOutputState().mark();
}
}

View File

@@ -1,53 +1,53 @@
/*
* 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.io.file.mapping;
/**
* Pass through {@link FieldSetMapper} useful for passing a {@link FieldSet}
* back directly rather than a mapped object.
*
* @author Lucas Ward
*
*/
public class PassThroughFieldSetMapper implements FieldSetMapper, FieldSetCreator {
/*
* (non-Javadoc)
* @see org.springframework.batch.io.file.FieldSetMapper#mapLine(org.springframework.batch.io.file.FieldSet)
*/
public Object mapLine(FieldSet fs) {
return fs;
}
/**
* If the input is a {@link FieldSet} pass it to the caller. Otherwise
* convert to a String with toString() and convert it to a single field
* {@link FieldSet}.
*
* @see org.springframework.batch.io.file.mapping.FieldSetCreator#mapItem(java.lang.Object)
*/
public FieldSet mapItem(Object data) {
if (data instanceof FieldSet) {
return (FieldSet) data;
}
if (!(data instanceof String)) {
data = "" + data;
}
return new DefaultFieldSet(new String[] { (String) data });
}
}
/*
* 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.io.file.mapping;
/**
* Pass through {@link FieldSetMapper} useful for passing a {@link FieldSet}
* back directly rather than a mapped object.
*
* @author Lucas Ward
*
*/
public class PassThroughFieldSetMapper implements FieldSetMapper, FieldSetCreator {
/*
* (non-Javadoc)
* @see org.springframework.batch.io.file.FieldSetMapper#mapLine(org.springframework.batch.io.file.FieldSet)
*/
public Object mapLine(FieldSet fs) {
return fs;
}
/**
* If the input is a {@link FieldSet} pass it to the caller. Otherwise
* convert to a String with toString() and convert it to a single field
* {@link FieldSet}.
*
* @see org.springframework.batch.io.file.mapping.FieldSetCreator#mapItem(java.lang.Object)
*/
public FieldSet mapItem(Object data) {
if (data instanceof FieldSet) {
return (FieldSet) data;
}
if (!(data instanceof String)) {
data = "" + data;
}
return new DefaultFieldSet(new String[] { (String) data });
}
}

View File

@@ -1,339 +1,339 @@
/*
* 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.io.file.separator;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import org.springframework.batch.item.ExecutionContext;
import org.springframework.batch.item.ItemReader;
import org.springframework.batch.item.ItemStream;
import org.springframework.batch.item.exception.MarkFailedException;
import org.springframework.batch.item.exception.ResetFailedException;
import org.springframework.batch.item.exception.StreamException;
import org.springframework.batch.item.stream.ItemStreamSupport;
import org.springframework.core.io.Resource;
import org.springframework.util.Assert;
/**
* An input source that reads lines one by one from a resource. <br/>
*
* A line can consist of multiple lines in the input resource, according to the
* {@link RecordSeparatorPolicy} in force. By default a line is either
* terminated by a newline (as per {@link BufferedReader#readLine()}), or can
* be continued onto the next line if a field surrounded by quotes (\") contains
* a newline.<br/>
*
* Comment lines can be indicated using a line prefix (or collection of
* prefixes) and they will be ignored. The default is "#", so lines starting
* with a pound sign will be ignored.<br/>
*
* All the public methods that interact with the underlying resource (open,
* close, read etc.) are synchronized on this.<br/>
*
* Package private because this is not intended to be a public API - used
* internally by the flat file input sources. That makes abuses of the fact that
* it is stateful easier to control.<br/>
*
* @author Dave Syer
* @author Rob Harrop
*/
public class ResourceLineReader extends ItemStreamSupport implements LineReader, ItemReader {
private static final Collection DEFAULT_COMMENTS = Collections.singleton("#");
private static final String DEFAULT_ENCODING = "ISO-8859-1";
private static final int READ_AHEAD_LIMIT = 100000;
private final Resource resource;
private final String encoding;
private Collection comments = DEFAULT_COMMENTS;
// Encapsulates the state of the input source.
private State state = null;
private RecordSeparatorPolicy recordSeparatorPolicy = new DefaultRecordSeparatorPolicy();
public ResourceLineReader(Resource resource) throws IOException {
this(resource, DEFAULT_ENCODING);
}
public ResourceLineReader(Resource resource, String encoding) {
Assert.notNull(resource, "'resource' cannot be null.");
Assert.notNull(encoding, "'encoding' cannot be null.");
this.resource = resource;
this.encoding = encoding;
}
/**
* Setter for the {@link RecordSeparatorPolicy}. Default value is a
* {@link DefaultRecordSeparatorPolicy}. Ideally should not be changed once
* a reader is in use, but it would not be fatal if it was.
*
* @param recordSeparatorPolicy the new {@link RecordSeparatorPolicy}
*/
public void setRecordSeparatorPolicy(RecordSeparatorPolicy recordSeparatorPolicy) {
/*
* The rest of the code accesses the policy in synchronized blocks,
* copying the reference before using it. So in principle it can be
* changed in flight - the results might not be what the user expected!
*/
this.recordSeparatorPolicy = recordSeparatorPolicy;
}
/**
* Setter for comment prefixes. Can be used to ignore header lines as well
* by using e.g. the first couple of column names as a prefix.
*
* @param comments an array of comment line prefixes.
*/
public void setComments(String[] comments) {
this.comments = new HashSet(Arrays.asList(comments));
}
/**
* Read the next line from the input resource, ignoring comments, and
* according to the {@link RecordSeparatorPolicy}.
*
* @return a String.
*
* @see org.springframework.batch.item.reader.support.ItemReader#read()
*/
public synchronized Object read() {
// Make a copy of the recordSeparatorPolicy reference, in case it is
// changed during a read operation (unlikely, but you never know)...
RecordSeparatorPolicy recordSeparatorPolicy = this.recordSeparatorPolicy;
String line = readLine();
String record = line;
if (line != null) {
while (line != null && !recordSeparatorPolicy.isEndOfRecord(record)) {
record = recordSeparatorPolicy.preProcess(record) + (line = readLine());
}
}
return recordSeparatorPolicy.postProcess(record);
}
/**
* @return the next non-comment line
*/
private String readLine() {
return getState().readLine();
}
/**
* @return
*/
private State getState() {
if (state == null) {
open();
}
return state;
}
/**
* A no-op because the oobject is initialized with all it needs to open in
* the constructor.
*
* @see org.springframework.batch.item.ResourceLifecycle#open()
*/
public synchronized void open() {
state = new State();
state.open();
}
/**
* Close the reader associated with this input source.
*
* @see org.springframework.batch.item.stream.ItemStreamSupport#close(org.springframework.batch.item.ExecutionContext)
*/
public synchronized void close(ExecutionContext executionContext) {
if (state == null) {
return;
}
try {
state.close();
}
finally {
state = null;
}
}
/**
* Getter for current line count (not the current number of lines returned).
*
* @return the current line count.
*/
public int getPosition() {
return getState().getCurrentLineCount();
}
/**
* Mark the state for return later with reset. Uses the read-ahead limit
* from an underlying {@link BufferedReader}, which means that there is a
* limit to how much data can be recovered if the mark needs to be reset.<br/>
*
* Mark is supported as long as this {@link ItemStream} is used in a
* single-threaded environment. The state backing the mark is a single
* counter, keeping track of the current position, so multiple threads
* cannot be accommodated.
*
* @see #reset()
*
* @throws MarkFailedException if the mark could not be set.
*/
public synchronized void mark() throws MarkFailedException {
getState().mark();
}
/**
* Reset the reader to the last mark.
*
* @see #mark()
*
* @throws ResetFailedException if the reset is unsuccessful, e.g. if
* the read-ahead limit was breached.
*/
public synchronized void reset() throws ResetFailedException {
getState().reset();
}
private boolean isComment(String line) {
for (Iterator iter = comments.iterator(); iter.hasNext();) {
String prefix = (String) iter.next();
if (line.startsWith(prefix)) {
return true;
}
}
return false;
}
private class State {
private BufferedReader reader;
private int currentLineCount = 0;
private int markedLineCount = -1;
public String readLine() {
String line = null;
try {
line = this.reader.readLine();
if (line == null) {
return null;
}
currentLineCount++;
while (isComment(line)) {
line = reader.readLine();
if (line == null) {
return null;
}
currentLineCount++;
}
}
catch (IOException e) {
throw new StreamException("Unable to read from resource '" + resource + "' at line "
+ currentLineCount, e);
}
return line;
}
/**
*
*/
public void open() {
try {
reader = new BufferedReader(new InputStreamReader(resource.getInputStream(), encoding));
mark();
}
catch (IOException e) {
throw new StreamException("Could not open resource", e);
}
}
/**
* Close the reader and reset the counters.
*/
public void close() {
if (reader == null) {
return;
}
try {
reader.close();
}
catch (IOException e) {
throw new StreamException("Could not close reader", e);
}
finally {
currentLineCount = 0;
markedLineCount = -1;
}
}
/**
* @return the current line count
*/
public int getCurrentLineCount() {
return currentLineCount;
}
/**
* Mark the underlying reader and set the line counters.
*/
public void mark() throws MarkFailedException {
try {
reader.mark(READ_AHEAD_LIMIT);
markedLineCount = currentLineCount;
}
catch (IOException e) {
throw new MarkFailedException("Could not mark reader", e);
}
}
/**
* Reset the reader and line counters to the last marked position if
* possible.
*/
public void reset() throws ResetFailedException {
if (markedLineCount < 0) {
return;
}
try {
this.reader.reset();
currentLineCount = markedLineCount;
}
catch (IOException e) {
throw new ResetFailedException("Could not reset reader", e);
}
}
}
}
/*
* 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.io.file.separator;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import org.springframework.batch.item.ExecutionContext;
import org.springframework.batch.item.ItemReader;
import org.springframework.batch.item.ItemStream;
import org.springframework.batch.item.exception.MarkFailedException;
import org.springframework.batch.item.exception.ResetFailedException;
import org.springframework.batch.item.exception.StreamException;
import org.springframework.batch.item.stream.ItemStreamSupport;
import org.springframework.core.io.Resource;
import org.springframework.util.Assert;
/**
* An input source that reads lines one by one from a resource. <br/>
*
* A line can consist of multiple lines in the input resource, according to the
* {@link RecordSeparatorPolicy} in force. By default a line is either
* terminated by a newline (as per {@link BufferedReader#readLine()}), or can
* be continued onto the next line if a field surrounded by quotes (\") contains
* a newline.<br/>
*
* Comment lines can be indicated using a line prefix (or collection of
* prefixes) and they will be ignored. The default is "#", so lines starting
* with a pound sign will be ignored.<br/>
*
* All the public methods that interact with the underlying resource (open,
* close, read etc.) are synchronized on this.<br/>
*
* Package private because this is not intended to be a public API - used
* internally by the flat file input sources. That makes abuses of the fact that
* it is stateful easier to control.<br/>
*
* @author Dave Syer
* @author Rob Harrop
*/
public class ResourceLineReader extends ItemStreamSupport implements LineReader, ItemReader {
private static final Collection DEFAULT_COMMENTS = Collections.singleton("#");
private static final String DEFAULT_ENCODING = "ISO-8859-1";
private static final int READ_AHEAD_LIMIT = 100000;
private final Resource resource;
private final String encoding;
private Collection comments = DEFAULT_COMMENTS;
// Encapsulates the state of the input source.
private State state = null;
private RecordSeparatorPolicy recordSeparatorPolicy = new DefaultRecordSeparatorPolicy();
public ResourceLineReader(Resource resource) throws IOException {
this(resource, DEFAULT_ENCODING);
}
public ResourceLineReader(Resource resource, String encoding) {
Assert.notNull(resource, "'resource' cannot be null.");
Assert.notNull(encoding, "'encoding' cannot be null.");
this.resource = resource;
this.encoding = encoding;
}
/**
* Setter for the {@link RecordSeparatorPolicy}. Default value is a
* {@link DefaultRecordSeparatorPolicy}. Ideally should not be changed once
* a reader is in use, but it would not be fatal if it was.
*
* @param recordSeparatorPolicy the new {@link RecordSeparatorPolicy}
*/
public void setRecordSeparatorPolicy(RecordSeparatorPolicy recordSeparatorPolicy) {
/*
* The rest of the code accesses the policy in synchronized blocks,
* copying the reference before using it. So in principle it can be
* changed in flight - the results might not be what the user expected!
*/
this.recordSeparatorPolicy = recordSeparatorPolicy;
}
/**
* Setter for comment prefixes. Can be used to ignore header lines as well
* by using e.g. the first couple of column names as a prefix.
*
* @param comments an array of comment line prefixes.
*/
public void setComments(String[] comments) {
this.comments = new HashSet(Arrays.asList(comments));
}
/**
* Read the next line from the input resource, ignoring comments, and
* according to the {@link RecordSeparatorPolicy}.
*
* @return a String.
*
* @see org.springframework.batch.item.reader.support.ItemReader#read()
*/
public synchronized Object read() {
// Make a copy of the recordSeparatorPolicy reference, in case it is
// changed during a read operation (unlikely, but you never know)...
RecordSeparatorPolicy recordSeparatorPolicy = this.recordSeparatorPolicy;
String line = readLine();
String record = line;
if (line != null) {
while (line != null && !recordSeparatorPolicy.isEndOfRecord(record)) {
record = recordSeparatorPolicy.preProcess(record) + (line = readLine());
}
}
return recordSeparatorPolicy.postProcess(record);
}
/**
* @return the next non-comment line
*/
private String readLine() {
return getState().readLine();
}
/**
* @return
*/
private State getState() {
if (state == null) {
open();
}
return state;
}
/**
* A no-op because the oobject is initialized with all it needs to open in
* the constructor.
*
* @see org.springframework.batch.item.ResourceLifecycle#open()
*/
public synchronized void open() {
state = new State();
state.open();
}
/**
* Close the reader associated with this input source.
*
* @see org.springframework.batch.item.stream.ItemStreamSupport#close(org.springframework.batch.item.ExecutionContext)
*/
public synchronized void close(ExecutionContext executionContext) {
if (state == null) {
return;
}
try {
state.close();
}
finally {
state = null;
}
}
/**
* Getter for current line count (not the current number of lines returned).
*
* @return the current line count.
*/
public int getPosition() {
return getState().getCurrentLineCount();
}
/**
* Mark the state for return later with reset. Uses the read-ahead limit
* from an underlying {@link BufferedReader}, which means that there is a
* limit to how much data can be recovered if the mark needs to be reset.<br/>
*
* Mark is supported as long as this {@link ItemStream} is used in a
* single-threaded environment. The state backing the mark is a single
* counter, keeping track of the current position, so multiple threads
* cannot be accommodated.
*
* @see #reset()
*
* @throws MarkFailedException if the mark could not be set.
*/
public synchronized void mark() throws MarkFailedException {
getState().mark();
}
/**
* Reset the reader to the last mark.
*
* @see #mark()
*
* @throws ResetFailedException if the reset is unsuccessful, e.g. if
* the read-ahead limit was breached.
*/
public synchronized void reset() throws ResetFailedException {
getState().reset();
}
private boolean isComment(String line) {
for (Iterator iter = comments.iterator(); iter.hasNext();) {
String prefix = (String) iter.next();
if (line.startsWith(prefix)) {
return true;
}
}
return false;
}
private class State {
private BufferedReader reader;
private int currentLineCount = 0;
private int markedLineCount = -1;
public String readLine() {
String line = null;
try {
line = this.reader.readLine();
if (line == null) {
return null;
}
currentLineCount++;
while (isComment(line)) {
line = reader.readLine();
if (line == null) {
return null;
}
currentLineCount++;
}
}
catch (IOException e) {
throw new StreamException("Unable to read from resource '" + resource + "' at line "
+ currentLineCount, e);
}
return line;
}
/**
*
*/
public void open() {
try {
reader = new BufferedReader(new InputStreamReader(resource.getInputStream(), encoding));
mark();
}
catch (IOException e) {
throw new StreamException("Could not open resource", e);
}
}
/**
* Close the reader and reset the counters.
*/
public void close() {
if (reader == null) {
return;
}
try {
reader.close();
}
catch (IOException e) {
throw new StreamException("Could not close reader", e);
}
finally {
currentLineCount = 0;
markedLineCount = -1;
}
}
/**
* @return the current line count
*/
public int getCurrentLineCount() {
return currentLineCount;
}
/**
* Mark the underlying reader and set the line counters.
*/
public void mark() throws MarkFailedException {
try {
reader.mark(READ_AHEAD_LIMIT);
markedLineCount = currentLineCount;
}
catch (IOException e) {
throw new MarkFailedException("Could not mark reader", e);
}
}
/**
* Reset the reader and line counters to the last marked position if
* possible.
*/
public void reset() throws ResetFailedException {
if (markedLineCount < 0) {
return;
}
try {
this.reader.reset();
currentLineCount = markedLineCount;
}
catch (IOException e) {
throw new ResetFailedException("Could not reset reader", e);
}
}
}
}

View File

@@ -1,84 +1,84 @@
/*
* 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.io.file.transform;
import java.util.ArrayList;
import java.util.List;
import org.springframework.batch.io.file.mapping.DefaultFieldSet;
import org.springframework.batch.io.file.mapping.FieldSet;
/**
* @author Dave Syer
* @author Robert Kasanicky
*
*/
public abstract class AbstractLineTokenizer implements LineTokenizer {
protected String[] names = new String[0];
/**
* Setter for column names. Optional, but if set, then all lines must have
* as many or fewer tokens.
*
* @param names
*/
public void setNames(String[] names) {
this.names = names;
}
/**
* @return <code>true</code> if column names have been specified
* @see #setNames(String[])
*/
public boolean hasNames() {
if (names != null && names.length > 0) {
return true;
}
return false;
}
/**
* Yields the tokens resulting from the splitting of the supplied
* <code>line</code>.
*
* @param line the line to be tokenised (can be <code>null</code>)
*
* @return the resulting tokens
*/
public FieldSet tokenize(String line) {
if (line == null || line.length()==0) {
return new DefaultFieldSet(new String[0]);
}
List tokens = new ArrayList(doTokenize(line));
for (int i=tokens.size(); i<names.length; i++) {
tokens.add(null);
}
String[] values = (String[]) tokens.toArray(new String[tokens.size()]);
if (names.length==0) {
return new DefaultFieldSet(values);
}
return new DefaultFieldSet(values, names);
}
protected abstract List doTokenize(String line);
}
/*
* 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.io.file.transform;
import java.util.ArrayList;
import java.util.List;
import org.springframework.batch.io.file.mapping.DefaultFieldSet;
import org.springframework.batch.io.file.mapping.FieldSet;
/**
* @author Dave Syer
* @author Robert Kasanicky
*
*/
public abstract class AbstractLineTokenizer implements LineTokenizer {
protected String[] names = new String[0];
/**
* Setter for column names. Optional, but if set, then all lines must have
* as many or fewer tokens.
*
* @param names
*/
public void setNames(String[] names) {
this.names = names;
}
/**
* @return <code>true</code> if column names have been specified
* @see #setNames(String[])
*/
public boolean hasNames() {
if (names != null && names.length > 0) {
return true;
}
return false;
}
/**
* Yields the tokens resulting from the splitting of the supplied
* <code>line</code>.
*
* @param line the line to be tokenised (can be <code>null</code>)
*
* @return the resulting tokens
*/
public FieldSet tokenize(String line) {
if (line == null || line.length()==0) {
return new DefaultFieldSet(new String[0]);
}
List tokens = new ArrayList(doTokenize(line));
for (int i=tokens.size(); i<names.length; i++) {
tokens.add(null);
}
String[] values = (String[]) tokens.toArray(new String[tokens.size()]);
if (names.length==0) {
return new DefaultFieldSet(values);
}
return new DefaultFieldSet(values, names);
}
protected abstract List doTokenize(String line);
}

View File

@@ -1,213 +1,213 @@
/*
* 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.io.file.transform;
import java.util.ArrayList;
import java.util.List;
import org.springframework.batch.io.exception.ConfigurationException;
import org.springframework.util.StringUtils;
/**
*
* @author Rob Harrop
* @author Dave Syer
*
*/
public class DelimitedLineTokenizer extends AbstractLineTokenizer {
/**
* Convenient constant for the common case of a tab delimiter.
*/
public static final char DELIMITER_TAB = '\t';
/**
* Convenient constant for the common case of a comma delimiter.
*/
public static final char DELIMITER_COMMA = ',';
/**
* Convenient constant for the common case of a " character used to escape
* delimiters or line endings.
*/
public static final char DEFAULT_QUOTE_CHARACTER = '"';
// the delimiter character used when reading input.
private char delimiter;
private char quoteCharacter = DEFAULT_QUOTE_CHARACTER;
private String quoteString;
/**
* Create a new instance of the {@link DelimitedLineTokenizer} class for the
* common case where the delimiter is a {@link #DELIMITER_COMMA comma}.
*
* @see #DelimitedLineTokenizer(char)
* @see #DELIMITER_COMMA
*/
public DelimitedLineTokenizer() {
this(DELIMITER_COMMA);
}
/**
* Create a new instance of the {@link DelimitedLineTokenizer} class.
*
* @param delimiter the desired delimiter
*/
public DelimitedLineTokenizer(char delimiter) {
if (delimiter == DEFAULT_QUOTE_CHARACTER) {
throw new ConfigurationException("'" + DEFAULT_QUOTE_CHARACTER
+ "' is not allowed as delimiter for tokenizers.");
}
this.delimiter = delimiter;
setQuoteCharacter(DEFAULT_QUOTE_CHARACTER);
}
/**
* Setter for the delimiter character.
* @param delimiter
*/
public void setDelimiter(char delimiter) {
this.delimiter = delimiter;
}
/**
* Public setter for the quoteCharacter. The quote character can be used to
* extend a field across line endings or to enclose a String which contains
* the delimiter. Inside a quoted token the quote character can be used to
* escape itself, thus "a""b""c" is tokenized to a"b"c.
*
* @param quoteCharacter the quoteCharacter to set
*
* @see #DEFAULT_QUOTE_CHARACTER
*/
public void setQuoteCharacter(char quoteCharacter) {
this.quoteCharacter = quoteCharacter;
this.quoteString = "" + quoteCharacter;
}
/**
* Yields the tokens resulting from the splitting of the supplied
* <code>line</code>.
*
* @param line the line to be tokenized
*
* @return the resulting tokens
*/
protected List doTokenize(String line) {
List tokens = new ArrayList();
// line is never null in current implementation
// line is checked in parent: AbstractLineTokenizer.tokenize()
char[] chars = line.toCharArray();
boolean inQuoted = false;
int lastCut = 0;
int length = chars.length;
for (int i = 0; i < length; i++) {
char currentChar = chars[i];
boolean isEnd = (i == (length - 1));
if ((isDelimiterCharacter(currentChar) && !inQuoted) || isEnd) {
int endPosition = (isEnd ? (length - lastCut) : (i - lastCut));
if (isEnd && isDelimiterCharacter(currentChar)) {
endPosition--;
}
String value = null;
value = maybeStripQuotes(new String(chars, lastCut, endPosition));
tokens.add(value);
if (isEnd && (isDelimiterCharacter(currentChar))) {
tokens.add("");
}
lastCut = i + 1;
}
else if (isQuoteCharacter(currentChar)) {
inQuoted = !inQuoted;
}
}
return tokens;
}
/**
* If the string is quoted strip (possibly with whitespace outside the
* quotes (which will be stripped), replace escaped quotes inside the
* string. Quotes are escaped with double instances of the quote character.
* @param string
* @return the same string but stripped and unescaped if necessary
*/
private String maybeStripQuotes(String string) {
String value = string.trim();
if (isQuoted(value)) {
value = StringUtils.replace(value, "" + quoteCharacter + quoteCharacter, "" + quoteCharacter);
int endLength = value.length() - 1;
// used to deal with empty quoted values
if(endLength == 0) {
endLength = 1;
}
string = value.substring(1, endLength);
}
return string;
}
/**
* Is this string surrounded by quite characters?
* @param value
* @return true if the value starts and ends with the
* {@link #quoteCharacter}
*/
private boolean isQuoted(String value) {
if (value.startsWith(quoteString) && value.endsWith(quoteString)) {
return true;
}
return false;
}
/**
* Is the supplied character the delimiter character?
*
* @param c the character to be checked
* @return <code>true</code> if the supplied character is the delimiter
* character
* @see DelimitedLineTokenizer#DelimitedLineTokenizer(char)
*/
private boolean isDelimiterCharacter(char c) {
return c == this.delimiter;
}
/**
* Is the supplied character a quote character?
*
* @param c the character to be checked
* @return <code>true</code> if the supplied character is an quote
* character
* @see #setQuoteCharacter(char)
*/
protected boolean isQuoteCharacter(char c) {
return c == quoteCharacter;
}
}
/*
* 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.io.file.transform;
import java.util.ArrayList;
import java.util.List;
import org.springframework.batch.io.exception.ConfigurationException;
import org.springframework.util.StringUtils;
/**
*
* @author Rob Harrop
* @author Dave Syer
*
*/
public class DelimitedLineTokenizer extends AbstractLineTokenizer {
/**
* Convenient constant for the common case of a tab delimiter.
*/
public static final char DELIMITER_TAB = '\t';
/**
* Convenient constant for the common case of a comma delimiter.
*/
public static final char DELIMITER_COMMA = ',';
/**
* Convenient constant for the common case of a " character used to escape
* delimiters or line endings.
*/
public static final char DEFAULT_QUOTE_CHARACTER = '"';
// the delimiter character used when reading input.
private char delimiter;
private char quoteCharacter = DEFAULT_QUOTE_CHARACTER;
private String quoteString;
/**
* Create a new instance of the {@link DelimitedLineTokenizer} class for the
* common case where the delimiter is a {@link #DELIMITER_COMMA comma}.
*
* @see #DelimitedLineTokenizer(char)
* @see #DELIMITER_COMMA
*/
public DelimitedLineTokenizer() {
this(DELIMITER_COMMA);
}
/**
* Create a new instance of the {@link DelimitedLineTokenizer} class.
*
* @param delimiter the desired delimiter
*/
public DelimitedLineTokenizer(char delimiter) {
if (delimiter == DEFAULT_QUOTE_CHARACTER) {
throw new ConfigurationException("'" + DEFAULT_QUOTE_CHARACTER
+ "' is not allowed as delimiter for tokenizers.");
}
this.delimiter = delimiter;
setQuoteCharacter(DEFAULT_QUOTE_CHARACTER);
}
/**
* Setter for the delimiter character.
* @param delimiter
*/
public void setDelimiter(char delimiter) {
this.delimiter = delimiter;
}
/**
* Public setter for the quoteCharacter. The quote character can be used to
* extend a field across line endings or to enclose a String which contains
* the delimiter. Inside a quoted token the quote character can be used to
* escape itself, thus "a""b""c" is tokenized to a"b"c.
*
* @param quoteCharacter the quoteCharacter to set
*
* @see #DEFAULT_QUOTE_CHARACTER
*/
public void setQuoteCharacter(char quoteCharacter) {
this.quoteCharacter = quoteCharacter;
this.quoteString = "" + quoteCharacter;
}
/**
* Yields the tokens resulting from the splitting of the supplied
* <code>line</code>.
*
* @param line the line to be tokenized
*
* @return the resulting tokens
*/
protected List doTokenize(String line) {
List tokens = new ArrayList();
// line is never null in current implementation
// line is checked in parent: AbstractLineTokenizer.tokenize()
char[] chars = line.toCharArray();
boolean inQuoted = false;
int lastCut = 0;
int length = chars.length;
for (int i = 0; i < length; i++) {
char currentChar = chars[i];
boolean isEnd = (i == (length - 1));
if ((isDelimiterCharacter(currentChar) && !inQuoted) || isEnd) {
int endPosition = (isEnd ? (length - lastCut) : (i - lastCut));
if (isEnd && isDelimiterCharacter(currentChar)) {
endPosition--;
}
String value = null;
value = maybeStripQuotes(new String(chars, lastCut, endPosition));
tokens.add(value);
if (isEnd && (isDelimiterCharacter(currentChar))) {
tokens.add("");
}
lastCut = i + 1;
}
else if (isQuoteCharacter(currentChar)) {
inQuoted = !inQuoted;
}
}
return tokens;
}
/**
* If the string is quoted strip (possibly with whitespace outside the
* quotes (which will be stripped), replace escaped quotes inside the
* string. Quotes are escaped with double instances of the quote character.
* @param string
* @return the same string but stripped and unescaped if necessary
*/
private String maybeStripQuotes(String string) {
String value = string.trim();
if (isQuoted(value)) {
value = StringUtils.replace(value, "" + quoteCharacter + quoteCharacter, "" + quoteCharacter);
int endLength = value.length() - 1;
// used to deal with empty quoted values
if(endLength == 0) {
endLength = 1;
}
string = value.substring(1, endLength);
}
return string;
}
/**
* Is this string surrounded by quite characters?
* @param value
* @return true if the value starts and ends with the
* {@link #quoteCharacter}
*/
private boolean isQuoted(String value) {
if (value.startsWith(quoteString) && value.endsWith(quoteString)) {
return true;
}
return false;
}
/**
* Is the supplied character the delimiter character?
*
* @param c the character to be checked
* @return <code>true</code> if the supplied character is the delimiter
* character
* @see DelimitedLineTokenizer#DelimitedLineTokenizer(char)
*/
private boolean isDelimiterCharacter(char c) {
return c == this.delimiter;
}
/**
* Is the supplied character a quote character?
*
* @param c the character to be checked
* @return <code>true</code> if the supplied character is an quote
* character
* @see #setQuoteCharacter(char)
*/
protected boolean isQuoteCharacter(char c) {
return c == quoteCharacter;
}
}

View File

@@ -1,180 +1,180 @@
/*
* 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.io.file.transform;
import java.util.Arrays;
import org.springframework.batch.io.file.mapping.FieldSet;
import org.springframework.util.Assert;
/**
* LineAggregator implementation which produces line by aggregating provided
* strings into columns with fixed length. Columns are specified by array of
* ranges ({@link #setColumns(Range[])}.</br>
*
* @author tomas.slanina
* @author peter.zozom
* @author Dave Syer
*/
public class FixedLengthLineAggregator implements LineAggregator {
private static final int ALIGN_CENTER = 1;
private static final int ALIGN_RIGHT = 2;
private static final int ALIGN_LEFT = 3;
private Range[] ranges;
private int lastColumn;
private int align = ALIGN_LEFT;
private char padding = ' ';
/**
* Set column ranges. Used in conjunction with the
* {@link RangeArrayPropertyEditor} this property can be set in the form of
* a String describing the range boundaries, e.g. "1,4,7" or "1-3,4-6,7" or
* "1-2,4-5,7-10".
*
* @param columns
* array of Range objects which specify column start and end
* position
*/
public void setColumns(Range[] columns) {
Assert.notNull(columns);
lastColumn = findLastColumn(columns);
this.ranges = columns;
}
/**
* Aggregate provided strings into single line using specified column
* ranges.
*
* @param fieldSet
* arrays of strings representing data to be aggregated
* @return aggregated strings
*/
public String aggregate(FieldSet fieldSet) {
Assert.notNull(fieldSet);
Assert.notNull(ranges);
String[] args = fieldSet.getValues();
Assert.isTrue(args.length <= ranges.length,
"Number of arguments must match number of fields in a record");
// calculate line length
int lineLength = ranges[lastColumn].hasMaxValue() ? ranges[lastColumn]
.getMax() : ranges[lastColumn].getMin()
+ args[lastColumn].length() - 1;
// create stringBuffer with length of line filled with padding
// characters
char[] emptyLine = new char[lineLength];
Arrays.fill(emptyLine, padding);
StringBuffer stringBuffer = new StringBuffer(lineLength);
stringBuffer.append(emptyLine);
// aggregate all strings
for (int i = 0; i < args.length; i++) {
// offset where text will be inserted
int start = ranges[i].getMin() - 1;
// calculate column length
int columnLength;
if ((i == lastColumn) && (!ranges[lastColumn].hasMaxValue())) {
columnLength = args[lastColumn].length();
} else {
columnLength = ranges[i].getMax() - ranges[i].getMin() + 1;
}
String textToInsert = (args[i] == null) ? "" : args[i];
Assert
.isTrue(columnLength >= textToInsert.length(),
"Supplied text: " + textToInsert
+ " is longer than defined length: "
+ columnLength);
switch (align) {
case ALIGN_RIGHT:
start += (columnLength - textToInsert.length());
break;
case ALIGN_CENTER:
start += ((columnLength - textToInsert.length()) / 2);
break;
case ALIGN_LEFT:
// nothing to do
break;
}
stringBuffer.replace(start, start + textToInsert.length(),
textToInsert);
}
return stringBuffer.toString();
}
/**
* Recognized alignments are <code>CENTER, RIGHT, LEFT</code>. An
* IllegalArgumentException is thrown in case the argument does not match
* any of the recognized values.
*
* @param alignment
* the alignment to be used
*/
public void setAlignment(String alignment) {
if ("CENTER".equalsIgnoreCase(alignment)) {
this.align = ALIGN_CENTER;
} else if ("RIGHT".equalsIgnoreCase(alignment)) {
this.align = ALIGN_RIGHT;
} else if ("LEFT".equalsIgnoreCase(alignment)) {
this.align = ALIGN_LEFT;
} else {
throw new IllegalArgumentException(
"Only 'CENTER', 'RIGHT' or 'LEFT' are allowed alignment values");
}
}
/**
* Setter for padding (default is space).
*
* @param padding
* the padding character
*/
public void setPadding(char padding) {
this.padding = padding;
}
/*
* Find last column. Columns are not sorted. Returns index of last column
* (column with highest offset).
*/
private int findLastColumn(Range[] columns) {
int lastOffset = 1;
int lastIndex = 0;
for (int i = 0; i < columns.length; i++) {
if (columns[i].getMin() > lastOffset) {
lastOffset = columns[i].getMin();
lastIndex = i;
}
}
return lastIndex;
}
}
/*
* 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.io.file.transform;
import java.util.Arrays;
import org.springframework.batch.io.file.mapping.FieldSet;
import org.springframework.util.Assert;
/**
* LineAggregator implementation which produces line by aggregating provided
* strings into columns with fixed length. Columns are specified by array of
* ranges ({@link #setColumns(Range[])}.</br>
*
* @author tomas.slanina
* @author peter.zozom
* @author Dave Syer
*/
public class FixedLengthLineAggregator implements LineAggregator {
private static final int ALIGN_CENTER = 1;
private static final int ALIGN_RIGHT = 2;
private static final int ALIGN_LEFT = 3;
private Range[] ranges;
private int lastColumn;
private int align = ALIGN_LEFT;
private char padding = ' ';
/**
* Set column ranges. Used in conjunction with the
* {@link RangeArrayPropertyEditor} this property can be set in the form of
* a String describing the range boundaries, e.g. "1,4,7" or "1-3,4-6,7" or
* "1-2,4-5,7-10".
*
* @param columns
* array of Range objects which specify column start and end
* position
*/
public void setColumns(Range[] columns) {
Assert.notNull(columns);
lastColumn = findLastColumn(columns);
this.ranges = columns;
}
/**
* Aggregate provided strings into single line using specified column
* ranges.
*
* @param fieldSet
* arrays of strings representing data to be aggregated
* @return aggregated strings
*/
public String aggregate(FieldSet fieldSet) {
Assert.notNull(fieldSet);
Assert.notNull(ranges);
String[] args = fieldSet.getValues();
Assert.isTrue(args.length <= ranges.length,
"Number of arguments must match number of fields in a record");
// calculate line length
int lineLength = ranges[lastColumn].hasMaxValue() ? ranges[lastColumn]
.getMax() : ranges[lastColumn].getMin()
+ args[lastColumn].length() - 1;
// create stringBuffer with length of line filled with padding
// characters
char[] emptyLine = new char[lineLength];
Arrays.fill(emptyLine, padding);
StringBuffer stringBuffer = new StringBuffer(lineLength);
stringBuffer.append(emptyLine);
// aggregate all strings
for (int i = 0; i < args.length; i++) {
// offset where text will be inserted
int start = ranges[i].getMin() - 1;
// calculate column length
int columnLength;
if ((i == lastColumn) && (!ranges[lastColumn].hasMaxValue())) {
columnLength = args[lastColumn].length();
} else {
columnLength = ranges[i].getMax() - ranges[i].getMin() + 1;
}
String textToInsert = (args[i] == null) ? "" : args[i];
Assert
.isTrue(columnLength >= textToInsert.length(),
"Supplied text: " + textToInsert
+ " is longer than defined length: "
+ columnLength);
switch (align) {
case ALIGN_RIGHT:
start += (columnLength - textToInsert.length());
break;
case ALIGN_CENTER:
start += ((columnLength - textToInsert.length()) / 2);
break;
case ALIGN_LEFT:
// nothing to do
break;
}
stringBuffer.replace(start, start + textToInsert.length(),
textToInsert);
}
return stringBuffer.toString();
}
/**
* Recognized alignments are <code>CENTER, RIGHT, LEFT</code>. An
* IllegalArgumentException is thrown in case the argument does not match
* any of the recognized values.
*
* @param alignment
* the alignment to be used
*/
public void setAlignment(String alignment) {
if ("CENTER".equalsIgnoreCase(alignment)) {
this.align = ALIGN_CENTER;
} else if ("RIGHT".equalsIgnoreCase(alignment)) {
this.align = ALIGN_RIGHT;
} else if ("LEFT".equalsIgnoreCase(alignment)) {
this.align = ALIGN_LEFT;
} else {
throw new IllegalArgumentException(
"Only 'CENTER', 'RIGHT' or 'LEFT' are allowed alignment values");
}
}
/**
* Setter for padding (default is space).
*
* @param padding
* the padding character
*/
public void setPadding(char padding) {
this.padding = padding;
}
/*
* Find last column. Columns are not sorted. Returns index of last column
* (column with highest offset).
*/
private int findLastColumn(Range[] columns) {
int lastOffset = 1;
int lastIndex = 0;
for (int i = 0; i < columns.length; i++) {
if (columns[i].getMin() > lastOffset) {
lastOffset = columns[i].getMin();
lastIndex = i;
}
}
return lastIndex;
}
}

View File

@@ -1,80 +1,80 @@
/*
* 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.io.file.transform;
import java.util.ArrayList;
import java.util.List;
/**
* Tokenizer used to process data obtained from files with fixed-length format.
* Columns are specified by array of Range objects ({@link #setColumns(Range[])}).
*
* @author tomas.slanina
* @author peter.zozom
* @author Dave Syer
*/
public class FixedLengthTokenizer extends AbstractLineTokenizer {
private Range[] ranges;
/**
* Set the column ranges. Used in conjunction with the
* {@link RangeArrayPropertyEditor} this property can be set in the form of
* a String describing the range boundaries, e.g. "1,4,7" or "1-3,4-6,7" or
* "1-2,4-5,7-10".
*
* @param ranges the column ranges expected in the input
*/
public void setColumns(Range[] ranges) {
this.ranges = ranges;
}
/**
* Yields the tokens resulting from the splitting of the supplied
* <code>line</code>.
*
* @param line
* the line to be tokenised (can be <code>null</code>)
*
* @return the resulting tokens (empty if the line is null)
*/
protected List doTokenize(String line) {
List tokens = new ArrayList(ranges.length);
int lineLength;
String token;
lineLength = line.length();
for (int i = 0; i < ranges.length; i++) {
int startPos = ranges[i].getMin() - 1;
int endPos = ranges[i].getMax();
if (lineLength >= endPos) {
token = line.substring(startPos, endPos);
} else if (lineLength >= startPos) {
token = line.substring(startPos);
} else {
token = "";
}
tokens.add(token);
}
return tokens;
}
}
/*
* 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.io.file.transform;
import java.util.ArrayList;
import java.util.List;
/**
* Tokenizer used to process data obtained from files with fixed-length format.
* Columns are specified by array of Range objects ({@link #setColumns(Range[])}).
*
* @author tomas.slanina
* @author peter.zozom
* @author Dave Syer
*/
public class FixedLengthTokenizer extends AbstractLineTokenizer {
private Range[] ranges;
/**
* Set the column ranges. Used in conjunction with the
* {@link RangeArrayPropertyEditor} this property can be set in the form of
* a String describing the range boundaries, e.g. "1,4,7" or "1-3,4-6,7" or
* "1-2,4-5,7-10".
*
* @param ranges the column ranges expected in the input
*/
public void setColumns(Range[] ranges) {
this.ranges = ranges;
}
/**
* Yields the tokens resulting from the splitting of the supplied
* <code>line</code>.
*
* @param line
* the line to be tokenised (can be <code>null</code>)
*
* @return the resulting tokens (empty if the line is null)
*/
protected List doTokenize(String line) {
List tokens = new ArrayList(ranges.length);
int lineLength;
String token;
lineLength = line.length();
for (int i = 0; i < ranges.length; i++) {
int startPos = ranges[i].getMin() - 1;
int endPos = ranges[i].getMax();
if (lineLength >= endPos) {
token = line.substring(startPos, endPos);
} else if (lineLength >= startPos) {
token = line.substring(startPos);
} else {
token = "";
}
tokens.add(token);
}
return tokens;
}
}

View File

@@ -1,62 +1,62 @@
package org.springframework.batch.io.file.transform;
import org.springframework.util.Assert;
/**
* A class to represent ranges. A Range can have minimum/maximum values from
* interval &lt;1,Integer.MAX_VALUE-1&gt; A Range can be unbounded at maximum
* side. This can be specified by passing {@link Range#UPPER_BORDER_NOT_DEFINED}} as max
* value or using constructor {@link #Range(int)}.
*
* @author peter.zozom
*/
public class Range {
private final static int UPPER_BORDER_NOT_DEFINED = Integer.MAX_VALUE;
private int min;
private int max;
public Range(int min) {
checkMinMaxValues(min, UPPER_BORDER_NOT_DEFINED);
this.min = min;
this.max = UPPER_BORDER_NOT_DEFINED;
}
public Range(int min, int max) {
checkMinMaxValues(min, max);
this.min = min;
this.max = max;
}
public int getMax() {
return max;
}
public int getMin() {
return min;
}
public void setMax(int max) {
checkMinMaxValues(this.min, max);
this.max = max;
}
public void setMin(int min) {
checkMinMaxValues(min, this.max);
this.min = min;
}
public boolean hasMaxValue() {
return max != UPPER_BORDER_NOT_DEFINED;
}
public String toString() {
return hasMaxValue() ? min + "-" + max : String.valueOf(min);
}
private void checkMinMaxValues(int min, int max) {
Assert.isTrue(min>0, "Min value must be higher than zero");
Assert.isTrue(min<=max, "Min value should be lower or equal to max value");
}
}
package org.springframework.batch.io.file.transform;
import org.springframework.util.Assert;
/**
* A class to represent ranges. A Range can have minimum/maximum values from
* interval &lt;1,Integer.MAX_VALUE-1&gt; A Range can be unbounded at maximum
* side. This can be specified by passing {@link Range#UPPER_BORDER_NOT_DEFINED}} as max
* value or using constructor {@link #Range(int)}.
*
* @author peter.zozom
*/
public class Range {
private final static int UPPER_BORDER_NOT_DEFINED = Integer.MAX_VALUE;
private int min;
private int max;
public Range(int min) {
checkMinMaxValues(min, UPPER_BORDER_NOT_DEFINED);
this.min = min;
this.max = UPPER_BORDER_NOT_DEFINED;
}
public Range(int min, int max) {
checkMinMaxValues(min, max);
this.min = min;
this.max = max;
}
public int getMax() {
return max;
}
public int getMin() {
return min;
}
public void setMax(int max) {
checkMinMaxValues(this.min, max);
this.max = max;
}
public void setMin(int min) {
checkMinMaxValues(min, this.max);
this.min = min;
}
public boolean hasMaxValue() {
return max != UPPER_BORDER_NOT_DEFINED;
}
public String toString() {
return hasMaxValue() ? min + "-" + max : String.valueOf(min);
}
private void checkMinMaxValues(int min, int max) {
Assert.isTrue(min>0, "Min value must be higher than zero");
Assert.isTrue(min<=max, "Min value should be lower or equal to max value");
}
}

View File

@@ -1,131 +1,131 @@
package org.springframework.batch.io.file.transform;
import java.beans.PropertyEditorSupport;
import java.util.Arrays;
import java.util.Comparator;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/**
* Property editor implementation which parses string and creates array of
* ranges. Ranges can be provided in any order. </br> Input string should be
* provided in following format: 'range1, range2, range3,...' where range is
* specified as:
* <ul>
* <li>'X-Y', where X is minimum value and Y is maximum value (condition X<=Y
* is verified)</li>
* <li>or 'Z', where Z is minimum and maximum is calculated as (minimum of
* adjacent range - 1). Maximum of the last range is never calculated. Range
* stays unbound at maximum side if maximum value is not provided.</li>
* </ul>
* Minimum and maximum values can be from interval &lt;1, Integer.MAX_VALUE-1&gt;
* <p>
* Examples:</br>
* '1, 15, 25, 38, 55-60' is equal to '1-14, 15-24, 25-37, 38-54, 55-60' </br>
* '36, 14, 1-10, 15, 49-57' is equal to '36-48, 14-14, 1-10, 15-35, 49-57'
* <p>
* Property editor also allows to validate whether ranges are disjoint. Validation
* can be turned on/off by using {@link #forceDisjointRanges}. By default
* validation is turned off.
*
* @author peter.zozom
*/
public class RangeArrayPropertyEditor extends PropertyEditorSupport {
private boolean forceDisjointRanges = false;
/**
* Set force disjoint ranges. If set to TRUE, ranges are validated to be disjoint.
* For example: defining ranges '1-10, 5-15' will cause IllegalArgumentException in
* case of forceDisjointRanges=TRUE.
* @param forceDisjointRanges
*/
public void setForceDisjointRanges(boolean forceDisjointRanges) {
this.forceDisjointRanges = forceDisjointRanges;
}
public void setAsText(String text) throws IllegalArgumentException {
//split text into ranges
String[] strRanges = text.split(",");
Range[] ranges = new Range[strRanges.length];
//parse ranges and create array of Range objects
for (int i = 0; i < strRanges.length; i++) {
String[] range = strRanges[i].split("-");
int min;
int max;
if ((range.length == 1) && (StringUtils.hasText(range[0]))) {
min = Integer.parseInt(range[0].trim());
// correct max value will be assigned later
ranges[i] = new Range(min);
} else if ((range.length == 2) && (StringUtils.hasText(range[0]))
&& (StringUtils.hasText(range[1]))) {
min = Integer.parseInt(range[0].trim());
max = Integer.parseInt(range[1].trim());
ranges[i] = new Range(min,max);
} else {
throw new IllegalArgumentException("Range[" + i + "]: range (" + strRanges[i] + ") is invalid");
}
}
setMaxValues(ranges);
setValue(ranges);
}
public String getAsText() {
Range[] ranges = (Range[])getValue();
StringBuffer sb = new StringBuffer();
for (int i = 0; i < ranges.length; i++) {
if(i>0) {
sb.append(", ");
}
sb.append(ranges[i]);
}
return sb.toString();
}
private void setMaxValues(Range[] ranges) {
//clone array, original array should stay same
Range[] c = (Range[])ranges.clone();
//sort array of Ranges
Arrays.sort(c, new Comparator() {
public int compare(Object o1, Object o2) {
Range c1 = (Range)o1;
Range c2 = (Range)o2;
return c1.getMin()-c2.getMin();
}
}
);
//set max values for all unbound ranges (except last range)
for (int i = 0; i < c.length - 1; i++) {
if (!c[i].hasMaxValue()) {
//set max value to (min value - 1) of the next range
c[i].setMax(c[i+1].getMin() - 1);
}
}
if (forceDisjointRanges) {
verifyRanges(c);
}
}
private void verifyRanges(Range[] ranges) {
//verify that ranges are disjoint
for(int i = 1; i < ranges.length;i++) {
Assert.isTrue(ranges[i-1].getMax() < ranges[i].getMin(),
"Ranges must be disjoint. Range[" + (i-1) + "]: (" + ranges[i-1] +
") Range[" + i +"]: (" + ranges[i] + ")");
}
}
}
package org.springframework.batch.io.file.transform;
import java.beans.PropertyEditorSupport;
import java.util.Arrays;
import java.util.Comparator;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/**
* Property editor implementation which parses string and creates array of
* ranges. Ranges can be provided in any order. </br> Input string should be
* provided in following format: 'range1, range2, range3,...' where range is
* specified as:
* <ul>
* <li>'X-Y', where X is minimum value and Y is maximum value (condition X<=Y
* is verified)</li>
* <li>or 'Z', where Z is minimum and maximum is calculated as (minimum of
* adjacent range - 1). Maximum of the last range is never calculated. Range
* stays unbound at maximum side if maximum value is not provided.</li>
* </ul>
* Minimum and maximum values can be from interval &lt;1, Integer.MAX_VALUE-1&gt;
* <p>
* Examples:</br>
* '1, 15, 25, 38, 55-60' is equal to '1-14, 15-24, 25-37, 38-54, 55-60' </br>
* '36, 14, 1-10, 15, 49-57' is equal to '36-48, 14-14, 1-10, 15-35, 49-57'
* <p>
* Property editor also allows to validate whether ranges are disjoint. Validation
* can be turned on/off by using {@link #forceDisjointRanges}. By default
* validation is turned off.
*
* @author peter.zozom
*/
public class RangeArrayPropertyEditor extends PropertyEditorSupport {
private boolean forceDisjointRanges = false;
/**
* Set force disjoint ranges. If set to TRUE, ranges are validated to be disjoint.
* For example: defining ranges '1-10, 5-15' will cause IllegalArgumentException in
* case of forceDisjointRanges=TRUE.
* @param forceDisjointRanges
*/
public void setForceDisjointRanges(boolean forceDisjointRanges) {
this.forceDisjointRanges = forceDisjointRanges;
}
public void setAsText(String text) throws IllegalArgumentException {
//split text into ranges
String[] strRanges = text.split(",");
Range[] ranges = new Range[strRanges.length];
//parse ranges and create array of Range objects
for (int i = 0; i < strRanges.length; i++) {
String[] range = strRanges[i].split("-");
int min;
int max;
if ((range.length == 1) && (StringUtils.hasText(range[0]))) {
min = Integer.parseInt(range[0].trim());
// correct max value will be assigned later
ranges[i] = new Range(min);
} else if ((range.length == 2) && (StringUtils.hasText(range[0]))
&& (StringUtils.hasText(range[1]))) {
min = Integer.parseInt(range[0].trim());
max = Integer.parseInt(range[1].trim());
ranges[i] = new Range(min,max);
} else {
throw new IllegalArgumentException("Range[" + i + "]: range (" + strRanges[i] + ") is invalid");
}
}
setMaxValues(ranges);
setValue(ranges);
}
public String getAsText() {
Range[] ranges = (Range[])getValue();
StringBuffer sb = new StringBuffer();
for (int i = 0; i < ranges.length; i++) {
if(i>0) {
sb.append(", ");
}
sb.append(ranges[i]);
}
return sb.toString();
}
private void setMaxValues(Range[] ranges) {
//clone array, original array should stay same
Range[] c = (Range[])ranges.clone();
//sort array of Ranges
Arrays.sort(c, new Comparator() {
public int compare(Object o1, Object o2) {
Range c1 = (Range)o1;
Range c2 = (Range)o2;
return c1.getMin()-c2.getMin();
}
}
);
//set max values for all unbound ranges (except last range)
for (int i = 0; i < c.length - 1; i++) {
if (!c[i].hasMaxValue()) {
//set max value to (min value - 1) of the next range
c[i].setMax(c[i+1].getMin() - 1);
}
}
if (forceDisjointRanges) {
verifyRanges(c);
}
}
private void verifyRanges(Range[] ranges) {
//verify that ranges are disjoint
for(int i = 1; i < ranges.length;i++) {
Assert.isTrue(ranges[i-1].getMax() < ranges[i].getMin(),
"Ranges must be disjoint. Range[" + (i-1) + "]: (" + ranges[i-1] +
") Range[" + i +"]: (" + ranges[i] + ")");
}
}
}

View File

@@ -1,227 +1,227 @@
/*
* 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.io.support;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import org.springframework.batch.item.ItemWriter;
import org.springframework.batch.item.exception.ClearFailedException;
import org.springframework.batch.item.exception.FlushFailedException;
import org.springframework.batch.repeat.RepeatContext;
import org.springframework.batch.repeat.synch.RepeatSynchronizationManager;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.core.JdbcOperations;
import org.springframework.jdbc.core.PreparedStatementCallback;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import org.springframework.util.Assert;
/**
* {@link ItemWriter} that uses the batching features from
* {@link PreparedStatement} if available and can take some rudimentary steps to
* locate a failure during a flush, and identify the items that failed. When one
* of those items is encountered again the batch is flushed aggressively so that
* the bad item is eventually identified and can be dealt with in isolation.<br/>
*
* The user must provide an SQL query and a special callback
* {@link ItemPreparedStatementSetter}, which is responsible for mapping the
* item to a PreparedStatement.
*
* @author Dave Syer
*
*/
public class BatchSqlUpdateItemWriter implements ItemWriter, InitializingBean {
/**
* Key for items processed in the current transaction {@link RepeatContext}.
*/
protected static final String ITEMS_PROCESSED = BatchSqlUpdateItemWriter.class.getName() + ".ITEMS_PROCESSED";
private Set failed = new HashSet();
private JdbcOperations jdbcTemplate;
private ItemPreparedStatementSetter preparedStatementSetter;
private String sql;
/**
* Public setter for the query string to execute on write. The parameters
* should correspond to those known to the
* {@link ItemPreparedStatementSetter}.
* @param sql the query to set
*/
public void setSql(String sql) {
this.sql = sql;
}
/**
* Public setter for the {@link ItemPreparedStatementSetter}.
* @param preparedStatementSetter the {@link ItemPreparedStatementSetter} to
* set
*/
public void setItemPreparedStatementSetter(ItemPreparedStatementSetter preparedStatementSetter) {
this.preparedStatementSetter = preparedStatementSetter;
}
/**
* Public setter for the {@link JdbcOperations}.
* @param jdbcTemplate the {@link JdbcOperations} to set
*/
public void setJdbcTemplate(JdbcOperations jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
/**
* Check mandatory properties - there must be a delegate.
*
* @see org.springframework.dao.support.DaoSupport#initDao()
*/
public void afterPropertiesSet() throws Exception {
Assert.notNull(jdbcTemplate, "BatchSqlUpdateItemWriter requires an data source.");
Assert.notNull(preparedStatementSetter, "BatchSqlUpdateItemWriter requires a ItemPreparedStatementSetter");
}
/**
* Buffer the item in a transaction resource, but flush aggressively if the
* item was previously part of a failed chunk.
*
* @throws Exception
*
* @see org.springframework.batch.io.OutputSource#write(java.lang.Object)
*/
public void write(Object output) throws Exception {
bindTransactionResources();
getProcessed().add(output);
flushIfNecessary(output);
}
/**
* Accessor for the list of processed items in this transaction.
*
* @return the processed
*/
private Set getProcessed() {
Assert.state(TransactionSynchronizationManager.hasResource(ITEMS_PROCESSED),
"Processed items not bound to transaction.");
Set processed = (Set) TransactionSynchronizationManager.getResource(ITEMS_PROCESSED);
return processed;
}
/**
* Set up the {@link RepeatContext} as a transaction resource.
*
* @param context the context to set
*/
private void bindTransactionResources() {
if (TransactionSynchronizationManager.hasResource(ITEMS_PROCESSED)) {
return;
}
TransactionSynchronizationManager.bindResource(ITEMS_PROCESSED, new HashSet());
}
/**
* Remove the transaction resource associated with this context.
*/
private void unbindTransactionResources() {
if (!TransactionSynchronizationManager.hasResource(ITEMS_PROCESSED)) {
return;
}
TransactionSynchronizationManager.unbindResource(ITEMS_PROCESSED);
}
/**
* Accessor for the context property.
*
* @param output
*
* @return the context
*/
private void flushIfNecessary(Object output) throws Exception {
boolean flush;
synchronized (failed) {
flush = failed.contains(output);
}
if (flush) {
RepeatContext context = RepeatSynchronizationManager.getContext();
// Force early completion to commit aggressively if we encounter a
// failed item (from a failed chunk but we don't know which one was
// the problem).
context.setCompleteOnly();
// Flush now, so that if there is a failure this record can be
// skipped.
doFlush();
}
}
/**
* Flush the hibernate session from within a repeat context.
*/
private void doFlush() {
final Set processed = getProcessed();
try {
if (!processed.isEmpty()) {
jdbcTemplate.execute(sql, new PreparedStatementCallback() {
public Object doInPreparedStatement(PreparedStatement ps) throws SQLException, DataAccessException {
for (Iterator iterator = processed.iterator(); iterator.hasNext();) {
Object item = (Object) iterator.next();
preparedStatementSetter.setValues(item, ps);
ps.addBatch();
}
return ps.executeBatch();
}
});
}
}
catch (RuntimeException e) {
synchronized (failed) {
failed.addAll(processed);
}
throw e;
}
finally {
getProcessed().clear();
}
}
/**
* Unbind transaction resources, effectively clearing the item buffer.
*
* @see org.springframework.batch.item.ItemWriter#clear()
*/
public void clear() throws ClearFailedException {
unbindTransactionResources();
}
/**
* Flush the internal item buffer and record failures if there are any.
*
* @see org.springframework.batch.item.ItemWriter#flush()
*/
public void flush() throws FlushFailedException {
try {
doFlush();
}
finally {
unbindTransactionResources();
}
}
}
/*
* 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.io.support;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import org.springframework.batch.item.ItemWriter;
import org.springframework.batch.item.exception.ClearFailedException;
import org.springframework.batch.item.exception.FlushFailedException;
import org.springframework.batch.repeat.RepeatContext;
import org.springframework.batch.repeat.synch.RepeatSynchronizationManager;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.core.JdbcOperations;
import org.springframework.jdbc.core.PreparedStatementCallback;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import org.springframework.util.Assert;
/**
* {@link ItemWriter} that uses the batching features from
* {@link PreparedStatement} if available and can take some rudimentary steps to
* locate a failure during a flush, and identify the items that failed. When one
* of those items is encountered again the batch is flushed aggressively so that
* the bad item is eventually identified and can be dealt with in isolation.<br/>
*
* The user must provide an SQL query and a special callback
* {@link ItemPreparedStatementSetter}, which is responsible for mapping the
* item to a PreparedStatement.
*
* @author Dave Syer
*
*/
public class BatchSqlUpdateItemWriter implements ItemWriter, InitializingBean {
/**
* Key for items processed in the current transaction {@link RepeatContext}.
*/
protected static final String ITEMS_PROCESSED = BatchSqlUpdateItemWriter.class.getName() + ".ITEMS_PROCESSED";
private Set failed = new HashSet();
private JdbcOperations jdbcTemplate;
private ItemPreparedStatementSetter preparedStatementSetter;
private String sql;
/**
* Public setter for the query string to execute on write. The parameters
* should correspond to those known to the
* {@link ItemPreparedStatementSetter}.
* @param sql the query to set
*/
public void setSql(String sql) {
this.sql = sql;
}
/**
* Public setter for the {@link ItemPreparedStatementSetter}.
* @param preparedStatementSetter the {@link ItemPreparedStatementSetter} to
* set
*/
public void setItemPreparedStatementSetter(ItemPreparedStatementSetter preparedStatementSetter) {
this.preparedStatementSetter = preparedStatementSetter;
}
/**
* Public setter for the {@link JdbcOperations}.
* @param jdbcTemplate the {@link JdbcOperations} to set
*/
public void setJdbcTemplate(JdbcOperations jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
/**
* Check mandatory properties - there must be a delegate.
*
* @see org.springframework.dao.support.DaoSupport#initDao()
*/
public void afterPropertiesSet() throws Exception {
Assert.notNull(jdbcTemplate, "BatchSqlUpdateItemWriter requires an data source.");
Assert.notNull(preparedStatementSetter, "BatchSqlUpdateItemWriter requires a ItemPreparedStatementSetter");
}
/**
* Buffer the item in a transaction resource, but flush aggressively if the
* item was previously part of a failed chunk.
*
* @throws Exception
*
* @see org.springframework.batch.io.OutputSource#write(java.lang.Object)
*/
public void write(Object output) throws Exception {
bindTransactionResources();
getProcessed().add(output);
flushIfNecessary(output);
}
/**
* Accessor for the list of processed items in this transaction.
*
* @return the processed
*/
private Set getProcessed() {
Assert.state(TransactionSynchronizationManager.hasResource(ITEMS_PROCESSED),
"Processed items not bound to transaction.");
Set processed = (Set) TransactionSynchronizationManager.getResource(ITEMS_PROCESSED);
return processed;
}
/**
* Set up the {@link RepeatContext} as a transaction resource.
*
* @param context the context to set
*/
private void bindTransactionResources() {
if (TransactionSynchronizationManager.hasResource(ITEMS_PROCESSED)) {
return;
}
TransactionSynchronizationManager.bindResource(ITEMS_PROCESSED, new HashSet());
}
/**
* Remove the transaction resource associated with this context.
*/
private void unbindTransactionResources() {
if (!TransactionSynchronizationManager.hasResource(ITEMS_PROCESSED)) {
return;
}
TransactionSynchronizationManager.unbindResource(ITEMS_PROCESSED);
}
/**
* Accessor for the context property.
*
* @param output
*
* @return the context
*/
private void flushIfNecessary(Object output) throws Exception {
boolean flush;
synchronized (failed) {
flush = failed.contains(output);
}
if (flush) {
RepeatContext context = RepeatSynchronizationManager.getContext();
// Force early completion to commit aggressively if we encounter a
// failed item (from a failed chunk but we don't know which one was
// the problem).
context.setCompleteOnly();
// Flush now, so that if there is a failure this record can be
// skipped.
doFlush();
}
}
/**
* Flush the hibernate session from within a repeat context.
*/
private void doFlush() {
final Set processed = getProcessed();
try {
if (!processed.isEmpty()) {
jdbcTemplate.execute(sql, new PreparedStatementCallback() {
public Object doInPreparedStatement(PreparedStatement ps) throws SQLException, DataAccessException {
for (Iterator iterator = processed.iterator(); iterator.hasNext();) {
Object item = (Object) iterator.next();
preparedStatementSetter.setValues(item, ps);
ps.addBatch();
}
return ps.executeBatch();
}
});
}
}
catch (RuntimeException e) {
synchronized (failed) {
failed.addAll(processed);
}
throw e;
}
finally {
getProcessed().clear();
}
}
/**
* Unbind transaction resources, effectively clearing the item buffer.
*
* @see org.springframework.batch.item.ItemWriter#clear()
*/
public void clear() throws ClearFailedException {
unbindTransactionResources();
}
/**
* Flush the internal item buffer and record failures if there are any.
*
* @see org.springframework.batch.item.ItemWriter#flush()
*/
public void flush() throws FlushFailedException {
try {
doFlush();
}
finally {
unbindTransactionResources();
}
}
}

View File

@@ -1,66 +1,66 @@
package org.springframework.batch.io.support;
import java.io.File;
import java.io.IOException;
import org.springframework.dao.DataAccessResourceFailureException;
import org.springframework.util.Assert;
/**
* Utility methods for files used in batch processing.
*
* @author Peter Zozom
*/
public class FileUtils {
// forbids instantiation
private FileUtils() {}
/**
* Set up output file for batch processing. This method implements common logic for
* handling output files when starting or restarting job/step.
* When starting output file processing, method creates/overwrites new file.
* When restarting output file processing, method checks whether file is writable.
*
* @param file file to be set up
* @param restarted TRUE signalizes that we are restarting output file processing
* @param overwriteOutputFile If set to TRUE, output file will be overwritten
* (this flag is ignored when processing is restart)
*
* @throws IllegalArgumentException when file is NULL
* @throws IllegalStateException when staring output file processing, file exists and
* flag "shouldDeleteExisting" is set to FALSE
* @throws DataAccessResourceFailureException when unable to create file or file is not writable
*/
public static void setUpOutputFile(File file, boolean restarted,
boolean overwriteOutputFile) {
Assert.notNull(file);
try {
if (!restarted) {
if (file.exists()) {
if(!overwriteOutputFile){
throw new DataAccessResourceFailureException("File already exists: ["
+ file.getAbsolutePath() + "]");
}
file.delete();
}
if (file.getParent() != null ) {
new File(file.getParent()).mkdirs();
}
file.createNewFile();
}
} catch (IOException ioe) {
throw new DataAccessResourceFailureException(
"Unable to create file: [" + file.getAbsolutePath() + "]",
ioe);
}
if (!file.canWrite()) {
throw new DataAccessResourceFailureException(
"File is not writable: [" + file.getAbsolutePath() + "]");
}
}
}
package org.springframework.batch.io.support;
import java.io.File;
import java.io.IOException;
import org.springframework.dao.DataAccessResourceFailureException;
import org.springframework.util.Assert;
/**
* Utility methods for files used in batch processing.
*
* @author Peter Zozom
*/
public class FileUtils {
// forbids instantiation
private FileUtils() {}
/**
* Set up output file for batch processing. This method implements common logic for
* handling output files when starting or restarting job/step.
* When starting output file processing, method creates/overwrites new file.
* When restarting output file processing, method checks whether file is writable.
*
* @param file file to be set up
* @param restarted TRUE signalizes that we are restarting output file processing
* @param overwriteOutputFile If set to TRUE, output file will be overwritten
* (this flag is ignored when processing is restart)
*
* @throws IllegalArgumentException when file is NULL
* @throws IllegalStateException when staring output file processing, file exists and
* flag "shouldDeleteExisting" is set to FALSE
* @throws DataAccessResourceFailureException when unable to create file or file is not writable
*/
public static void setUpOutputFile(File file, boolean restarted,
boolean overwriteOutputFile) {
Assert.notNull(file);
try {
if (!restarted) {
if (file.exists()) {
if(!overwriteOutputFile){
throw new DataAccessResourceFailureException("File already exists: ["
+ file.getAbsolutePath() + "]");
}
file.delete();
}
if (file.getParent() != null ) {
new File(file.getParent()).mkdirs();
}
file.createNewFile();
}
} catch (IOException ioe) {
throw new DataAccessResourceFailureException(
"Unable to create file: [" + file.getAbsolutePath() + "]",
ioe);
}
if (!file.canWrite()) {
throw new DataAccessResourceFailureException(
"File is not writable: [" + file.getAbsolutePath() + "]");
}
}
}

View File

@@ -1,210 +1,210 @@
/*
* 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.io.support;
import java.util.HashSet;
import java.util.Set;
import org.hibernate.SessionFactory;
import org.springframework.batch.item.ItemWriter;
import org.springframework.batch.item.exception.ClearFailedException;
import org.springframework.batch.item.exception.FlushFailedException;
import org.springframework.batch.repeat.RepeatContext;
import org.springframework.batch.repeat.synch.RepeatSynchronizationManager;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.orm.hibernate3.HibernateOperations;
import org.springframework.orm.hibernate3.HibernateTemplate;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import org.springframework.util.Assert;
/**
* {@link ItemWriter} that is aware of the Hibernate session and can take some
* responsibilities to do with chunk boundaries away from a less smart
* {@link ItemWriter} (the delegate). A delegate is required, and will be used
* to do the actual writing of the item.<br/>
*
* @author Dave Syer
*
*/
public class HibernateAwareItemWriter implements ItemWriter, InitializingBean {
/**
* Key for items processed in the current transaction {@link RepeatContext}.
*/
private static final String ITEMS_PROCESSED = HibernateAwareItemWriter.class.getName() + ".ITEMS_PROCESSED";
private Set failed = new HashSet();
private ItemWriter delegate;
private HibernateOperations hibernateTemplate;
/**
* Public setter for the {@link ItemWriter} property.
*
* @param delegate the delegate to set
*/
public void setDelegate(ItemWriter delegate) {
this.delegate = delegate;
}
/**
* Public setter for the {@link HibernateOperations} property.
*
* @param hibernateTemplate the hibernateTemplate to set
*/
public void setHibernateTemplate(HibernateOperations hibernateTemplate) {
this.hibernateTemplate = hibernateTemplate;
}
/**
* Set the Hibernate SessionFactory to be used internally. Will
* automatically create a HibernateTemplate for the given SessionFactory.
*
* @see #setHibernateTemplate
*/
public final void setSessionFactory(SessionFactory sessionFactory) {
this.hibernateTemplate = new HibernateTemplate(sessionFactory);
}
/**
* Check mandatory properties - there must be a delegate.
*
* @see org.springframework.dao.support.DaoSupport#initDao()
*/
public void afterPropertiesSet() throws Exception {
Assert.notNull(delegate, "HibernateAwareItemWriter requires an ItemWriter as a delegate.");
Assert.notNull(hibernateTemplate, "HibernateAwareItemWriter requires a HibernateOperations");
}
/**
* Use the delegate to actually do the writing, but flush aggressively if
* the item was previously part of a failed chunk.
*
* @throws Exception
*
* @see org.springframework.batch.io.OutputSource#write(java.lang.Object)
*/
public void write(Object output) throws Exception {
bindTransactionResources();
getProcessed().add(output);
delegate.write(output);
flushIfNecessary(output);
}
/**
* Accessor for the list of processed items in this transaction.
*
* @return the processed
*/
private Set getProcessed() {
Assert.state(TransactionSynchronizationManager.hasResource(ITEMS_PROCESSED),
"Processed items not bound to transaction.");
Set processed = (Set) TransactionSynchronizationManager.getResource(ITEMS_PROCESSED);
return processed;
}
/**
* Set up the {@link RepeatContext} as a transaction resource.
*
* @param context the context to set
*/
private void bindTransactionResources() {
if (TransactionSynchronizationManager.hasResource(ITEMS_PROCESSED)) {
return;
}
TransactionSynchronizationManager.bindResource(ITEMS_PROCESSED, new HashSet());
}
/**
* Remove the transaction resource associated with this context.
*/
private void unbindTransactionResources() {
if (!TransactionSynchronizationManager.hasResource(ITEMS_PROCESSED)) {
return;
}
TransactionSynchronizationManager.unbindResource(ITEMS_PROCESSED);
}
/**
* Accessor for the context property.
*
* @param output
*
* @return the context
*/
private void flushIfNecessary(Object output) throws Exception {
boolean flush;
synchronized (failed) {
flush = failed.contains(output);
}
if (flush) {
RepeatContext context = RepeatSynchronizationManager.getContext();
// Force early completion to commit aggressively if we encounter a
// failed item (from a failed chunk but we don't know which one was
// the problem).
context.setCompleteOnly();
// Flush now, so that if there is a failure this record can be
// skipped.
doHibernateFlush();
}
}
/**
* Flush the hibernate session from within a repeat context.
*/
private void doHibernateFlush() {
try {
hibernateTemplate.flush();
// This should happen when the transaction commits anyway, but to be
// sure...
hibernateTemplate.clear();
}
catch (RuntimeException e) {
synchronized (failed) {
failed.addAll(getProcessed());
}
// This used to contain a call to onError, however, I think this
// should be handled within the step.
throw e;
}
}
/**
* Call the delegate clear() method, and then clear the hibernate session.
*
* @see org.springframework.batch.item.ItemWriter#clear()
*/
public void clear() throws ClearFailedException {
unbindTransactionResources();
hibernateTemplate.clear();
delegate.clear();
}
/**
* Flush the Hibernate session and record failures if there are any. The
* delegate flush will also be called.
*
* @see org.springframework.batch.item.ItemWriter#flush()
*/
public void flush() throws FlushFailedException {
bindTransactionResources();
doHibernateFlush();
unbindTransactionResources();
delegate.flush();
}
}
/*
* 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.io.support;
import java.util.HashSet;
import java.util.Set;
import org.hibernate.SessionFactory;
import org.springframework.batch.item.ItemWriter;
import org.springframework.batch.item.exception.ClearFailedException;
import org.springframework.batch.item.exception.FlushFailedException;
import org.springframework.batch.repeat.RepeatContext;
import org.springframework.batch.repeat.synch.RepeatSynchronizationManager;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.orm.hibernate3.HibernateOperations;
import org.springframework.orm.hibernate3.HibernateTemplate;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import org.springframework.util.Assert;
/**
* {@link ItemWriter} that is aware of the Hibernate session and can take some
* responsibilities to do with chunk boundaries away from a less smart
* {@link ItemWriter} (the delegate). A delegate is required, and will be used
* to do the actual writing of the item.<br/>
*
* @author Dave Syer
*
*/
public class HibernateAwareItemWriter implements ItemWriter, InitializingBean {
/**
* Key for items processed in the current transaction {@link RepeatContext}.
*/
private static final String ITEMS_PROCESSED = HibernateAwareItemWriter.class.getName() + ".ITEMS_PROCESSED";
private Set failed = new HashSet();
private ItemWriter delegate;
private HibernateOperations hibernateTemplate;
/**
* Public setter for the {@link ItemWriter} property.
*
* @param delegate the delegate to set
*/
public void setDelegate(ItemWriter delegate) {
this.delegate = delegate;
}
/**
* Public setter for the {@link HibernateOperations} property.
*
* @param hibernateTemplate the hibernateTemplate to set
*/
public void setHibernateTemplate(HibernateOperations hibernateTemplate) {
this.hibernateTemplate = hibernateTemplate;
}
/**
* Set the Hibernate SessionFactory to be used internally. Will
* automatically create a HibernateTemplate for the given SessionFactory.
*
* @see #setHibernateTemplate
*/
public final void setSessionFactory(SessionFactory sessionFactory) {
this.hibernateTemplate = new HibernateTemplate(sessionFactory);
}
/**
* Check mandatory properties - there must be a delegate.
*
* @see org.springframework.dao.support.DaoSupport#initDao()
*/
public void afterPropertiesSet() throws Exception {
Assert.notNull(delegate, "HibernateAwareItemWriter requires an ItemWriter as a delegate.");
Assert.notNull(hibernateTemplate, "HibernateAwareItemWriter requires a HibernateOperations");
}
/**
* Use the delegate to actually do the writing, but flush aggressively if
* the item was previously part of a failed chunk.
*
* @throws Exception
*
* @see org.springframework.batch.io.OutputSource#write(java.lang.Object)
*/
public void write(Object output) throws Exception {
bindTransactionResources();
getProcessed().add(output);
delegate.write(output);
flushIfNecessary(output);
}
/**
* Accessor for the list of processed items in this transaction.
*
* @return the processed
*/
private Set getProcessed() {
Assert.state(TransactionSynchronizationManager.hasResource(ITEMS_PROCESSED),
"Processed items not bound to transaction.");
Set processed = (Set) TransactionSynchronizationManager.getResource(ITEMS_PROCESSED);
return processed;
}
/**
* Set up the {@link RepeatContext} as a transaction resource.
*
* @param context the context to set
*/
private void bindTransactionResources() {
if (TransactionSynchronizationManager.hasResource(ITEMS_PROCESSED)) {
return;
}
TransactionSynchronizationManager.bindResource(ITEMS_PROCESSED, new HashSet());
}
/**
* Remove the transaction resource associated with this context.
*/
private void unbindTransactionResources() {
if (!TransactionSynchronizationManager.hasResource(ITEMS_PROCESSED)) {
return;
}
TransactionSynchronizationManager.unbindResource(ITEMS_PROCESSED);
}
/**
* Accessor for the context property.
*
* @param output
*
* @return the context
*/
private void flushIfNecessary(Object output) throws Exception {
boolean flush;
synchronized (failed) {
flush = failed.contains(output);
}
if (flush) {
RepeatContext context = RepeatSynchronizationManager.getContext();
// Force early completion to commit aggressively if we encounter a
// failed item (from a failed chunk but we don't know which one was
// the problem).
context.setCompleteOnly();
// Flush now, so that if there is a failure this record can be
// skipped.
doHibernateFlush();
}
}
/**
* Flush the hibernate session from within a repeat context.
*/
private void doHibernateFlush() {
try {
hibernateTemplate.flush();
// This should happen when the transaction commits anyway, but to be
// sure...
hibernateTemplate.clear();
}
catch (RuntimeException e) {
synchronized (failed) {
failed.addAll(getProcessed());
}
// This used to contain a call to onError, however, I think this
// should be handled within the step.
throw e;
}
}
/**
* Call the delegate clear() method, and then clear the hibernate session.
*
* @see org.springframework.batch.item.ItemWriter#clear()
*/
public void clear() throws ClearFailedException {
unbindTransactionResources();
hibernateTemplate.clear();
delegate.clear();
}
/**
* Flush the Hibernate session and record failures if there are any. The
* delegate flush will also be called.
*
* @see org.springframework.batch.item.ItemWriter#flush()
*/
public void flush() throws FlushFailedException {
bindTransactionResources();
doHibernateFlush();
unbindTransactionResources();
delegate.flush();
}
}

View File

@@ -1,14 +1,14 @@
package org.springframework.batch.io.xml;
import javax.xml.stream.XMLEventReader;
/**
* Deserializes XML fragment to domain object.
* XML fragment is a standalone XML document corresponding to a single record.
*
* @author Robert Kasanicky
*/
public interface EventReaderDeserializer {
Object deserializeFragment(XMLEventReader eventReader);
}
package org.springframework.batch.io.xml;
import javax.xml.stream.XMLEventReader;
/**
* Deserializes XML fragment to domain object.
* XML fragment is a standalone XML document corresponding to a single record.
*
* @author Robert Kasanicky
*/
public interface EventReaderDeserializer {
Object deserializeFragment(XMLEventReader eventReader);
}

View File

@@ -1,21 +1,21 @@
package org.springframework.batch.io.xml;
import javax.xml.stream.XMLEventWriter;
/**
* Interface wrapping the serialization of an object
* to xml. Primarily useful for abstracting how an object
* is serialized to an XMLEvent from a specific marshaller.
*
* @author Lucas Ward
*
*/
public interface EventWriterSerializer {
/**
* Serialize an Object.
*
* @param output
*/
void serializeObject(XMLEventWriter writer, Object output);
}
package org.springframework.batch.io.xml;
import javax.xml.stream.XMLEventWriter;
/**
* Interface wrapping the serialization of an object
* to xml. Primarily useful for abstracting how an object
* is serialized to an XMLEvent from a specific marshaller.
*
* @author Lucas Ward
*
*/
public interface EventWriterSerializer {
/**
* Serialize an Object.
*
* @param output
*/
void serializeObject(XMLEventWriter writer, Object output);
}

View File

@@ -1,269 +1,269 @@
package org.springframework.batch.io.xml;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import javax.xml.namespace.QName;
import javax.xml.stream.XMLEventReader;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.events.StartElement;
import org.springframework.batch.io.Skippable;
import org.springframework.batch.io.xml.stax.DefaultFragmentEventReader;
import org.springframework.batch.io.xml.stax.DefaultTransactionalEventReader;
import org.springframework.batch.io.xml.stax.FragmentEventReader;
import org.springframework.batch.io.xml.stax.TransactionalEventReader;
import org.springframework.batch.item.ExecutionContext;
import org.springframework.batch.item.ExecutionContextUserSupport;
import org.springframework.batch.item.ItemReader;
import org.springframework.batch.item.ItemStream;
import org.springframework.batch.item.exception.StreamException;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.core.io.Resource;
import org.springframework.dao.DataAccessResourceFailureException;
import org.springframework.util.Assert;
/**
* Input source for reading XML input based on StAX.
*
* It extracts fragments from the input XML document which correspond to records
* for processing. The fragments are wrapped with StartDocument and EndDocument
* events so that the fragments can be further processed like standalone XML
* documents.
*
* @author Robert Kasanicky
*/
public class StaxEventItemReader extends ExecutionContextUserSupport implements ItemReader, Skippable, ItemStream,
InitializingBean {
private static final String READ_COUNT_STATISTICS_NAME = "read.count";
private FragmentEventReader fragmentReader;
private TransactionalEventReader txReader;
private EventReaderDeserializer eventReaderDeserializer;
private Resource resource;
private InputStream inputStream;
private String fragmentRootElementName;
private boolean initialized = false;
private long lastCommitPointRecordCount = 0;
private long currentRecordCount = 0;
private List skipRecords = new ArrayList();
private boolean saveState = false;
public StaxEventItemReader() {
setName(StaxEventItemReader.class.getSimpleName());
}
/**
* Read in the next root element from the file, and return it.
*
* @return the next available record, if none exist, return null
* @see org.springframework.batch.io.ItemReader#read()
*/
public Object read() {
if (!initialized) {
throw new StreamException("ItemStream must be open before it can be read.");
}
Object item = null;
do {
currentRecordCount++;
if (moveCursorToNextFragment(fragmentReader)) {
fragmentReader.markStartFragment();
item = eventReaderDeserializer.deserializeFragment(fragmentReader);
fragmentReader.markFragmentProcessed();
}
} while (skipRecords.contains(new Long(currentRecordCount)));
if (item == null) {
currentRecordCount--;
}
return item;
}
public void close(ExecutionContext executionContext) {
initialized = false;
if (fragmentReader == null && inputStream == null) {
return;
}
try {
fragmentReader.close();
inputStream.close();
}
catch (XMLStreamException e) {
throw new DataAccessResourceFailureException("Error while closing event reader", e);
}
catch (IOException e) {
throw new DataAccessResourceFailureException("Error while closing input stream", e);
}
finally {
fragmentReader = null;
inputStream = null;
}
}
public void open(ExecutionContext executionContext) {
Assert.state(resource.exists(), "Input resource does not exist: [" + resource + "]");
try {
inputStream = resource.getInputStream();
txReader = new DefaultTransactionalEventReader(XMLInputFactory.newInstance().createXMLEventReader(
inputStream));
fragmentReader = new DefaultFragmentEventReader(txReader);
}
catch (XMLStreamException xse) {
throw new DataAccessResourceFailureException("Unable to create XML reader", xse);
}
catch (IOException ioe) {
throw new DataAccessResourceFailureException("Unable to get input stream", ioe);
}
initialized = true;
if (executionContext.containsKey(getKey(READ_COUNT_STATISTICS_NAME))) {
long restoredRecordCount = executionContext.getLong(getKey(READ_COUNT_STATISTICS_NAME));
int REASONABLE_ADHOC_COMMIT_FREQUENCY = 100;
while (currentRecordCount <= restoredRecordCount) {
currentRecordCount++;
if (currentRecordCount % REASONABLE_ADHOC_COMMIT_FREQUENCY == 0) {
txReader.onCommit(); // reset the history buffer
}
if (!fragmentReader.hasNext()) {
throw new StreamException("Restore point must be before end of input");
}
fragmentReader.next();
moveCursorToNextFragment(fragmentReader);
}
mark(); // reset the history buffer
}
}
public void setResource(Resource resource) {
this.resource = resource;
}
/**
* @param eventReaderDeserializer maps xml fragments corresponding to
* records to objects
*/
public void setFragmentDeserializer(EventReaderDeserializer eventReaderDeserializer) {
this.eventReaderDeserializer = eventReaderDeserializer;
}
/**
* @param fragmentRootElementName name of the root element of the fragment
* TODO String can be ambiguous due to namespaces, use QName?
*/
public void setFragmentRootElementName(String fragmentRootElementName) {
this.fragmentRootElementName = fragmentRootElementName;
}
/**
* Mark the last read record as 'skipped', so that I will not be returned
* from read() in the case of a rollback.
*
* @see Skippable#skip()
*/
public void skip() {
skipRecords.add(new Long(currentRecordCount));
}
/**
* Ensure that all required dependencies for the ItemReader to run are
* provided after all properties have been set.
*
* @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet()
* @throws IllegalArgumentException if the Resource, FragmentDeserializer or
* FragmentRootElementName is null, or if the root element is empty.
* @throws IllegalStateException if the Resource does not exist.
*/
public void afterPropertiesSet() throws Exception {
Assert.notNull(resource, "The Resource must not be null.");
Assert.notNull(eventReaderDeserializer, "The FragmentDeserializer must not be null.");
Assert.hasLength(fragmentRootElementName, "The FragmentRootElementName must not be null");
}
/**
* @see ItemStream#update(ExecutionContext)
*/
public void update(ExecutionContext executionContext) {
if(saveState){
Assert.notNull(executionContext, "ExecutionContext must not be null");
executionContext.putLong(getKey(READ_COUNT_STATISTICS_NAME), currentRecordCount);
}
}
/**
* Responsible for moving the cursor before the StartElement of the fragment
* root.
*
* This implementation simply looks for the next corresponding element, it
* does not care about element nesting. You will need to override this
* method to correctly handle composite fragments.
*
* @return <code>true</code> if next fragment was found,
* <code>false</code> otherwise.
*/
protected boolean moveCursorToNextFragment(XMLEventReader reader) {
try {
while (true) {
while (reader.peek() != null && !reader.peek().isStartElement()) {
reader.nextEvent();
}
if (reader.peek() == null) {
return false;
}
QName startElementName = ((StartElement) reader.peek()).getName();
if (startElementName.getLocalPart().equals(fragmentRootElementName)) {
return true;
}
else {
reader.nextEvent();
}
}
}
catch (XMLStreamException e) {
throw new DataAccessResourceFailureException("Error while reading from event reader", e);
}
}
/**
* Mark is supported as long as this {@link ItemStream} is used in a
* single-threaded environment. The state backing the mark is a single
* counter, keeping track of the current position, so multiple threads
* cannot be accommodated.
*
* @see org.springframework.batch.item.reader.AbstractItemReader#mark()
*/
public void mark() {
lastCommitPointRecordCount = currentRecordCount;
txReader.onCommit();
skipRecords = new ArrayList();
}
/*
* (non-Javadoc)
* @see org.springframework.batch.item.ItemStream#reset(org.springframework.batch.item.ExecutionContext)
*/
public void reset() {
currentRecordCount = lastCommitPointRecordCount;
txReader.onRollback();
fragmentReader.reset();
}
public void setSaveState(boolean saveState) {
this.saveState = saveState;
}
}
package org.springframework.batch.io.xml;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import javax.xml.namespace.QName;
import javax.xml.stream.XMLEventReader;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.events.StartElement;
import org.springframework.batch.io.Skippable;
import org.springframework.batch.io.xml.stax.DefaultFragmentEventReader;
import org.springframework.batch.io.xml.stax.DefaultTransactionalEventReader;
import org.springframework.batch.io.xml.stax.FragmentEventReader;
import org.springframework.batch.io.xml.stax.TransactionalEventReader;
import org.springframework.batch.item.ExecutionContext;
import org.springframework.batch.item.ExecutionContextUserSupport;
import org.springframework.batch.item.ItemReader;
import org.springframework.batch.item.ItemStream;
import org.springframework.batch.item.exception.StreamException;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.core.io.Resource;
import org.springframework.dao.DataAccessResourceFailureException;
import org.springframework.util.Assert;
/**
* Input source for reading XML input based on StAX.
*
* It extracts fragments from the input XML document which correspond to records
* for processing. The fragments are wrapped with StartDocument and EndDocument
* events so that the fragments can be further processed like standalone XML
* documents.
*
* @author Robert Kasanicky
*/
public class StaxEventItemReader extends ExecutionContextUserSupport implements ItemReader, Skippable, ItemStream,
InitializingBean {
private static final String READ_COUNT_STATISTICS_NAME = "read.count";
private FragmentEventReader fragmentReader;
private TransactionalEventReader txReader;
private EventReaderDeserializer eventReaderDeserializer;
private Resource resource;
private InputStream inputStream;
private String fragmentRootElementName;
private boolean initialized = false;
private long lastCommitPointRecordCount = 0;
private long currentRecordCount = 0;
private List skipRecords = new ArrayList();
private boolean saveState = false;
public StaxEventItemReader() {
setName(StaxEventItemReader.class.getSimpleName());
}
/**
* Read in the next root element from the file, and return it.
*
* @return the next available record, if none exist, return null
* @see org.springframework.batch.io.ItemReader#read()
*/
public Object read() {
if (!initialized) {
throw new StreamException("ItemStream must be open before it can be read.");
}
Object item = null;
do {
currentRecordCount++;
if (moveCursorToNextFragment(fragmentReader)) {
fragmentReader.markStartFragment();
item = eventReaderDeserializer.deserializeFragment(fragmentReader);
fragmentReader.markFragmentProcessed();
}
} while (skipRecords.contains(new Long(currentRecordCount)));
if (item == null) {
currentRecordCount--;
}
return item;
}
public void close(ExecutionContext executionContext) {
initialized = false;
if (fragmentReader == null && inputStream == null) {
return;
}
try {
fragmentReader.close();
inputStream.close();
}
catch (XMLStreamException e) {
throw new DataAccessResourceFailureException("Error while closing event reader", e);
}
catch (IOException e) {
throw new DataAccessResourceFailureException("Error while closing input stream", e);
}
finally {
fragmentReader = null;
inputStream = null;
}
}
public void open(ExecutionContext executionContext) {
Assert.state(resource.exists(), "Input resource does not exist: [" + resource + "]");
try {
inputStream = resource.getInputStream();
txReader = new DefaultTransactionalEventReader(XMLInputFactory.newInstance().createXMLEventReader(
inputStream));
fragmentReader = new DefaultFragmentEventReader(txReader);
}
catch (XMLStreamException xse) {
throw new DataAccessResourceFailureException("Unable to create XML reader", xse);
}
catch (IOException ioe) {
throw new DataAccessResourceFailureException("Unable to get input stream", ioe);
}
initialized = true;
if (executionContext.containsKey(getKey(READ_COUNT_STATISTICS_NAME))) {
long restoredRecordCount = executionContext.getLong(getKey(READ_COUNT_STATISTICS_NAME));
int REASONABLE_ADHOC_COMMIT_FREQUENCY = 100;
while (currentRecordCount <= restoredRecordCount) {
currentRecordCount++;
if (currentRecordCount % REASONABLE_ADHOC_COMMIT_FREQUENCY == 0) {
txReader.onCommit(); // reset the history buffer
}
if (!fragmentReader.hasNext()) {
throw new StreamException("Restore point must be before end of input");
}
fragmentReader.next();
moveCursorToNextFragment(fragmentReader);
}
mark(); // reset the history buffer
}
}
public void setResource(Resource resource) {
this.resource = resource;
}
/**
* @param eventReaderDeserializer maps xml fragments corresponding to
* records to objects
*/
public void setFragmentDeserializer(EventReaderDeserializer eventReaderDeserializer) {
this.eventReaderDeserializer = eventReaderDeserializer;
}
/**
* @param fragmentRootElementName name of the root element of the fragment
* TODO String can be ambiguous due to namespaces, use QName?
*/
public void setFragmentRootElementName(String fragmentRootElementName) {
this.fragmentRootElementName = fragmentRootElementName;
}
/**
* Mark the last read record as 'skipped', so that I will not be returned
* from read() in the case of a rollback.
*
* @see Skippable#skip()
*/
public void skip() {
skipRecords.add(new Long(currentRecordCount));
}
/**
* Ensure that all required dependencies for the ItemReader to run are
* provided after all properties have been set.
*
* @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet()
* @throws IllegalArgumentException if the Resource, FragmentDeserializer or
* FragmentRootElementName is null, or if the root element is empty.
* @throws IllegalStateException if the Resource does not exist.
*/
public void afterPropertiesSet() throws Exception {
Assert.notNull(resource, "The Resource must not be null.");
Assert.notNull(eventReaderDeserializer, "The FragmentDeserializer must not be null.");
Assert.hasLength(fragmentRootElementName, "The FragmentRootElementName must not be null");
}
/**
* @see ItemStream#update(ExecutionContext)
*/
public void update(ExecutionContext executionContext) {
if(saveState){
Assert.notNull(executionContext, "ExecutionContext must not be null");
executionContext.putLong(getKey(READ_COUNT_STATISTICS_NAME), currentRecordCount);
}
}
/**
* Responsible for moving the cursor before the StartElement of the fragment
* root.
*
* This implementation simply looks for the next corresponding element, it
* does not care about element nesting. You will need to override this
* method to correctly handle composite fragments.
*
* @return <code>true</code> if next fragment was found,
* <code>false</code> otherwise.
*/
protected boolean moveCursorToNextFragment(XMLEventReader reader) {
try {
while (true) {
while (reader.peek() != null && !reader.peek().isStartElement()) {
reader.nextEvent();
}
if (reader.peek() == null) {
return false;
}
QName startElementName = ((StartElement) reader.peek()).getName();
if (startElementName.getLocalPart().equals(fragmentRootElementName)) {
return true;
}
else {
reader.nextEvent();
}
}
}
catch (XMLStreamException e) {
throw new DataAccessResourceFailureException("Error while reading from event reader", e);
}
}
/**
* Mark is supported as long as this {@link ItemStream} is used in a
* single-threaded environment. The state backing the mark is a single
* counter, keeping track of the current position, so multiple threads
* cannot be accommodated.
*
* @see org.springframework.batch.item.reader.AbstractItemReader#mark()
*/
public void mark() {
lastCommitPointRecordCount = currentRecordCount;
txReader.onCommit();
skipRecords = new ArrayList();
}
/*
* (non-Javadoc)
* @see org.springframework.batch.item.ItemStream#reset(org.springframework.batch.item.ExecutionContext)
*/
public void reset() {
currentRecordCount = lastCommitPointRecordCount;
txReader.onRollback();
fragmentReader.reset();
}
public void setSaveState(boolean saveState) {
this.saveState = saveState;
}
}

View File

@@ -1,447 +1,447 @@
package org.springframework.batch.io.xml;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import javax.xml.stream.XMLEventFactory;
import javax.xml.stream.XMLEventWriter;
import javax.xml.stream.XMLOutputFactory;
import javax.xml.stream.XMLStreamException;
import org.springframework.batch.io.support.FileUtils;
import org.springframework.batch.io.xml.stax.NoStartEndDocumentStreamWriter;
import org.springframework.batch.item.ExecutionContext;
import org.springframework.batch.item.ExecutionContextUserSupport;
import org.springframework.batch.item.ItemStream;
import org.springframework.batch.item.ItemWriter;
import org.springframework.batch.item.exception.ClearFailedException;
import org.springframework.batch.item.exception.FlushFailedException;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.core.io.Resource;
import org.springframework.dao.DataAccessResourceFailureException;
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 item writer also provides restart, statistics and transaction features
* by implementing corresponding interfaces.
*
* Output is buffered until {@link #flush()} is called - only then the actual
* writing to file takes place.
*
* @author Peter Zozom
* @author Robert Kasanicky
*
*/
public class StaxEventItemWriter extends ExecutionContextUserSupport implements ItemWriter, ItemStream,
InitializingBean {
// default encoding
private static final String DEFAULT_ENCODING = "UTF-8";
// default encoding
private static final String DEFAULT_XML_VERSION = "1.0";
// default root tag name
private static final String DEFAULT_ROOT_TAG_NAME = "root";
// restart data property name
private static final String RESTART_DATA_NAME = "position";
// restart data property name
private static final String WRITE_STATISTICS_NAME = "record.count";
// file system resource
private Resource resource;
// xml serializer
private EventWriterSerializer serializer;
// encoding to be used while reading from the resource
private String encoding = DEFAULT_ENCODING;
// XML version
private String version = DEFAULT_XML_VERSION;
// name of the root tag
private String rootTagName = DEFAULT_ROOT_TAG_NAME;
// root element attributes
private Map rootElementAttributes = null;
// signalizes that marshalling was restarted
private boolean restarted = false;
// 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
private XMLEventWriter eventWriter;
// XML event writer
private XMLEventWriter delegateEventWriter;
// byte offset in file channel at last commit point
private long lastCommitPointPosition = 0;
// processed record count at last commit point
private long lastCommitPointRecordCount = 0;
// current count of processed records
private long currentRecordCount = 0;
private boolean saveState = false;
// holds the list of items for writing before they are actually written on
// #flush()
private List buffer = new ArrayList();
public StaxEventItemWriter() {
setName(StaxEventItemWriter.class.getSimpleName());
}
/**
* Set output file.
*
* @param resource the output file
*/
public void setResource(Resource resource) {
this.resource = resource;
}
/**
* Set Object to XML serializer.
*
* @param serializer the Object to XML serializer
*/
public void setSerializer(EventWriterSerializer serializer) {
this.serializer = serializer;
}
/**
* Get used encoding.
*
* @return the encoding used
*/
public String getEncoding() {
return encoding;
}
/**
* Set encoding to be used for output file.
*
* @param encoding the encoding to be used
*/
public void setEncoding(String encoding) {
this.encoding = encoding;
}
/**
* Get XML version.
*
* @return the XML version used
*/
public String getVersion() {
return version;
}
/**
* Set XML version to be used for output XML.
*
* @param version the XML version to be used
*/
public void setVersion(String version) {
this.version = version;
}
/**
* Get the tag name of the root element.
*
* @return the root element tag name
*/
public String getRootTagName() {
return rootTagName;
}
/**
* 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) {
this.rootTagName = rootTagName;
}
/**
* Get attributes of the root element.
*
* @return attributes of the root element
*/
public Map getRootElementAttributes() {
return rootElementAttributes;
}
/**
* Set the root element attributes to be written.
*
* @param rootElementAttributes attributes of the root element
*/
public void setRootElementAttributes(Map rootElementAttributes) {
this.rootElementAttributes = rootElementAttributes;
}
/**
* Set "overwrite" flag for the output file. Flag is ignored when output
* file processing is restarted.
*
* @param shouldDeleteIfExists
*/
public void setOverwriteOutput(boolean overwriteOutput) {
this.overwriteOutput = overwriteOutput;
}
/**
* @throws Exception
* @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet()
*/
public void afterPropertiesSet() throws Exception {
Assert.notNull(resource);
Assert.notNull(serializer);
}
/**
* Open the output source
*
* @see org.springframework.batch.item.ResourceLifecycle#open()
*/
public void open(ExecutionContext executionContext) {
long startAtPosition = 0;
// if restart data is provided, restart from provided offset
// otherwise start from beginning
if (executionContext.containsKey(getKey(RESTART_DATA_NAME))) {
startAtPosition = executionContext.getLong(getKey(RESTART_DATA_NAME));
restarted = true;
}
open(startAtPosition);
}
/**
* Helper method for opening output source at given file position
*/
private void open(long position) {
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);
}
XMLOutputFactory outputFactory = XMLOutputFactory.newInstance();
try {
delegateEventWriter = outputFactory.createXMLEventWriter(os, encoding);
eventWriter = new NoStartEndDocumentStreamWriter(delegateEventWriter);
if (!restarted) {
startDocument(delegateEventWriter);
}
}
catch (XMLStreamException xse) {
throw new DataAccessResourceFailureException("Unable to write to file resource: [" + resource + "]", xse);
}
}
/**
* Writes simple XML header containing:
* <ul>
* <li>xml declaration - defines encoding and XML version</li>
* <li>opening tag of the root element and its attributes</li>
* </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
* @throws XMLStreamException
*/
private void startDocument(XMLEventWriter writer) throws XMLStreamException {
XMLEventFactory factory = XMLEventFactory.newInstance();
// write start document
writer.add(factory.createStartDocument(getEncoding(), getVersion()));
// write root tag
writer.add(factory.createStartElement("", "", getRootTagName()));
// 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()));
}
}
}
/**
* Finishes the XML document. It closes any start tag and writes
* corresponding end tags.
*
* @param writer XML event writer
* @throws XMLStreamException
*/
private 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.flush();
ByteBuffer bbuf = ByteBuffer.wrap(("</" + getRootTagName() + ">").getBytes());
try {
channel.write(bbuf);
}
catch (IOException ioe) {
throw new DataAccessResourceFailureException("Unable to close file resource: [" + resource + "]", ioe);
}
}
/**
* Flush and close the output source.
*
* @see org.springframework.batch.item.ResourceLifecycle#close(ExecutionContext)
*/
public void close(ExecutionContext executionContext) {
flush();
try {
endDocument(delegateEventWriter);
eventWriter.close();
channel.close();
}
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);
}
}
/**
* Write the value object to internal buffer.
*
* @param item the value object
* @see #flush()
*/
public void write(Object item) {
currentRecordCount++;
buffer.add(item);
}
/**
* Get the restart data.
* @see org.springframework.batch.item.ItemStream#update(ExecutionContext)
*/
public void update(ExecutionContext executionContext) {
if (saveState) {
Assert.notNull(executionContext, "ExecutionContext must not be null");
executionContext.putLong(getKey(RESTART_DATA_NAME), getPosition());
executionContext.putLong(getKey(WRITE_STATISTICS_NAME), currentRecordCount);
}
}
/*
* 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() {
long position;
try {
eventWriter.flush();
position = channel.position();
}
catch (Exception e) {
throw new DataAccessResourceFailureException("Unable to write to file resource: [" + resource + "]", e);
}
return position;
}
/**
* Set the file channel position.
*
* @param newPosition new file channel position
*/
private void setPosition(long newPosition) {
try {
Assert.state(channel.size() >= lastCommitPointPosition,
"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);
}
}
/**
* Writes buffered items to XML stream and marks restore point.
*/
public void flush() throws FlushFailedException {
for (Iterator iterator = buffer.listIterator(); iterator.hasNext();) {
Object item = iterator.next();
serializer.serializeObject(eventWriter, item);
}
buffer.clear();
lastCommitPointPosition = getPosition();
lastCommitPointRecordCount = currentRecordCount;
}
/**
* Clear the output buffer
*/
public void clear() throws ClearFailedException {
currentRecordCount = lastCommitPointRecordCount;
buffer.clear();
}
public void setSaveState(boolean saveState) {
this.saveState = saveState;
}
}
package org.springframework.batch.io.xml;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import javax.xml.stream.XMLEventFactory;
import javax.xml.stream.XMLEventWriter;
import javax.xml.stream.XMLOutputFactory;
import javax.xml.stream.XMLStreamException;
import org.springframework.batch.io.support.FileUtils;
import org.springframework.batch.io.xml.stax.NoStartEndDocumentStreamWriter;
import org.springframework.batch.item.ExecutionContext;
import org.springframework.batch.item.ExecutionContextUserSupport;
import org.springframework.batch.item.ItemStream;
import org.springframework.batch.item.ItemWriter;
import org.springframework.batch.item.exception.ClearFailedException;
import org.springframework.batch.item.exception.FlushFailedException;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.core.io.Resource;
import org.springframework.dao.DataAccessResourceFailureException;
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 item writer also provides restart, statistics and transaction features
* by implementing corresponding interfaces.
*
* Output is buffered until {@link #flush()} is called - only then the actual
* writing to file takes place.
*
* @author Peter Zozom
* @author Robert Kasanicky
*
*/
public class StaxEventItemWriter extends ExecutionContextUserSupport implements ItemWriter, ItemStream,
InitializingBean {
// default encoding
private static final String DEFAULT_ENCODING = "UTF-8";
// default encoding
private static final String DEFAULT_XML_VERSION = "1.0";
// default root tag name
private static final String DEFAULT_ROOT_TAG_NAME = "root";
// restart data property name
private static final String RESTART_DATA_NAME = "position";
// restart data property name
private static final String WRITE_STATISTICS_NAME = "record.count";
// file system resource
private Resource resource;
// xml serializer
private EventWriterSerializer serializer;
// encoding to be used while reading from the resource
private String encoding = DEFAULT_ENCODING;
// XML version
private String version = DEFAULT_XML_VERSION;
// name of the root tag
private String rootTagName = DEFAULT_ROOT_TAG_NAME;
// root element attributes
private Map rootElementAttributes = null;
// signalizes that marshalling was restarted
private boolean restarted = false;
// 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
private XMLEventWriter eventWriter;
// XML event writer
private XMLEventWriter delegateEventWriter;
// byte offset in file channel at last commit point
private long lastCommitPointPosition = 0;
// processed record count at last commit point
private long lastCommitPointRecordCount = 0;
// current count of processed records
private long currentRecordCount = 0;
private boolean saveState = false;
// holds the list of items for writing before they are actually written on
// #flush()
private List buffer = new ArrayList();
public StaxEventItemWriter() {
setName(StaxEventItemWriter.class.getSimpleName());
}
/**
* Set output file.
*
* @param resource the output file
*/
public void setResource(Resource resource) {
this.resource = resource;
}
/**
* Set Object to XML serializer.
*
* @param serializer the Object to XML serializer
*/
public void setSerializer(EventWriterSerializer serializer) {
this.serializer = serializer;
}
/**
* Get used encoding.
*
* @return the encoding used
*/
public String getEncoding() {
return encoding;
}
/**
* Set encoding to be used for output file.
*
* @param encoding the encoding to be used
*/
public void setEncoding(String encoding) {
this.encoding = encoding;
}
/**
* Get XML version.
*
* @return the XML version used
*/
public String getVersion() {
return version;
}
/**
* Set XML version to be used for output XML.
*
* @param version the XML version to be used
*/
public void setVersion(String version) {
this.version = version;
}
/**
* Get the tag name of the root element.
*
* @return the root element tag name
*/
public String getRootTagName() {
return rootTagName;
}
/**
* 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) {
this.rootTagName = rootTagName;
}
/**
* Get attributes of the root element.
*
* @return attributes of the root element
*/
public Map getRootElementAttributes() {
return rootElementAttributes;
}
/**
* Set the root element attributes to be written.
*
* @param rootElementAttributes attributes of the root element
*/
public void setRootElementAttributes(Map rootElementAttributes) {
this.rootElementAttributes = rootElementAttributes;
}
/**
* Set "overwrite" flag for the output file. Flag is ignored when output
* file processing is restarted.
*
* @param shouldDeleteIfExists
*/
public void setOverwriteOutput(boolean overwriteOutput) {
this.overwriteOutput = overwriteOutput;
}
/**
* @throws Exception
* @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet()
*/
public void afterPropertiesSet() throws Exception {
Assert.notNull(resource);
Assert.notNull(serializer);
}
/**
* Open the output source
*
* @see org.springframework.batch.item.ResourceLifecycle#open()
*/
public void open(ExecutionContext executionContext) {
long startAtPosition = 0;
// if restart data is provided, restart from provided offset
// otherwise start from beginning
if (executionContext.containsKey(getKey(RESTART_DATA_NAME))) {
startAtPosition = executionContext.getLong(getKey(RESTART_DATA_NAME));
restarted = true;
}
open(startAtPosition);
}
/**
* Helper method for opening output source at given file position
*/
private void open(long position) {
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);
}
XMLOutputFactory outputFactory = XMLOutputFactory.newInstance();
try {
delegateEventWriter = outputFactory.createXMLEventWriter(os, encoding);
eventWriter = new NoStartEndDocumentStreamWriter(delegateEventWriter);
if (!restarted) {
startDocument(delegateEventWriter);
}
}
catch (XMLStreamException xse) {
throw new DataAccessResourceFailureException("Unable to write to file resource: [" + resource + "]", xse);
}
}
/**
* Writes simple XML header containing:
* <ul>
* <li>xml declaration - defines encoding and XML version</li>
* <li>opening tag of the root element and its attributes</li>
* </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
* @throws XMLStreamException
*/
private void startDocument(XMLEventWriter writer) throws XMLStreamException {
XMLEventFactory factory = XMLEventFactory.newInstance();
// write start document
writer.add(factory.createStartDocument(getEncoding(), getVersion()));
// write root tag
writer.add(factory.createStartElement("", "", getRootTagName()));
// 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()));
}
}
}
/**
* Finishes the XML document. It closes any start tag and writes
* corresponding end tags.
*
* @param writer XML event writer
* @throws XMLStreamException
*/
private 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.flush();
ByteBuffer bbuf = ByteBuffer.wrap(("</" + getRootTagName() + ">").getBytes());
try {
channel.write(bbuf);
}
catch (IOException ioe) {
throw new DataAccessResourceFailureException("Unable to close file resource: [" + resource + "]", ioe);
}
}
/**
* Flush and close the output source.
*
* @see org.springframework.batch.item.ResourceLifecycle#close(ExecutionContext)
*/
public void close(ExecutionContext executionContext) {
flush();
try {
endDocument(delegateEventWriter);
eventWriter.close();
channel.close();
}
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);
}
}
/**
* Write the value object to internal buffer.
*
* @param item the value object
* @see #flush()
*/
public void write(Object item) {
currentRecordCount++;
buffer.add(item);
}
/**
* Get the restart data.
* @see org.springframework.batch.item.ItemStream#update(ExecutionContext)
*/
public void update(ExecutionContext executionContext) {
if (saveState) {
Assert.notNull(executionContext, "ExecutionContext must not be null");
executionContext.putLong(getKey(RESTART_DATA_NAME), getPosition());
executionContext.putLong(getKey(WRITE_STATISTICS_NAME), currentRecordCount);
}
}
/*
* 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() {
long position;
try {
eventWriter.flush();
position = channel.position();
}
catch (Exception e) {
throw new DataAccessResourceFailureException("Unable to write to file resource: [" + resource + "]", e);
}
return position;
}
/**
* Set the file channel position.
*
* @param newPosition new file channel position
*/
private void setPosition(long newPosition) {
try {
Assert.state(channel.size() >= lastCommitPointPosition,
"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);
}
}
/**
* Writes buffered items to XML stream and marks restore point.
*/
public void flush() throws FlushFailedException {
for (Iterator iterator = buffer.listIterator(); iterator.hasNext();) {
Object item = iterator.next();
serializer.serializeObject(eventWriter, item);
}
buffer.clear();
lastCommitPointPosition = getPosition();
lastCommitPointRecordCount = currentRecordCount;
}
/**
* Clear the output buffer
*/
public void clear() throws ClearFailedException {
currentRecordCount = lastCommitPointRecordCount;
buffer.clear();
}
public void setSaveState(boolean saveState) {
this.saveState = saveState;
}
}

View File

@@ -1,37 +1,37 @@
package org.springframework.batch.io.xml.oxm;
import java.io.IOException;
import javax.xml.stream.XMLEventWriter;
import javax.xml.transform.Result;
import org.springframework.batch.io.xml.EventWriterSerializer;
import org.springframework.dao.DataAccessResourceFailureException;
import org.springframework.oxm.Marshaller;
import org.springframework.xml.transform.StaxResult;
/**
* Object to xml serializer that wraps a Spring OXM {@link Marshaller} object.
*
* @author Lucas Ward
*
*/
public class MarshallingEventWriterSerializer implements EventWriterSerializer {
private Marshaller marshaller;
public MarshallingEventWriterSerializer(Marshaller marshaller) {
this.marshaller = marshaller;
}
public void serializeObject(XMLEventWriter writer, Object output) {
Result result = new StaxResult(writer);
try {
marshaller.marshal(output, result);
}
catch (IOException xse) {
throw new DataAccessResourceFailureException("Unable to write to file resource: [" + result.getSystemId()
+ "]", xse);
}
}
}
package org.springframework.batch.io.xml.oxm;
import java.io.IOException;
import javax.xml.stream.XMLEventWriter;
import javax.xml.transform.Result;
import org.springframework.batch.io.xml.EventWriterSerializer;
import org.springframework.dao.DataAccessResourceFailureException;
import org.springframework.oxm.Marshaller;
import org.springframework.xml.transform.StaxResult;
/**
* Object to xml serializer that wraps a Spring OXM {@link Marshaller} object.
*
* @author Lucas Ward
*
*/
public class MarshallingEventWriterSerializer implements EventWriterSerializer {
private Marshaller marshaller;
public MarshallingEventWriterSerializer(Marshaller marshaller) {
this.marshaller = marshaller;
}
public void serializeObject(XMLEventWriter writer, Object output) {
Result result = new StaxResult(writer);
try {
marshaller.marshal(output, result);
}
catch (IOException xse) {
throw new DataAccessResourceFailureException("Unable to write to file resource: [" + result.getSystemId()
+ "]", xse);
}
}
}

View File

@@ -1,38 +1,38 @@
package org.springframework.batch.io.xml.oxm;
import java.io.IOException;
import javax.xml.stream.XMLEventReader;
import org.springframework.batch.io.xml.EventReaderDeserializer;
import org.springframework.dao.DataAccessResourceFailureException;
import org.springframework.oxm.Unmarshaller;
import org.springframework.util.Assert;
import org.springframework.xml.transform.StaxSource;
/**
* Delegates deserializing to Spring OXM {@link Unmarshaller}.
*
* @author Robert Kasanicky
* @author Lucas Ward
*/
public class UnmarshallingEventReaderDeserializer implements EventReaderDeserializer {
private Unmarshaller unmarshaller;
public UnmarshallingEventReaderDeserializer(Unmarshaller unmarshaller){
Assert.notNull(unmarshaller);
this.unmarshaller = unmarshaller;
}
public Object deserializeFragment(XMLEventReader eventReader) {
Object item = null;
try {
item = unmarshaller.unmarshal(new StaxSource(eventReader));
}
catch (IOException e) {
throw new DataAccessResourceFailureException("IO error during unmarshalling", e);
}
return item;
}
}
package org.springframework.batch.io.xml.oxm;
import java.io.IOException;
import javax.xml.stream.XMLEventReader;
import org.springframework.batch.io.xml.EventReaderDeserializer;
import org.springframework.dao.DataAccessResourceFailureException;
import org.springframework.oxm.Unmarshaller;
import org.springframework.util.Assert;
import org.springframework.xml.transform.StaxSource;
/**
* Delegates deserializing to Spring OXM {@link Unmarshaller}.
*
* @author Robert Kasanicky
* @author Lucas Ward
*/
public class UnmarshallingEventReaderDeserializer implements EventReaderDeserializer {
private Unmarshaller unmarshaller;
public UnmarshallingEventReaderDeserializer(Unmarshaller unmarshaller){
Assert.notNull(unmarshaller);
this.unmarshaller = unmarshaller;
}
public Object deserializeFragment(XMLEventReader eventReader) {
Object item = null;
try {
item = unmarshaller.unmarshal(new StaxSource(eventReader));
}
catch (IOException e) {
throw new DataAccessResourceFailureException("IO error during unmarshalling", e);
}
return item;
}
}

View File

@@ -1,58 +1,58 @@
package org.springframework.batch.io.xml.stax;
import javax.xml.stream.XMLEventReader;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.events.XMLEvent;
/**
* Delegates all functionality to the wrapped reader allowing
* subclasses to override only the methods they want to change.
*
* @author Robert Kasanicky
*/
abstract class AbstractEventReaderWrapper implements XMLEventReader {
protected XMLEventReader wrappedEventReader;
public AbstractEventReaderWrapper(XMLEventReader wrappedEventReader) {
this.wrappedEventReader = wrappedEventReader;
}
public void close() throws XMLStreamException {
wrappedEventReader.close();
}
public String getElementText() throws XMLStreamException {
return wrappedEventReader.getElementText();
}
public Object getProperty(String name) throws IllegalArgumentException {
return wrappedEventReader.getProperty(name);
}
public boolean hasNext() {
return wrappedEventReader.hasNext();
}
public XMLEvent nextEvent() throws XMLStreamException {
return wrappedEventReader.nextEvent();
}
public XMLEvent nextTag() throws XMLStreamException {
return wrappedEventReader.nextTag();
}
public XMLEvent peek() throws XMLStreamException {
return wrappedEventReader.peek();
}
public Object next() {
return wrappedEventReader.next();
}
public void remove() {
wrappedEventReader.remove();
}
}
package org.springframework.batch.io.xml.stax;
import javax.xml.stream.XMLEventReader;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.events.XMLEvent;
/**
* Delegates all functionality to the wrapped reader allowing
* subclasses to override only the methods they want to change.
*
* @author Robert Kasanicky
*/
abstract class AbstractEventReaderWrapper implements XMLEventReader {
protected XMLEventReader wrappedEventReader;
public AbstractEventReaderWrapper(XMLEventReader wrappedEventReader) {
this.wrappedEventReader = wrappedEventReader;
}
public void close() throws XMLStreamException {
wrappedEventReader.close();
}
public String getElementText() throws XMLStreamException {
return wrappedEventReader.getElementText();
}
public Object getProperty(String name) throws IllegalArgumentException {
return wrappedEventReader.getProperty(name);
}
public boolean hasNext() {
return wrappedEventReader.hasNext();
}
public XMLEvent nextEvent() throws XMLStreamException {
return wrappedEventReader.nextEvent();
}
public XMLEvent nextTag() throws XMLStreamException {
return wrappedEventReader.nextTag();
}
public XMLEvent peek() throws XMLStreamException {
return wrappedEventReader.peek();
}
public Object next() {
return wrappedEventReader.next();
}
public void remove() {
wrappedEventReader.remove();
}
}

View File

@@ -1,58 +1,58 @@
package org.springframework.batch.io.xml.stax;
import javax.xml.namespace.NamespaceContext;
import javax.xml.stream.XMLEventReader;
import javax.xml.stream.XMLEventWriter;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.events.XMLEvent;
/**
* Delegates all functionality to the wrapped writer allowing
* subclasses to override only the methods they want to change.
*
* @author Robert Kasanicky
*/
abstract class AbstractEventWriterWrapper implements XMLEventWriter {
protected XMLEventWriter wrappedEventWriter;
public AbstractEventWriterWrapper(XMLEventWriter wrappedEventWriter) {
this.wrappedEventWriter = wrappedEventWriter;
}
public void add(XMLEvent event) throws XMLStreamException {
wrappedEventWriter.add(event);
}
public void add(XMLEventReader reader) throws XMLStreamException {
wrappedEventWriter.add(reader);
}
public void close() throws XMLStreamException {
wrappedEventWriter.close();
}
public void flush() throws XMLStreamException {
wrappedEventWriter.flush();
}
public NamespaceContext getNamespaceContext() {
return wrappedEventWriter.getNamespaceContext();
}
public String getPrefix(String uri) throws XMLStreamException {
return wrappedEventWriter.getPrefix(uri);
}
public void setDefaultNamespace(String uri) throws XMLStreamException {
wrappedEventWriter.setDefaultNamespace(uri);
}
public void setNamespaceContext(NamespaceContext context) throws XMLStreamException {
wrappedEventWriter.setNamespaceContext(context);
}
public void setPrefix(String prefix, String uri) throws XMLStreamException {
wrappedEventWriter.setPrefix(prefix, uri);
}
}
package org.springframework.batch.io.xml.stax;
import javax.xml.namespace.NamespaceContext;
import javax.xml.stream.XMLEventReader;
import javax.xml.stream.XMLEventWriter;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.events.XMLEvent;
/**
* Delegates all functionality to the wrapped writer allowing
* subclasses to override only the methods they want to change.
*
* @author Robert Kasanicky
*/
abstract class AbstractEventWriterWrapper implements XMLEventWriter {
protected XMLEventWriter wrappedEventWriter;
public AbstractEventWriterWrapper(XMLEventWriter wrappedEventWriter) {
this.wrappedEventWriter = wrappedEventWriter;
}
public void add(XMLEvent event) throws XMLStreamException {
wrappedEventWriter.add(event);
}
public void add(XMLEventReader reader) throws XMLStreamException {
wrappedEventWriter.add(reader);
}
public void close() throws XMLStreamException {
wrappedEventWriter.close();
}
public void flush() throws XMLStreamException {
wrappedEventWriter.flush();
}
public NamespaceContext getNamespaceContext() {
return wrappedEventWriter.getNamespaceContext();
}
public String getPrefix(String uri) throws XMLStreamException {
return wrappedEventWriter.getPrefix(uri);
}
public void setDefaultNamespace(String uri) throws XMLStreamException {
wrappedEventWriter.setDefaultNamespace(uri);
}
public void setNamespaceContext(NamespaceContext context) throws XMLStreamException {
wrappedEventWriter.setNamespaceContext(context);
}
public void setPrefix(String prefix, String uri) throws XMLStreamException {
wrappedEventWriter.setPrefix(prefix, uri);
}
}

View File

@@ -1,180 +1,180 @@
package org.springframework.batch.io.xml.stax;
import java.util.NoSuchElementException;
import javax.xml.namespace.QName;
import javax.xml.stream.XMLEventFactory;
import javax.xml.stream.XMLEventReader;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.events.EndDocument;
import javax.xml.stream.events.EndElement;
import javax.xml.stream.events.StartDocument;
import javax.xml.stream.events.StartElement;
import javax.xml.stream.events.XMLEvent;
import org.springframework.dao.DataAccessResourceFailureException;
/**
* Default implementation of {@link FragmentEventReader}
*
* @author Robert Kasanicky
*/
public class DefaultFragmentEventReader extends AbstractEventReaderWrapper implements FragmentEventReader {
// true when the next event is the StartElement of next fragment
private boolean startFragmentFollows = false;
// true when the next event is the EndElement of current fragment
private boolean endFragmentFollows = false;
// true while cursor is inside fragment
private boolean insideFragment = false;
// true when reader should behave like the cursor was at the end of document
private boolean fakeDocumentEnd = false;
private StartDocument startDocumentEvent = null;
private EndDocument endDocumentEvent = null;
// fragment root name is remembered so that the matching closing element can
// be identified
private QName fragmentRootName = null;
// counts the occurrences of current fragmentRootName (increased for
// StartElement, decreased for EndElement)
private int matchCounter = 0;
/**
* Caches the StartDocument event for later use.
* @param wrappedEventReader the original wrapped event reader
*/
public DefaultFragmentEventReader(XMLEventReader wrappedEventReader) {
super(wrappedEventReader);
try {
startDocumentEvent = (StartDocument) wrappedEventReader.peek();
}
catch (XMLStreamException e) {
throw new DataAccessResourceFailureException("Error reading start document from event reader", e);
}
endDocumentEvent = XMLEventFactory.newInstance().createEndDocument();
}
public void markStartFragment() {
startFragmentFollows = true;
fragmentRootName = null;
}
public boolean hasNext() {
try {
if (peek() != null) {
return true;
}
}
catch (XMLStreamException e) {
throw new DataAccessResourceFailureException("Error reading XML stream", e);
}
return false;
}
public Object next() {
try {
return nextEvent();
}
catch (XMLStreamException e) {
throw new DataAccessResourceFailureException("Error reading XML stream", e);
}
}
public XMLEvent nextEvent() throws XMLStreamException {
if (fakeDocumentEnd) {
throw new NoSuchElementException();
}
XMLEvent event = wrappedEventReader.peek();
XMLEvent proxyEvent = alterEvent(event, false);
checkFragmentEnd(proxyEvent);
if (event == proxyEvent) {
wrappedEventReader.nextEvent();
}
return proxyEvent;
}
/**
* Sets the endFragmentFollows flag to true if next event is the last event
* of the fragment.
* @param event peek() from wrapped event reader
*/
private void checkFragmentEnd(XMLEvent event) {
if (event.isStartElement() && ((StartElement) event).getName().equals(fragmentRootName)) {
matchCounter++;
}
else if (event.isEndElement() && ((EndElement) event).getName().equals(fragmentRootName)) {
matchCounter--;
if (matchCounter == 0) {
endFragmentFollows = true;
}
}
}
/**
* @param event peek() from wrapped event reader
* @param peek if true do not change the internal state
* @return StartDocument event if peek() points to beginning of fragment
* EndDocument event if cursor is right behind the end of fragment original
* event otherwise
*/
private XMLEvent alterEvent(XMLEvent event, boolean peek) {
if (startFragmentFollows) {
fragmentRootName = ((StartElement) event).getName();
if (!peek) {
startFragmentFollows = false;
insideFragment = true;
}
return startDocumentEvent;
}
else if (endFragmentFollows) {
if (!peek) {
endFragmentFollows = false;
insideFragment = false;
fakeDocumentEnd = true;
}
return endDocumentEvent;
}
return event;
}
public XMLEvent peek() throws XMLStreamException {
if (fakeDocumentEnd) {
return null;
}
return alterEvent(wrappedEventReader.peek(), true);
}
/**
* Finishes reading the fragment in case the fragment was processed without
* being read until the end.
*/
public void markFragmentProcessed() {
if (insideFragment|| startFragmentFollows) {
try {
while (!(nextEvent() instanceof EndDocument)) {
// just read all events until EndDocument
}
}
catch (XMLStreamException e) {
throw new DataAccessResourceFailureException("Error reading XML stream", e);
}
}
fakeDocumentEnd = false;
}
public void reset() {
insideFragment = false;
startFragmentFollows = false;
endFragmentFollows = false;
fakeDocumentEnd = false;
}
}
package org.springframework.batch.io.xml.stax;
import java.util.NoSuchElementException;
import javax.xml.namespace.QName;
import javax.xml.stream.XMLEventFactory;
import javax.xml.stream.XMLEventReader;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.events.EndDocument;
import javax.xml.stream.events.EndElement;
import javax.xml.stream.events.StartDocument;
import javax.xml.stream.events.StartElement;
import javax.xml.stream.events.XMLEvent;
import org.springframework.dao.DataAccessResourceFailureException;
/**
* Default implementation of {@link FragmentEventReader}
*
* @author Robert Kasanicky
*/
public class DefaultFragmentEventReader extends AbstractEventReaderWrapper implements FragmentEventReader {
// true when the next event is the StartElement of next fragment
private boolean startFragmentFollows = false;
// true when the next event is the EndElement of current fragment
private boolean endFragmentFollows = false;
// true while cursor is inside fragment
private boolean insideFragment = false;
// true when reader should behave like the cursor was at the end of document
private boolean fakeDocumentEnd = false;
private StartDocument startDocumentEvent = null;
private EndDocument endDocumentEvent = null;
// fragment root name is remembered so that the matching closing element can
// be identified
private QName fragmentRootName = null;
// counts the occurrences of current fragmentRootName (increased for
// StartElement, decreased for EndElement)
private int matchCounter = 0;
/**
* Caches the StartDocument event for later use.
* @param wrappedEventReader the original wrapped event reader
*/
public DefaultFragmentEventReader(XMLEventReader wrappedEventReader) {
super(wrappedEventReader);
try {
startDocumentEvent = (StartDocument) wrappedEventReader.peek();
}
catch (XMLStreamException e) {
throw new DataAccessResourceFailureException("Error reading start document from event reader", e);
}
endDocumentEvent = XMLEventFactory.newInstance().createEndDocument();
}
public void markStartFragment() {
startFragmentFollows = true;
fragmentRootName = null;
}
public boolean hasNext() {
try {
if (peek() != null) {
return true;
}
}
catch (XMLStreamException e) {
throw new DataAccessResourceFailureException("Error reading XML stream", e);
}
return false;
}
public Object next() {
try {
return nextEvent();
}
catch (XMLStreamException e) {
throw new DataAccessResourceFailureException("Error reading XML stream", e);
}
}
public XMLEvent nextEvent() throws XMLStreamException {
if (fakeDocumentEnd) {
throw new NoSuchElementException();
}
XMLEvent event = wrappedEventReader.peek();
XMLEvent proxyEvent = alterEvent(event, false);
checkFragmentEnd(proxyEvent);
if (event == proxyEvent) {
wrappedEventReader.nextEvent();
}
return proxyEvent;
}
/**
* Sets the endFragmentFollows flag to true if next event is the last event
* of the fragment.
* @param event peek() from wrapped event reader
*/
private void checkFragmentEnd(XMLEvent event) {
if (event.isStartElement() && ((StartElement) event).getName().equals(fragmentRootName)) {
matchCounter++;
}
else if (event.isEndElement() && ((EndElement) event).getName().equals(fragmentRootName)) {
matchCounter--;
if (matchCounter == 0) {
endFragmentFollows = true;
}
}
}
/**
* @param event peek() from wrapped event reader
* @param peek if true do not change the internal state
* @return StartDocument event if peek() points to beginning of fragment
* EndDocument event if cursor is right behind the end of fragment original
* event otherwise
*/
private XMLEvent alterEvent(XMLEvent event, boolean peek) {
if (startFragmentFollows) {
fragmentRootName = ((StartElement) event).getName();
if (!peek) {
startFragmentFollows = false;
insideFragment = true;
}
return startDocumentEvent;
}
else if (endFragmentFollows) {
if (!peek) {
endFragmentFollows = false;
insideFragment = false;
fakeDocumentEnd = true;
}
return endDocumentEvent;
}
return event;
}
public XMLEvent peek() throws XMLStreamException {
if (fakeDocumentEnd) {
return null;
}
return alterEvent(wrappedEventReader.peek(), true);
}
/**
* Finishes reading the fragment in case the fragment was processed without
* being read until the end.
*/
public void markFragmentProcessed() {
if (insideFragment|| startFragmentFollows) {
try {
while (!(nextEvent() instanceof EndDocument)) {
// just read all events until EndDocument
}
}
catch (XMLStreamException e) {
throw new DataAccessResourceFailureException("Error reading XML stream", e);
}
}
fakeDocumentEnd = false;
}
public void reset() {
insideFragment = false;
startFragmentFollows = false;
endFragmentFollows = false;
fakeDocumentEnd = false;
}
}

View File

@@ -1,96 +1,96 @@
package org.springframework.batch.io.xml.stax;
import java.util.NoSuchElementException;
import javax.xml.stream.XMLEventReader;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.events.XMLEvent;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.util.Assert;
/**
* Class used to wrap XMLEventReader. Events from wrapped reader are stored in
* {@link EventSequence} to support transactions.
*
* @author Tomas Slanina
* @author Robert Kasanicky
*/
public class DefaultTransactionalEventReader extends AbstractEventReaderWrapper implements TransactionalEventReader, InitializingBean {
private EventSequence recorder = new EventSequence();
/**
* Creates instance of this class and wraps XMLEventReader.
*
* @param parent event reader to be wrapped.
*/
public DefaultTransactionalEventReader(XMLEventReader wrappedReader) {
super(wrappedReader);
}
public void afterPropertiesSet() throws Exception {
Assert.notNull(wrappedEventReader);
}
/**
* Callback on transaction rollback.
*/
public void onRollback() {
recorder.reset();
}
/**
* Callback on transacion commit.
*
*/
public void onCommit() {
recorder.clear();
}
/**
* Check if there are more events. Returns true if there are more events and
* false otherwise.
*
* @return true if the event reader has more events, false otherwise
*/
public boolean hasNext() {
return recorder.hasNext() || wrappedEventReader.hasNext();
}
/**
* Get the next XMLEvent
*
* @see XMLEvent
* @throws XMLStreamException if there is an error with the underlying XML.
* @throws NoSuchElementException iteration has no more elements.
*/
public XMLEvent nextEvent() throws XMLStreamException {
if (!recorder.hasNext()) {
recorder.addEvent(wrappedEventReader.nextEvent());
}
return recorder.nextEvent();
}
/**
* Check the next XMLEvent without reading it from the stream. Returns null
* if the stream is at EOF or has no more XMLEvents. A call to peek() will
* be equal to the next return of next().
*
* @see XMLEvent
* @throws XMLStreamException
*/
public XMLEvent peek() throws XMLStreamException {
return (recorder.hasNext()) ? recorder.peek() : wrappedEventReader.peek();
}
/**
* In this implementation throws UnsupportedOperationException.
*/
public void remove() {
throw new UnsupportedOperationException();
}
}
package org.springframework.batch.io.xml.stax;
import java.util.NoSuchElementException;
import javax.xml.stream.XMLEventReader;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.events.XMLEvent;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.util.Assert;
/**
* Class used to wrap XMLEventReader. Events from wrapped reader are stored in
* {@link EventSequence} to support transactions.
*
* @author Tomas Slanina
* @author Robert Kasanicky
*/
public class DefaultTransactionalEventReader extends AbstractEventReaderWrapper implements TransactionalEventReader, InitializingBean {
private EventSequence recorder = new EventSequence();
/**
* Creates instance of this class and wraps XMLEventReader.
*
* @param parent event reader to be wrapped.
*/
public DefaultTransactionalEventReader(XMLEventReader wrappedReader) {
super(wrappedReader);
}
public void afterPropertiesSet() throws Exception {
Assert.notNull(wrappedEventReader);
}
/**
* Callback on transaction rollback.
*/
public void onRollback() {
recorder.reset();
}
/**
* Callback on transacion commit.
*
*/
public void onCommit() {
recorder.clear();
}
/**
* Check if there are more events. Returns true if there are more events and
* false otherwise.
*
* @return true if the event reader has more events, false otherwise
*/
public boolean hasNext() {
return recorder.hasNext() || wrappedEventReader.hasNext();
}
/**
* Get the next XMLEvent
*
* @see XMLEvent
* @throws XMLStreamException if there is an error with the underlying XML.
* @throws NoSuchElementException iteration has no more elements.
*/
public XMLEvent nextEvent() throws XMLStreamException {
if (!recorder.hasNext()) {
recorder.addEvent(wrappedEventReader.nextEvent());
}
return recorder.nextEvent();
}
/**
* Check the next XMLEvent without reading it from the stream. Returns null
* if the stream is at EOF or has no more XMLEvents. A call to peek() will
* be equal to the next return of next().
*
* @see XMLEvent
* @throws XMLStreamException
*/
public XMLEvent peek() throws XMLStreamException {
return (recorder.hasNext()) ? recorder.peek() : wrappedEventReader.peek();
}
/**
* In this implementation throws UnsupportedOperationException.
*/
public void remove() {
throw new UnsupportedOperationException();
}
}

View File

@@ -1,92 +1,92 @@
package org.springframework.batch.io.xml.stax;
import java.util.ArrayList;
import java.util.List;
import javax.xml.stream.events.XMLEvent;
/**
* Holds a list of XML events, typically corresponding to a single record.
*
* @author tomas.slanina
*/
class EventSequence {
private static final int BEFORE_BEGINNING = -1;
private List events;
private int currentIndex;
/**
* Creates instance of this class.
*
*/
public EventSequence() {
init();
}
/**
* Adds event to the list of stored events.
*
* @param event
*/
public void addEvent(XMLEvent event) {
events.add(event);
}
/**
* Gets next XMLEvent from cache and moves cursor to next event.
* If cache contains no more events, null is returned.
*
* @return
*/
public XMLEvent nextEvent() {
return (hasNext()) ? (XMLEvent)events.get(++currentIndex) :null;
}
/**
* Gets next XMLEvent from cache but cursor remains on the same position.
* If cache contains no more events, null is returned.
*
* @return
*/
public XMLEvent peek() {
return (hasNext()) ? (XMLEvent)events.get(currentIndex+1) :null;
}
/**
* Removes events from the internal cache.
*
*/
public void clear() {
init();
}
/**
* Resets cursor to the cache start.
*
*/
public void reset() {
currentIndex = BEFORE_BEGINNING;
}
/**
* Check if there are more events. Returns true if there are more events and
* false otherwise.
*
* @return true if the event reader has more events, false otherwise
*/
public boolean hasNext() {
return currentIndex + 1 < events.size();
}
private void init() {
events = (events != null) ? new ArrayList(events.size())
: new ArrayList(1000);
reset();
}
}
package org.springframework.batch.io.xml.stax;
import java.util.ArrayList;
import java.util.List;
import javax.xml.stream.events.XMLEvent;
/**
* Holds a list of XML events, typically corresponding to a single record.
*
* @author tomas.slanina
*/
class EventSequence {
private static final int BEFORE_BEGINNING = -1;
private List events;
private int currentIndex;
/**
* Creates instance of this class.
*
*/
public EventSequence() {
init();
}
/**
* Adds event to the list of stored events.
*
* @param event
*/
public void addEvent(XMLEvent event) {
events.add(event);
}
/**
* Gets next XMLEvent from cache and moves cursor to next event.
* If cache contains no more events, null is returned.
*
* @return
*/
public XMLEvent nextEvent() {
return (hasNext()) ? (XMLEvent)events.get(++currentIndex) :null;
}
/**
* Gets next XMLEvent from cache but cursor remains on the same position.
* If cache contains no more events, null is returned.
*
* @return
*/
public XMLEvent peek() {
return (hasNext()) ? (XMLEvent)events.get(currentIndex+1) :null;
}
/**
* Removes events from the internal cache.
*
*/
public void clear() {
init();
}
/**
* Resets cursor to the cache start.
*
*/
public void reset() {
currentIndex = BEFORE_BEGINNING;
}
/**
* Check if there are more events. Returns true if there are more events and
* false otherwise.
*
* @return true if the event reader has more events, false otherwise
*/
public boolean hasNext() {
return currentIndex + 1 < events.size();
}
private void init() {
events = (events != null) ? new ArrayList(events.size())
: new ArrayList(1000);
reset();
}
}

View File

@@ -1,33 +1,33 @@
package org.springframework.batch.io.xml.stax;
import javax.xml.stream.XMLEventReader;
/**
* Interface for event readers which support treating XML fragments as standalone XML documents
* by wrapping the fragments with StartDocument and EndDocument events.
*
* @author Robert Kasanicky
*/
public interface FragmentEventReader extends XMLEventReader {
/**
* Tells the event reader its cursor position is exactly before the fragment.
*/
void markStartFragment();
/**
* Tells the event reader the current fragment has been processed.
* If the cursor is still inside the fragment it should be moved
* after the end of the fragment.
*/
void markFragmentProcessed();
/**
* Reset the state of the fragment reader - make it forget
* it assumptions about current position of cursor
* (e.g. in case of rollback of the wrapped reader).
*/
void reset();
}
package org.springframework.batch.io.xml.stax;
import javax.xml.stream.XMLEventReader;
/**
* Interface for event readers which support treating XML fragments as standalone XML documents
* by wrapping the fragments with StartDocument and EndDocument events.
*
* @author Robert Kasanicky
*/
public interface FragmentEventReader extends XMLEventReader {
/**
* Tells the event reader its cursor position is exactly before the fragment.
*/
void markStartFragment();
/**
* Tells the event reader the current fragment has been processed.
* If the cursor is still inside the fragment it should be moved
* after the end of the fragment.
*/
void markFragmentProcessed();
/**
* Reset the state of the fragment reader - make it forget
* it assumptions about current position of cursor
* (e.g. in case of rollback of the wrapped reader).
*/
void reset();
}

View File

@@ -1,25 +1,25 @@
package org.springframework.batch.io.xml.stax;
import javax.xml.stream.XMLEventWriter;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.events.XMLEvent;
/**
* Delegating XMLEventWriter, which ignores start and end document events,
* but passes through everything else.
*
* @author peter.zozom
* @author Robert Kasanicky
*/
public class NoStartEndDocumentStreamWriter extends AbstractEventWriterWrapper {
public NoStartEndDocumentStreamWriter(XMLEventWriter wrappedEventWriter) {
super(wrappedEventWriter);
}
public void add(XMLEvent event) throws XMLStreamException {
if ((!event.isStartDocument()) && (!event.isEndDocument())) {
wrappedEventWriter.add(event);
}
}
}
package org.springframework.batch.io.xml.stax;
import javax.xml.stream.XMLEventWriter;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.events.XMLEvent;
/**
* Delegating XMLEventWriter, which ignores start and end document events,
* but passes through everything else.
*
* @author peter.zozom
* @author Robert Kasanicky
*/
public class NoStartEndDocumentStreamWriter extends AbstractEventWriterWrapper {
public NoStartEndDocumentStreamWriter(XMLEventWriter wrappedEventWriter) {
super(wrappedEventWriter);
}
public void add(XMLEvent event) throws XMLStreamException {
if ((!event.isStartDocument()) && (!event.isEndDocument())) {
wrappedEventWriter.add(event);
}
}
}

View File

@@ -1,22 +1,22 @@
package org.springframework.batch.io.xml.stax;
import javax.xml.stream.XMLEventReader;
/**
* XMLEventReader with transactional capabilities (ability to rollback to last commit point).
*
* @author Robert Kasanicky
*/
public interface TransactionalEventReader extends XMLEventReader{
/**
* Callback on transaction rollback.
*/
public void onRollback();
/**
* Callback on transaction commit.
*/
public void onCommit();
}
package org.springframework.batch.io.xml.stax;
import javax.xml.stream.XMLEventReader;
/**
* XMLEventReader with transactional capabilities (ability to rollback to last commit point).
*
* @author Robert Kasanicky
*/
public interface TransactionalEventReader extends XMLEventReader{
/**
* Callback on transaction rollback.
*/
public void onRollback();
/**
* Callback on transaction commit.
*/
public void onCommit();
}

View File

@@ -1,49 +1,49 @@
/*
* 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.item;
import org.springframework.util.Assert;
/**
* Facilitates assigning names to objects persisting data in
* {@link ExecutionContext} and generating keys for {@link ExecutionContext}
* based on the name.
*
* @author Robert Kasanicky
*/
public class ExecutionContextUserSupport {
private String name;
/**
* @param name unique name for the item stream used to create execution
* context keys.
*/
public void setName(String name) {
this.name = name;
}
/**
* Prefix the argument with the name of the item stream to create a unique
* key that can be safely used to identify data stored in
* {@link ExecutionContext}.
*/
protected String getKey(String s) {
Assert.hasLength(name, "ItemStream must have a name assigned.");
return name + "." + s;
}
}
/*
* 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.item;
import org.springframework.util.Assert;
/**
* Facilitates assigning names to objects persisting data in
* {@link ExecutionContext} and generating keys for {@link ExecutionContext}
* based on the name.
*
* @author Robert Kasanicky
*/
public class ExecutionContextUserSupport {
private String name;
/**
* @param name unique name for the item stream used to create execution
* context keys.
*/
public void setName(String name) {
this.name = name;
}
/**
* Prefix the argument with the name of the item stream to create a unique
* key that can be safely used to identify data stored in
* {@link ExecutionContext}.
*/
protected String getKey(String s) {
Assert.hasLength(name, "ItemStream must have a name assigned.");
return name + "." + s;
}
}

View File

@@ -1,113 +1,113 @@
/*
* 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.item.reader;
import java.util.ArrayList;
import java.util.Collection;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.batch.io.file.mapping.FieldSetMapper;
import org.springframework.batch.item.ItemReader;
/**
* An {@link ItemReader} that delivers a list as its item, storing up objects
* from the injected {@link ItemReader} until they are ready to be packed out
* as a collection. The {@link ItemReader} should mark the beginning and end of
* records with the constant values in {@link FieldSetMapper} ({@link AggregateItemReader#BEGIN_RECORD}
* and {@link AggregateItemReader#END_RECORD}).<br/>
*
* This class is thread safe (it can be used concurrently by multiple threads)
* as long as the {@link ItemReader} is also thread safe.
*
* @author Dave Syer
*
*/
public class AggregateItemReader extends DelegatingItemReader {
private static final Log log = LogFactory
.getLog(AggregateItemReader.class);
/**
* Marker for the end of a multi-object record.
*/
public static final Object END_RECORD = new Object();
/**
* Marker for the beginning of a multi-object record.
*/
public static final Object BEGIN_RECORD = new Object();
/**
* Get the next list of records.
* @throws Exception
*
* @see org.springframework.batch.item.ItemReader#read()
*/
public Object read() throws Exception {
ResultHolder holder = new ResultHolder();
while (process(super.read(), holder)) {
continue;
}
if (!holder.exhausted) {
return holder.records;
} else {
return null;
}
}
private boolean process(Object value, ResultHolder holder) {
// finish processing if we hit the end of file
if (value == null) {
log.debug("Exhausted ItemReader");
holder.exhausted = true;
return false;
}
// start a new collection
if (value == AggregateItemReader.BEGIN_RECORD) {
log.debug("Start of new record detected");
return true;
}
// mark we are finished with current collection
if (value == AggregateItemReader.END_RECORD) {
log.debug("End of record detected");
return false;
}
// add a simple record to the current collection
log.debug("Mapping: " + value);
holder.records.add(value);
return true;
}
/**
* Private class for temporary state management while item is being
* collected.
*
* @author Dave Syer
*
*/
private static class ResultHolder {
Collection records = new ArrayList();
boolean exhausted = false;
}
}
/*
* 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.item.reader;
import java.util.ArrayList;
import java.util.Collection;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.batch.io.file.mapping.FieldSetMapper;
import org.springframework.batch.item.ItemReader;
/**
* An {@link ItemReader} that delivers a list as its item, storing up objects
* from the injected {@link ItemReader} until they are ready to be packed out
* as a collection. The {@link ItemReader} should mark the beginning and end of
* records with the constant values in {@link FieldSetMapper} ({@link AggregateItemReader#BEGIN_RECORD}
* and {@link AggregateItemReader#END_RECORD}).<br/>
*
* This class is thread safe (it can be used concurrently by multiple threads)
* as long as the {@link ItemReader} is also thread safe.
*
* @author Dave Syer
*
*/
public class AggregateItemReader extends DelegatingItemReader {
private static final Log log = LogFactory
.getLog(AggregateItemReader.class);
/**
* Marker for the end of a multi-object record.
*/
public static final Object END_RECORD = new Object();
/**
* Marker for the beginning of a multi-object record.
*/
public static final Object BEGIN_RECORD = new Object();
/**
* Get the next list of records.
* @throws Exception
*
* @see org.springframework.batch.item.ItemReader#read()
*/
public Object read() throws Exception {
ResultHolder holder = new ResultHolder();
while (process(super.read(), holder)) {
continue;
}
if (!holder.exhausted) {
return holder.records;
} else {
return null;
}
}
private boolean process(Object value, ResultHolder holder) {
// finish processing if we hit the end of file
if (value == null) {
log.debug("Exhausted ItemReader");
holder.exhausted = true;
return false;
}
// start a new collection
if (value == AggregateItemReader.BEGIN_RECORD) {
log.debug("Start of new record detected");
return true;
}
// mark we are finished with current collection
if (value == AggregateItemReader.END_RECORD) {
log.debug("End of record detected");
return false;
}
// add a simple record to the current collection
log.debug("Mapping: " + value);
holder.records.add(value);
return true;
}
/**
* Private class for temporary state management while item is being
* collected.
*
* @author Dave Syer
*
*/
private static class ResultHolder {
Collection records = new ArrayList();
boolean exhausted = false;
}
}

View File

@@ -1,92 +1,92 @@
/*
* 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.item.reader;
import org.springframework.batch.io.Skippable;
import org.springframework.batch.item.ItemReader;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.util.Assert;
/**
* Simple wrapper around {@link ItemReader}. The input source is expected to
* take care of open and close operations. If necessary it should be registered
* as a step scoped bean to ensure that the lifecycle methods are called.
*
* @author Dave Syer
*/
public class DelegatingItemReader extends AbstractItemReader implements Skippable, InitializingBean {
private ItemReader itemReader;
/**
* Default constructor.
*/
public DelegatingItemReader() {
super();
}
/**
* Convenience constructor for setting mandatory property.
*/
public DelegatingItemReader(ItemReader itemReader) {
this();
this.itemReader = itemReader;
}
public void afterPropertiesSet() throws Exception {
Assert.notNull(itemReader, "ItemReader must not be null.");
}
/**
* Get the next object from the input source.
* @throws Exception
* @see org.springframework.batch.item.ItemReader#read()
*/
public Object read() throws Exception {
return itemReader.read();
}
/**
* Setter for input source.
* @param source
*/
public void setItemReader(ItemReader source) {
this.itemReader = source;
}
public void skip() {
if (itemReader instanceof Skippable) {
((Skippable) itemReader).skip();
}
}
/*
* (non-Javadoc)
* @see org.springframework.batch.item.ItemStream#mark(org.springframework.batch.item.ExecutionContext)
*/
public void mark() {
itemReader.mark();
}
/*
* (non-Javadoc)
* @see org.springframework.batch.item.ItemStream#reset(org.springframework.batch.item.ExecutionContext)
*/
public void reset() {
itemReader.reset();
}
}
/*
* 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.item.reader;
import org.springframework.batch.io.Skippable;
import org.springframework.batch.item.ItemReader;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.util.Assert;
/**
* Simple wrapper around {@link ItemReader}. The input source is expected to
* take care of open and close operations. If necessary it should be registered
* as a step scoped bean to ensure that the lifecycle methods are called.
*
* @author Dave Syer
*/
public class DelegatingItemReader extends AbstractItemReader implements Skippable, InitializingBean {
private ItemReader itemReader;
/**
* Default constructor.
*/
public DelegatingItemReader() {
super();
}
/**
* Convenience constructor for setting mandatory property.
*/
public DelegatingItemReader(ItemReader itemReader) {
this();
this.itemReader = itemReader;
}
public void afterPropertiesSet() throws Exception {
Assert.notNull(itemReader, "ItemReader must not be null.");
}
/**
* Get the next object from the input source.
* @throws Exception
* @see org.springframework.batch.item.ItemReader#read()
*/
public Object read() throws Exception {
return itemReader.read();
}
/**
* Setter for input source.
* @param source
*/
public void setItemReader(ItemReader source) {
this.itemReader = source;
}
public void skip() {
if (itemReader instanceof Skippable) {
((Skippable) itemReader).skip();
}
}
/*
* (non-Javadoc)
* @see org.springframework.batch.item.ItemStream#mark(org.springframework.batch.item.ExecutionContext)
*/
public void mark() {
itemReader.mark();
}
/*
* (non-Javadoc)
* @see org.springframework.batch.item.ItemStream#reset(org.springframework.batch.item.ExecutionContext)
*/
public void reset() {
itemReader.reset();
}
}

View File

@@ -1,55 +1,55 @@
/*
* 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.item.reader;
import org.springframework.batch.item.ExecutionContext;
import org.springframework.batch.item.ItemReader;
import org.springframework.batch.item.exception.MarkFailedException;
import org.springframework.batch.item.exception.ResetFailedException;
import org.springframework.batch.item.exception.StreamException;
import org.springframework.batch.support.AbstractMethodInvokingDelegator;
/**
* Invokes a custom method on a delegate plain old Java object which itself
* provides an item.
*
* @author Robert Kasanicky
*/
public class ItemReaderAdapter extends AbstractMethodInvokingDelegator implements ItemReader {
/**
* @return return value of the target method.
*/
public Object read() throws Exception {
return invokeDelegateMethod();
}
/**
* Do nothing.
*
* @see org.springframework.batch.item.ItemReader#close(ExecutionContext)
*/
public void close() throws StreamException {
}
public void mark() throws MarkFailedException {
}
public void reset() throws ResetFailedException {
}
}
/*
* 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.item.reader;
import org.springframework.batch.item.ExecutionContext;
import org.springframework.batch.item.ItemReader;
import org.springframework.batch.item.exception.MarkFailedException;
import org.springframework.batch.item.exception.ResetFailedException;
import org.springframework.batch.item.exception.StreamException;
import org.springframework.batch.support.AbstractMethodInvokingDelegator;
/**
* Invokes a custom method on a delegate plain old Java object which itself
* provides an item.
*
* @author Robert Kasanicky
*/
public class ItemReaderAdapter extends AbstractMethodInvokingDelegator implements ItemReader {
/**
* @return return value of the target method.
*/
public Object read() throws Exception {
return invokeDelegateMethod();
}
/**
* Do nothing.
*
* @see org.springframework.batch.item.ItemReader#close(ExecutionContext)
*/
public void close() throws StreamException {
}
public void mark() throws MarkFailedException {
}
public void reset() throws ResetFailedException {
}
}

View File

@@ -1,59 +1,59 @@
/*
* 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.item.reader;
import org.springframework.batch.item.validator.Validator;
import org.springframework.util.Assert;
/**
* Simple extension of {@link DelegatingItemReader} that provides for
* validation before returning input.
*
* @author Lucas Ward
*
*/
public class ValidatingItemReader extends DelegatingItemReader {
private Validator validator;
/* (non-Javadoc)
* @see org.springframework.batch.item.reader.DelegatingItemReader#afterPropertiesSet()
*/
public void afterPropertiesSet() throws Exception {
super.afterPropertiesSet();
Assert.notNull(validator, "Validator must not be null.");
}
/* (non-Javadoc)
* @see org.springframework.batch.item.reader.DelegatingItemReader#read()
*/
public Object read() throws Exception {
Object input = super.read();
if(input != null){
validator.validate(input);
}
return input;
}
/**
* Set the validator used to validate each item.
*
* @param validator
*/
public void setValidator(Validator validator) {
this.validator = validator;
}
}
/*
* 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.item.reader;
import org.springframework.batch.item.validator.Validator;
import org.springframework.util.Assert;
/**
* Simple extension of {@link DelegatingItemReader} that provides for
* validation before returning input.
*
* @author Lucas Ward
*
*/
public class ValidatingItemReader extends DelegatingItemReader {
private Validator validator;
/* (non-Javadoc)
* @see org.springframework.batch.item.reader.DelegatingItemReader#afterPropertiesSet()
*/
public void afterPropertiesSet() throws Exception {
super.afterPropertiesSet();
Assert.notNull(validator, "Validator must not be null.");
}
/* (non-Javadoc)
* @see org.springframework.batch.item.reader.DelegatingItemReader#read()
*/
public Object read() throws Exception {
Object input = super.read();
if(input != null){
validator.validate(input);
}
return input;
}
/**
* Set the validator used to validate each item.
*
* @param validator
*/
public void setValidator(Validator validator) {
this.validator = validator;
}
}

View File

@@ -1,30 +1,30 @@
package org.springframework.batch.item.writer;
import java.util.Iterator;
import java.util.List;
import org.springframework.batch.item.ItemWriter;
/**
* Runs a collection of ItemProcessors in fixed-order sequence.
*
* @author Robert Kasanicky
*/
public class CompositeItemWriter extends AbstractItemWriter {
private List delegates;
public void setDelegates(List delegates) {
this.delegates = delegates;
}
/**
* Calls injected ItemProcessors in order.
*/
public void write(Object data) throws Exception {
for (Iterator iterator = delegates.listIterator(); iterator.hasNext();) {
((ItemWriter) iterator.next()).write(data);
}
}
}
package org.springframework.batch.item.writer;
import java.util.Iterator;
import java.util.List;
import org.springframework.batch.item.ItemWriter;
/**
* Runs a collection of ItemProcessors in fixed-order sequence.
*
* @author Robert Kasanicky
*/
public class CompositeItemWriter extends AbstractItemWriter {
private List delegates;
public void setDelegates(List delegates) {
this.delegates = delegates;
}
/**
* Calls injected ItemProcessors in order.
*/
public void write(Object data) throws Exception {
for (Iterator iterator = delegates.listIterator(); iterator.hasNext();) {
((ItemWriter) iterator.next()).write(data);
}
}
}

View File

@@ -1,80 +1,80 @@
package org.springframework.batch.item.writer;
import org.springframework.batch.item.ItemWriter;
import org.springframework.batch.item.exception.ClearFailedException;
import org.springframework.batch.item.exception.FlushFailedException;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.util.Assert;
/**
* Simple wrapper around {@link ItemWriter}.
*
* @author Dave Syer
* @author Robert Kasanicky
*/
public class DelegatingItemWriter implements ItemWriter, InitializingBean {
private ItemWriter writer;
/**
* Default constructor.
*/
public DelegatingItemWriter() {
super();
}
/**
* @param itemWriter
*/
public DelegatingItemWriter(ItemWriter itemWriter) {
this();
this.writer = itemWriter;
}
/**
* Calls {@link #doProcess(Object)} and then writes the result to the
* delegate {@link ItemWriter}.
* @throws Exception
*
* @see ItemWriter#process(java.lang.Object)
*/
public void write(Object item) throws Exception {
Object result = doProcess(item);
writer.write(result);
}
/**
* By default returns the argument. This method is an extension point meant
* to be overridden by subclasses that implement processing logic.
* @throws Exception
*/
protected Object doProcess(Object item) throws Exception {
return item;
}
/**
* Setter for {@link ItemWriter}.
*/
public void setDelegate(ItemWriter writer) {
this.writer = writer;
}
public void afterPropertiesSet() throws Exception {
Assert.notNull(writer);
}
/**
* Delegates to {@link ItemWriter#clear()}
*/
public void clear() throws ClearFailedException {
writer.clear();
}
/**
* Delegates to {@link ItemWriter#flush()}
*/
public void flush() throws FlushFailedException {
writer.flush();
}
}
package org.springframework.batch.item.writer;
import org.springframework.batch.item.ItemWriter;
import org.springframework.batch.item.exception.ClearFailedException;
import org.springframework.batch.item.exception.FlushFailedException;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.util.Assert;
/**
* Simple wrapper around {@link ItemWriter}.
*
* @author Dave Syer
* @author Robert Kasanicky
*/
public class DelegatingItemWriter implements ItemWriter, InitializingBean {
private ItemWriter writer;
/**
* Default constructor.
*/
public DelegatingItemWriter() {
super();
}
/**
* @param itemWriter
*/
public DelegatingItemWriter(ItemWriter itemWriter) {
this();
this.writer = itemWriter;
}
/**
* Calls {@link #doProcess(Object)} and then writes the result to the
* delegate {@link ItemWriter}.
* @throws Exception
*
* @see ItemWriter#process(java.lang.Object)
*/
public void write(Object item) throws Exception {
Object result = doProcess(item);
writer.write(result);
}
/**
* By default returns the argument. This method is an extension point meant
* to be overridden by subclasses that implement processing logic.
* @throws Exception
*/
protected Object doProcess(Object item) throws Exception {
return item;
}
/**
* Setter for {@link ItemWriter}.
*/
public void setDelegate(ItemWriter writer) {
this.writer = writer;
}
public void afterPropertiesSet() throws Exception {
Assert.notNull(writer);
}
/**
* Delegates to {@link ItemWriter#clear()}
*/
public void clear() throws ClearFailedException {
writer.clear();
}
/**
* Delegates to {@link ItemWriter#flush()}
*/
public void flush() throws FlushFailedException {
writer.flush();
}
}

View File

@@ -1,54 +1,54 @@
/*
* 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.item.writer;
import org.springframework.batch.item.ItemWriter;
import org.springframework.batch.item.exception.ClearFailedException;
import org.springframework.batch.item.exception.FlushFailedException;
import org.springframework.batch.support.AbstractMethodInvokingDelegator;
/**
* Delegates item processing to a custom method -
* passes the item as an argument for the delegate method.
*
* @see PropertyExtractingDelegatingItemWriter
*
* @author Robert Kasanicky
*/
public class ItemWriterAdapter extends AbstractMethodInvokingDelegator implements ItemWriter {
public void write(Object item) throws Exception {
invokeDelegateMethodWithArgument(item);
}
/*
* No-op, can't call more than one method.
*
*/
public void clear() throws ClearFailedException {
}
/*
* No-op, can't call more than one method.
*
*/
public void flush() throws FlushFailedException {
}
}
/*
* 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.item.writer;
import org.springframework.batch.item.ItemWriter;
import org.springframework.batch.item.exception.ClearFailedException;
import org.springframework.batch.item.exception.FlushFailedException;
import org.springframework.batch.support.AbstractMethodInvokingDelegator;
/**
* Delegates item processing to a custom method -
* passes the item as an argument for the delegate method.
*
* @see PropertyExtractingDelegatingItemWriter
*
* @author Robert Kasanicky
*/
public class ItemWriterAdapter extends AbstractMethodInvokingDelegator implements ItemWriter {
public void write(Object item) throws Exception {
invokeDelegateMethodWithArgument(item);
}
/*
* No-op, can't call more than one method.
*
*/
public void clear() throws ClearFailedException {
}
/*
* No-op, can't call more than one method.
*
*/
public void flush() throws FlushFailedException {
}
}

View File

@@ -1,77 +1,77 @@
/*
* 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.item.writer;
import org.springframework.batch.item.ItemWriter;
import org.springframework.batch.item.exception.ClearFailedException;
import org.springframework.batch.item.exception.FlushFailedException;
import org.springframework.batch.support.AbstractMethodInvokingDelegator;
import org.springframework.beans.BeanWrapper;
import org.springframework.beans.BeanWrapperImpl;
import org.springframework.util.Assert;
/**
* Delegates processing to a custom method - extracts property values
* from item object and uses them as arguments for the delegate method.
*
* @see ItemWriterAdapter
*
* @author Robert Kasanicky
*/
public class PropertyExtractingDelegatingItemWriter extends AbstractMethodInvokingDelegator implements ItemWriter {
private String[] fieldsUsedAsTargetMethodArguments;
/**
* Extracts values from item's fields named in fieldsUsedAsTargetMethodArguments
* and passes them as arguments to the delegate method.
*/
public void write(Object item) throws Exception {
// helper for extracting property values from a bean
BeanWrapper beanWrapper = new BeanWrapperImpl(item);
Object[] methodArguments = new Object[fieldsUsedAsTargetMethodArguments.length];
for (int i = 0; i < fieldsUsedAsTargetMethodArguments.length; i++) {
methodArguments[i] = beanWrapper.getPropertyValue(fieldsUsedAsTargetMethodArguments[i]);
}
invokeDelegateMethodWithArguments(methodArguments);
}
public void afterPropertiesSet() throws Exception {
super.afterPropertiesSet();
Assert.notEmpty(fieldsUsedAsTargetMethodArguments);
}
/**
* @param fieldsUsedAsTargetMethodArguments the values of the these item's fields
* will be used as arguments for the delegate method. Nested property values are
* supported, e.g. <code>address.city</code>
*/
public void setFieldsUsedAsTargetMethodArguments(String[] fieldsUsedAsMethodArguments) {
this.fieldsUsedAsTargetMethodArguments = fieldsUsedAsMethodArguments;
}
public void clear() throws ClearFailedException {
}
public void flush() throws FlushFailedException {
}
}
/*
* 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.item.writer;
import org.springframework.batch.item.ItemWriter;
import org.springframework.batch.item.exception.ClearFailedException;
import org.springframework.batch.item.exception.FlushFailedException;
import org.springframework.batch.support.AbstractMethodInvokingDelegator;
import org.springframework.beans.BeanWrapper;
import org.springframework.beans.BeanWrapperImpl;
import org.springframework.util.Assert;
/**
* Delegates processing to a custom method - extracts property values
* from item object and uses them as arguments for the delegate method.
*
* @see ItemWriterAdapter
*
* @author Robert Kasanicky
*/
public class PropertyExtractingDelegatingItemWriter extends AbstractMethodInvokingDelegator implements ItemWriter {
private String[] fieldsUsedAsTargetMethodArguments;
/**
* Extracts values from item's fields named in fieldsUsedAsTargetMethodArguments
* and passes them as arguments to the delegate method.
*/
public void write(Object item) throws Exception {
// helper for extracting property values from a bean
BeanWrapper beanWrapper = new BeanWrapperImpl(item);
Object[] methodArguments = new Object[fieldsUsedAsTargetMethodArguments.length];
for (int i = 0; i < fieldsUsedAsTargetMethodArguments.length; i++) {
methodArguments[i] = beanWrapper.getPropertyValue(fieldsUsedAsTargetMethodArguments[i]);
}
invokeDelegateMethodWithArguments(methodArguments);
}
public void afterPropertiesSet() throws Exception {
super.afterPropertiesSet();
Assert.notEmpty(fieldsUsedAsTargetMethodArguments);
}
/**
* @param fieldsUsedAsTargetMethodArguments the values of the these item's fields
* will be used as arguments for the delegate method. Nested property values are
* supported, e.g. <code>address.city</code>
*/
public void setFieldsUsedAsTargetMethodArguments(String[] fieldsUsedAsMethodArguments) {
this.fieldsUsedAsTargetMethodArguments = fieldsUsedAsMethodArguments;
}
public void clear() throws ClearFailedException {
}
public void flush() throws FlushFailedException {
}
}

View File

@@ -1,223 +1,223 @@
/*
* 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.repeat;
import java.io.Serializable;
import org.springframework.util.StringUtils;
/**
* Value object used to carry information about the status of a
* {@link RepeatOperations}.
*
* @author Dave Syer
*
*/
public class ExitStatus implements Serializable {
/**
* Convenient constant value for when we detect that processing is underway.
* Used mainly by asynchronous launchers.
*/
public static final ExitStatus RUNNING = new ExitStatus(true, "RUNNING");
/**
* Convenient constant value representing unknown state - assumed
* continuable.
*/
public static final ExitStatus UNKNOWN = new ExitStatus(true, "UNKNOWN");
/**
* Convenient constant value representing unfinished processing.
*/
public static final ExitStatus CONTINUABLE = new ExitStatus(true, "CONTINUABLE");
/**
* Convenient constant value representing finished processing.
*/
public static final ExitStatus FINISHED = new ExitStatus(false, "COMPLETED");
/**
* Convenient constant value representing interrupted processing.
*/
public static final ExitStatus INTERRUPTED = new ExitStatus(false, "INTERRUPTED");
/**
* Convenient constant value representing job that did no processing (e.g.
* because it was already complete).
*/
public static final ExitStatus NOOP = new ExitStatus(false, "NOOP");
/**
* Convenient constant value representing finished processing with an error.
*/
public static final ExitStatus FAILED = new ExitStatus(false, "FAILED");
private final boolean continuable;
private final String exitCode;
private final String exitDescription;
public ExitStatus(boolean continuable) {
this(continuable, "", "");
}
public ExitStatus(boolean continuable, String exitCode) {
this(continuable, exitCode, "");
}
public ExitStatus(boolean continuable, String exitCode, String exitDescription) {
super();
this.continuable = continuable;
this.exitCode = exitCode;
this.exitDescription = exitDescription;
}
/**
* Flag to signal that processing can continue. This is distinct from any
* flag that might indicate that a batch is complete, or terminated, since a
* batch might be only a small part of a larger whole, which is still not
* finished.
*
* @return true if processing can continue.
*/
public boolean isContinuable() {
return continuable;
}
/**
* Getter for the exit code (defaults to blank).
*
* @return the exit code.
*/
public String getExitCode() {
return exitCode;
}
/**
* Getter for the exit description (defaults to blank)
*
* @return
*/
public String getExitDescription() {
return exitDescription;
}
/**
* Create a new {@link ExitStatus} with a logical combination of the
* continuable flag.
*
* @param continuable true if the caller thinks it is safe to continue.
* @return a new {@link ExitStatus} with {@link #isContinuable()} the
* logical and of the current value and the argument provided.
*/
public ExitStatus and(boolean continuable) {
return new ExitStatus(this.continuable && continuable, this.exitCode, this.exitDescription);
}
/**
* Create a new {@link ExitStatus} with a logical combination of the
* continuable flag, and a concatenation of the descriptions. The exit code
* is only replaced if the result is continuable or the input is not
* continuable.<br/>
*
* If the input is null just return this.
*
* @param status an {@link ExitStatus} to combine with this one.
* @return a new {@link ExitStatus} with {@link #isContinuable()} the
* logical and of the current value and the argument provided.
*/
public ExitStatus and(ExitStatus status) {
if (status == null) {
return this;
}
ExitStatus result = and(status.continuable).addExitDescription(status.exitDescription);
if (result.continuable || !status.continuable) {
result = result.replaceExitCode(status.exitCode);
}
return result;
}
/*
* (non-Javadoc)
*
* @see java.lang.Object#toString()
*/
public String toString() {
return "continuable=" + continuable + ";exitCode=" + exitCode + ";exitDescription=" + exitDescription;
}
/**
* Compare the fields one by one.
*
* @see java.lang.Object#equals(java.lang.Object)
*/
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
return toString().equals(obj.toString());
}
/**
* Compatible with the equals implementation.
*
* @see java.lang.Object#hashCode()
*/
public int hashCode() {
return toString().hashCode();
}
/**
* Add an exit code to an existing {@link ExitStatus}. If there is already
* a code present tit will be replaced.
*
* @param code the code to add
* @return a new {@link ExitStatus} with the same properties but a new exit
* code.
*/
public ExitStatus replaceExitCode(String code) {
return new ExitStatus(continuable, code, exitDescription);
}
/**
* Check if this status represents a running process.
*
* @return true if the exit code is "RUNNING" or "UNKNOWN"
*/
public boolean isRunning() {
return "RUNNING".equals(this.exitCode) || "UNKNOWN".equals(this.exitCode);
}
/**
* Add an exit description to an existing {@link ExitStatus}. If there is
* already a description present the two will be concatenated with a
* semicolon.
*
* @param description the description to add
* @return a new {@link ExitStatus} with the same properties but a new exit
* description
*/
public ExitStatus addExitDescription(String description) {
if (StringUtils.hasText(exitDescription) && StringUtils.hasText(description)
&& !exitDescription.equals(description)) {
description = exitDescription + "; " + description;
}
return new ExitStatus(continuable, exitCode, description);
}
}
/*
* 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.repeat;
import java.io.Serializable;
import org.springframework.util.StringUtils;
/**
* Value object used to carry information about the status of a
* {@link RepeatOperations}.
*
* @author Dave Syer
*
*/
public class ExitStatus implements Serializable {
/**
* Convenient constant value for when we detect that processing is underway.
* Used mainly by asynchronous launchers.
*/
public static final ExitStatus RUNNING = new ExitStatus(true, "RUNNING");
/**
* Convenient constant value representing unknown state - assumed
* continuable.
*/
public static final ExitStatus UNKNOWN = new ExitStatus(true, "UNKNOWN");
/**
* Convenient constant value representing unfinished processing.
*/
public static final ExitStatus CONTINUABLE = new ExitStatus(true, "CONTINUABLE");
/**
* Convenient constant value representing finished processing.
*/
public static final ExitStatus FINISHED = new ExitStatus(false, "COMPLETED");
/**
* Convenient constant value representing interrupted processing.
*/
public static final ExitStatus INTERRUPTED = new ExitStatus(false, "INTERRUPTED");
/**
* Convenient constant value representing job that did no processing (e.g.
* because it was already complete).
*/
public static final ExitStatus NOOP = new ExitStatus(false, "NOOP");
/**
* Convenient constant value representing finished processing with an error.
*/
public static final ExitStatus FAILED = new ExitStatus(false, "FAILED");
private final boolean continuable;
private final String exitCode;
private final String exitDescription;
public ExitStatus(boolean continuable) {
this(continuable, "", "");
}
public ExitStatus(boolean continuable, String exitCode) {
this(continuable, exitCode, "");
}
public ExitStatus(boolean continuable, String exitCode, String exitDescription) {
super();
this.continuable = continuable;
this.exitCode = exitCode;
this.exitDescription = exitDescription;
}
/**
* Flag to signal that processing can continue. This is distinct from any
* flag that might indicate that a batch is complete, or terminated, since a
* batch might be only a small part of a larger whole, which is still not
* finished.
*
* @return true if processing can continue.
*/
public boolean isContinuable() {
return continuable;
}
/**
* Getter for the exit code (defaults to blank).
*
* @return the exit code.
*/
public String getExitCode() {
return exitCode;
}
/**
* Getter for the exit description (defaults to blank)
*
* @return
*/
public String getExitDescription() {
return exitDescription;
}
/**
* Create a new {@link ExitStatus} with a logical combination of the
* continuable flag.
*
* @param continuable true if the caller thinks it is safe to continue.
* @return a new {@link ExitStatus} with {@link #isContinuable()} the
* logical and of the current value and the argument provided.
*/
public ExitStatus and(boolean continuable) {
return new ExitStatus(this.continuable && continuable, this.exitCode, this.exitDescription);
}
/**
* Create a new {@link ExitStatus} with a logical combination of the
* continuable flag, and a concatenation of the descriptions. The exit code
* is only replaced if the result is continuable or the input is not
* continuable.<br/>
*
* If the input is null just return this.
*
* @param status an {@link ExitStatus} to combine with this one.
* @return a new {@link ExitStatus} with {@link #isContinuable()} the
* logical and of the current value and the argument provided.
*/
public ExitStatus and(ExitStatus status) {
if (status == null) {
return this;
}
ExitStatus result = and(status.continuable).addExitDescription(status.exitDescription);
if (result.continuable || !status.continuable) {
result = result.replaceExitCode(status.exitCode);
}
return result;
}
/*
* (non-Javadoc)
*
* @see java.lang.Object#toString()
*/
public String toString() {
return "continuable=" + continuable + ";exitCode=" + exitCode + ";exitDescription=" + exitDescription;
}
/**
* Compare the fields one by one.
*
* @see java.lang.Object#equals(java.lang.Object)
*/
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
return toString().equals(obj.toString());
}
/**
* Compatible with the equals implementation.
*
* @see java.lang.Object#hashCode()
*/
public int hashCode() {
return toString().hashCode();
}
/**
* Add an exit code to an existing {@link ExitStatus}. If there is already
* a code present tit will be replaced.
*
* @param code the code to add
* @return a new {@link ExitStatus} with the same properties but a new exit
* code.
*/
public ExitStatus replaceExitCode(String code) {
return new ExitStatus(continuable, code, exitDescription);
}
/**
* Check if this status represents a running process.
*
* @return true if the exit code is "RUNNING" or "UNKNOWN"
*/
public boolean isRunning() {
return "RUNNING".equals(this.exitCode) || "UNKNOWN".equals(this.exitCode);
}
/**
* Add an exit description to an existing {@link ExitStatus}. If there is
* already a description present the two will be concatenated with a
* semicolon.
*
* @param description the description to add
* @return a new {@link ExitStatus} with the same properties but a new exit
* description
*/
public ExitStatus addExitDescription(String description) {
if (StringUtils.hasText(exitDescription) && StringUtils.hasText(description)
&& !exitDescription.equals(description)) {
description = exitDescription + "; " + description;
}
return new ExitStatus(continuable, exitCode, description);
}
}

View File

@@ -1,175 +1,175 @@
/*
* 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.support;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import org.springframework.batch.io.exception.DynamicMethodInvocationException;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.util.Assert;
import org.springframework.util.MethodInvoker;
/**
* Superclass for delegating classes which dynamically call a
* custom method of injected object.
* Provides convenient API for dynamic method invocation shielding
* subclasses from low-level details and exception handling.
*
* @author Robert Kasanicky
*/
public class AbstractMethodInvokingDelegator implements InitializingBean {
private Object targetObject;
private String targetMethod;
private Object[] arguments;
/**
* Invoker the target method with no arguments.
* @return object returned by invoked method
* @throws DynamicMethodInvocationException if the {@link MethodInvoker} used throws exception
*/
protected Object invokeDelegateMethod() {
MethodInvoker invoker = createMethodInvoker(targetObject, targetMethod);
invoker.setArguments(arguments);
return doInvoke(invoker);
}
/**
* Invokes the target method with given argument.
* @param object argument for the target method
* @return object returned by target method
* @throws DynamicMethodInvocationException if the {@link MethodInvoker} used throws exception
*/
protected Object invokeDelegateMethodWithArgument(Object object) {
MethodInvoker invoker = createMethodInvoker(targetObject, targetMethod);
invoker.setArguments(new Object[]{object});
return doInvoke(invoker);
}
/**
* Invokes the target method with given arguments.
* @param args arguments for the invoked method
* @return object returned by invoked method
* @throws DynamicMethodInvocationException if the {@link MethodInvoker} used throws exception
*/
protected Object invokeDelegateMethodWithArguments(Object[] args) {
MethodInvoker invoker = createMethodInvoker(targetObject, targetMethod);
invoker.setArguments(args);
return doInvoke(invoker);
}
/**
* Create a new configured instance of {@link MethodInvoker}.
*/
private MethodInvoker createMethodInvoker(Object targetObject, String targetMethod) {
MethodInvoker invoker = new MethodInvoker();
invoker.setTargetObject(targetObject);
invoker.setTargetMethod(targetMethod);
invoker.setArguments(arguments);
return invoker;
}
/**
* Prepare and invoke the invoker, rethrow checked exceptions as unchecked.
* @param invoker configured invoker
* @return return value of the invoked method
*/
private Object doInvoke(MethodInvoker invoker) {
try {
invoker.prepare();
}
catch (ClassNotFoundException e) {
throw new DynamicMethodInvocationException(e);
}
catch (NoSuchMethodException e) {
throw new DynamicMethodInvocationException(e);
}
try {
return invoker.invoke();
}
catch (InvocationTargetException e) {
throw new DynamicMethodInvocationException(e);
}
catch (IllegalAccessException e) {
throw new DynamicMethodInvocationException(e);
}
}
public void afterPropertiesSet() throws Exception {
Assert.notNull(targetObject);
Assert.hasLength(targetMethod);
Assert.state(targetClassDeclaresTargetMethod(),
"target class must declare a method with name matching the target method");
}
/**
* @return true if target class declares a method matching target method name
* with given number of arguments of appropriate type.
*/
private boolean targetClassDeclaresTargetMethod() {
MethodInvoker invoker = createMethodInvoker(targetObject, targetMethod);
Method[] methods = invoker.getTargetClass().getDeclaredMethods();
String targetMethodName = invoker.getTargetMethod();
for (int i=0; i < methods.length; i++) {
if (methods[i].getName().equals(targetMethodName)) {
Class[] params = methods[i].getParameterTypes();
if (arguments == null) {
return true;
} else if (arguments.length == params.length) {
boolean argumentsMatchParameters = true;
for (int j = 0; j < params.length; j++) {
if (!(params[j].isAssignableFrom(arguments[j].getClass()))) {
argumentsMatchParameters = false;
}
}
if (argumentsMatchParameters) return true;
}
}
}
return false;
}
/**
* @param targetObject the delegate - bean id can be used to set this value in Spring configuration
*/
public void setTargetObject(Object targetObject) {
this.targetObject = targetObject;
}
/**
* @param targetMethod name of the method to be invoked on {@link #targetObject}.
*/
public void setTargetMethod(String targetMethod) {
this.targetMethod = targetMethod;
}
/**
* @param arguments arguments values for the {{@link #targetMethod}.
* These are not expected to change during the lifetime of the delegator
* and will be used only when the subclass tries to invoke the target method
* without providing explicit argument values.
*/
public void setArguments(Object[] arguments) {
this.arguments = arguments;
}
}
/*
* 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.support;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import org.springframework.batch.io.exception.DynamicMethodInvocationException;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.util.Assert;
import org.springframework.util.MethodInvoker;
/**
* Superclass for delegating classes which dynamically call a
* custom method of injected object.
* Provides convenient API for dynamic method invocation shielding
* subclasses from low-level details and exception handling.
*
* @author Robert Kasanicky
*/
public class AbstractMethodInvokingDelegator implements InitializingBean {
private Object targetObject;
private String targetMethod;
private Object[] arguments;
/**
* Invoker the target method with no arguments.
* @return object returned by invoked method
* @throws DynamicMethodInvocationException if the {@link MethodInvoker} used throws exception
*/
protected Object invokeDelegateMethod() {
MethodInvoker invoker = createMethodInvoker(targetObject, targetMethod);
invoker.setArguments(arguments);
return doInvoke(invoker);
}
/**
* Invokes the target method with given argument.
* @param object argument for the target method
* @return object returned by target method
* @throws DynamicMethodInvocationException if the {@link MethodInvoker} used throws exception
*/
protected Object invokeDelegateMethodWithArgument(Object object) {
MethodInvoker invoker = createMethodInvoker(targetObject, targetMethod);
invoker.setArguments(new Object[]{object});
return doInvoke(invoker);
}
/**
* Invokes the target method with given arguments.
* @param args arguments for the invoked method
* @return object returned by invoked method
* @throws DynamicMethodInvocationException if the {@link MethodInvoker} used throws exception
*/
protected Object invokeDelegateMethodWithArguments(Object[] args) {
MethodInvoker invoker = createMethodInvoker(targetObject, targetMethod);
invoker.setArguments(args);
return doInvoke(invoker);
}
/**
* Create a new configured instance of {@link MethodInvoker}.
*/
private MethodInvoker createMethodInvoker(Object targetObject, String targetMethod) {
MethodInvoker invoker = new MethodInvoker();
invoker.setTargetObject(targetObject);
invoker.setTargetMethod(targetMethod);
invoker.setArguments(arguments);
return invoker;
}
/**
* Prepare and invoke the invoker, rethrow checked exceptions as unchecked.
* @param invoker configured invoker
* @return return value of the invoked method
*/
private Object doInvoke(MethodInvoker invoker) {
try {
invoker.prepare();
}
catch (ClassNotFoundException e) {
throw new DynamicMethodInvocationException(e);
}
catch (NoSuchMethodException e) {
throw new DynamicMethodInvocationException(e);
}
try {
return invoker.invoke();
}
catch (InvocationTargetException e) {
throw new DynamicMethodInvocationException(e);
}
catch (IllegalAccessException e) {
throw new DynamicMethodInvocationException(e);
}
}
public void afterPropertiesSet() throws Exception {
Assert.notNull(targetObject);
Assert.hasLength(targetMethod);
Assert.state(targetClassDeclaresTargetMethod(),
"target class must declare a method with name matching the target method");
}
/**
* @return true if target class declares a method matching target method name
* with given number of arguments of appropriate type.
*/
private boolean targetClassDeclaresTargetMethod() {
MethodInvoker invoker = createMethodInvoker(targetObject, targetMethod);
Method[] methods = invoker.getTargetClass().getDeclaredMethods();
String targetMethodName = invoker.getTargetMethod();
for (int i=0; i < methods.length; i++) {
if (methods[i].getName().equals(targetMethodName)) {
Class[] params = methods[i].getParameterTypes();
if (arguments == null) {
return true;
} else if (arguments.length == params.length) {
boolean argumentsMatchParameters = true;
for (int j = 0; j < params.length; j++) {
if (!(params[j].isAssignableFrom(arguments[j].getClass()))) {
argumentsMatchParameters = false;
}
}
if (argumentsMatchParameters) return true;
}
}
}
return false;
}
/**
* @param targetObject the delegate - bean id can be used to set this value in Spring configuration
*/
public void setTargetObject(Object targetObject) {
this.targetObject = targetObject;
}
/**
* @param targetMethod name of the method to be invoked on {@link #targetObject}.
*/
public void setTargetMethod(String targetMethod) {
this.targetMethod = targetMethod;
}
/**
* @param arguments arguments values for the {{@link #targetMethod}.
* These are not expected to change during the lifetime of the delegator
* and will be used only when the subclass tries to invoke the target method
* without providing explicit argument values.
*/
public void setArguments(Object[] arguments) {
this.arguments = arguments;
}
}