Change svn:eol-style to LF
This commit is contained in:
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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 });
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 <1,Integer.MAX_VALUE-1> 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 <1,Integer.MAX_VALUE-1> 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");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 <1, Integer.MAX_VALUE-1>
|
||||
* <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 <1, Integer.MAX_VALUE-1>
|
||||
* <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] + ")");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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() + "]");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user