Improve RowSet abstraction.

Extracted a ColumnNameExtractor to be able to remove
some logic from the sheet.

Increased tests for reading excel files including mapping to
objects.
This commit is contained in:
Marten Deinum
2014-09-17 11:58:08 +02:00
committed by Michael Minella
parent 5a4aa5d533
commit f05a429c16
41 changed files with 1209 additions and 713 deletions

View File

@@ -17,13 +17,13 @@ package org.springframework.batch.item.excel;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.batch.item.excel.support.rowset.*;
import org.springframework.batch.item.file.ResourceAwareItemReaderItemStream;
import org.springframework.batch.item.support.AbstractItemCountingItemStreamItemReader;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.core.io.Resource;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.ObjectUtils;
import java.io.IOException;
import java.io.InputStream;
@@ -49,7 +49,8 @@ public abstract class AbstractExcelItemReader<T> extends AbstractItemCountingIte
private RowCallbackHandler skippedRowsCallback;
private boolean noInput = false;
private boolean strict = true;
private RowSet rs;
private RowSetFactory rowSetFactory = new DefaultRowSetFactory();
private RowSet rs;
public AbstractExcelItemReader() {
super();
@@ -62,25 +63,25 @@ public abstract class AbstractExcelItemReader<T> extends AbstractItemCountingIte
return null;
}
if (rs.next()) {
try {
return this.rowMapper.mapRow(rs);
} catch (final Exception e) {
throw new ExcelFileParseException("Exception parsing Excel file.", e, this.resource.getDescription(),
rs.getMetaData().getSheetName(), rs.getCurrentRowIndex(), rs.getCurrentRow());
}
} else {
this.currentSheet++;
if (this.currentSheet >= this.getNumberOfSheets()) {
if (logger.isDebugEnabled() ) {
logger.debug("No more sheets in '" + this.resource.getDescription() + "'.");
}
return null;
} else {
this.openSheet();
return this.doRead();
}
}
if (rs.next()) {
try {
return this.rowMapper.mapRow(rs);
} catch (final Exception e) {
throw new ExcelFileParseException("Exception parsing Excel file.", e, this.resource.getDescription(),
rs.getMetaData().getSheetName(), rs.getCurrentRowIndex(), rs.getCurrentRow());
}
} else {
this.currentSheet++;
if (this.currentSheet >= this.getNumberOfSheets()) {
if (logger.isDebugEnabled()) {
logger.debug("No more sheets in '" + this.resource.getDescription() + "'.");
}
return null;
} else {
this.openSheet();
return this.doRead();
}
}
}
@Override
@@ -92,7 +93,7 @@ public abstract class AbstractExcelItemReader<T> extends AbstractItemCountingIte
throw new IllegalStateException("Input resource must exist (reader is in 'strict' mode): "
+ this.resource);
}
logger.warn("Input resource does not exist '"+this.resource.getDescription()+"'.");
logger.warn("Input resource does not exist '" + this.resource.getDescription() + "'.");
return;
}
@@ -101,7 +102,7 @@ public abstract class AbstractExcelItemReader<T> extends AbstractItemCountingIte
throw new IllegalStateException("Input resource must be readable (reader is in 'strict' mode): "
+ this.resource);
}
logger.warn("Input resource is not readable '"+this.resource.getDescription()+"'.");
logger.warn("Input resource is not readable '" + this.resource.getDescription() + "'.");
return;
}
@@ -109,7 +110,7 @@ public abstract class AbstractExcelItemReader<T> extends AbstractItemCountingIte
this.openSheet();
this.noInput = false;
if (logger.isDebugEnabled()) {
logger.debug("Opened workbook ["+this.resource.getFilename()+"] with "+this.getNumberOfSheets()+" sheets.");
logger.debug("Opened workbook [" + this.resource.getFilename() + "] with " + this.getNumberOfSheets() + " sheets.");
}
}
@@ -123,19 +124,20 @@ public abstract class AbstractExcelItemReader<T> extends AbstractItemCountingIte
private void openSheet() {
final Sheet sheet = this.getSheet(this.currentSheet);
this.rs = new RowSet(sheet);
this.rs =rowSetFactory.create(sheet);
if (logger.isDebugEnabled()) {
logger.debug("Opening sheet "+sheet.getName()+".");
if (logger.isDebugEnabled()) {
logger.debug("Opening sheet " + sheet.getName() + ".");
}
for (int i = 0; i < this.linesToSkip; i++) {
for (int i = 0; i < this.linesToSkip; i++) {
if (rs.next() && this.skippedRowsCallback != null) {
this.skippedRowsCallback.handleRow(rs);
}
}
if (logger.isDebugEnabled()) {
logger.debug("Openend sheet "+sheet.getName()+", with "+sheet.getNumberOfRows()+" rows.");
logger.debug("Openend sheet " + sheet.getName() + ", with " + sheet.getNumberOfRows() + " rows.");
}
}
@@ -199,6 +201,10 @@ public abstract class AbstractExcelItemReader<T> extends AbstractItemCountingIte
this.rowMapper = rowMapper;
}
public void setRowSetFactory(RowSetFactory rowSetFactory) {
this.rowSetFactory = rowSetFactory;
}
public void setSkippedRowsCallback(final RowCallbackHandler skippedRowsCallback) {
this.skippedRowsCallback = skippedRowsCallback;
}

View File

@@ -19,9 +19,9 @@ import org.springframework.batch.item.ParseException;
/**
* Exception thrown when parsing excel files. The name of the sheet, the row number on that sheet and the
* name of the excel file can be passed in so that in exception handling we can reuse it. This class only has
* name of the excel file can be passed in so that in exception handling we can reuse it. This class only has
* simply dependencies to make it is generic as possible.
*
*
* @author Marten Deinum
* @since 0.5.0
*/
@@ -34,16 +34,16 @@ public class ExcelFileParseException extends ParseException {
/**
* Construct an {@link ExcelFileParseException}.
*
* @param message the message
* @param cause the root cause
* @param filename the name of the excel file
* @param sheet the name of the sheet
*
* @param message the message
* @param cause the root cause
* @param filename the name of the excel file
* @param sheet the name of the sheet
* @param rowNumber the row number in the current sheet
* @param row the row data as text
* @param row the row data as text
*/
public ExcelFileParseException(final String message, final Throwable cause, final String filename,
final String sheet, final int rowNumber, final String[] row) {
final String sheet, final int rowNumber, final String[] row) {
super(message, cause);
this.filename = filename;
this.sheet = sheet;

View File

@@ -16,9 +16,11 @@
package org.springframework.batch.item.excel;
import org.springframework.batch.item.excel.support.rowset.RowSet;
/**
* Callback to handle skipped lines. Useful for header/footer processing.
*
*
* @author Marten Deinum
* @since 0.5.0
*/

View File

@@ -15,21 +15,22 @@
*/
package org.springframework.batch.item.excel;
import org.springframework.batch.item.excel.support.rowset.RowSet;
/**
* Map rows from an excel sheet to an object.
*
* @author Marten Deinum
* @since 0.5.0
*
* @param <T>
* @author Marten Deinum
* @since 0.5.0
*/
public interface RowMapper<T> {
/**
* Implementations must implement this method to map the provided row to
* Implementations must implement this method to map the provided row to
* the parameter type T. The row number represents the number of rows
* into a {@link Sheet} the current line resides.
*
*
* @param rs the RowSet used for mapping.
* @return mapped object of type T
* @throws Exception if error occured while parsing.

View File

@@ -1,87 +0,0 @@
package org.springframework.batch.item.excel;
import java.util.Properties;
/**
* @author Marten Deinum
*/
public class RowSet {
private final Sheet sheet;
private final RowSetMetaData metaData;
private int currentRowIndex = -1;
private String[] currentRow;
public RowSet(Sheet sheet) {
this.sheet=sheet;
this.metaData = new RowSetMetaData(sheet);
}
public RowSetMetaData getMetaData() {
return metaData;
}
public boolean next() {
currentRow = null;
currentRowIndex++;
if (currentRowIndex <= sheet.getNumberOfRows()) {
currentRow = sheet.getRow(currentRowIndex);
return true;
}
return false;
}
/**
* The current row index represents the number of rows
* into a {@link Sheet} the current line resides
*/
public int getCurrentRowIndex() {
return this.currentRowIndex;
}
/**
* Get the data of the current row.
*
* @return a String[] for the current data
*/
public String[] getCurrentRow() {
return this.currentRow;
}
/**
* Get the value of the given column
*
* @param idx index of the column to get, 0 based
* @return the value
* @throws java.lang.ArrayIndexOutOfBoundsException
*/
public String getColumnValue(int idx) {
return currentRow[idx];
}
/**
* Construct name-value pairs from the column names and string values. Null
* values are omitted.
*
* @return some properties representing the row set.
*
* @throws IllegalStateException if the column name meta data is not
* available.
*/
public Properties getProperties() {
final String[] names = metaData.getColumnNames();
if (names == null) {
throw new IllegalStateException("Cannot create properties without meta data");
}
Properties props = new Properties();
for (int i = 0; i < currentRow.length; i++) {
String value = currentRow[i];
if (value != null) {
props.setProperty(names[i], value);
}
}
return props;
}
}

View File

@@ -1,31 +0,0 @@
package org.springframework.batch.item.excel;
/**
* Created by in329dei on 3-9-2014.
*/
public class RowSetMetaData {
private final Sheet sheet;
RowSetMetaData(Sheet sheet) {
this.sheet = sheet;
}
public String[] getColumnNames() {
return sheet.getHeader();
}
public String getColumnName(int idx) {
String[] names = getColumnNames();
return names[idx];
}
public int getColumnCount() {
return sheet.getNumberOfColumns();
}
public String getSheetName() {
return sheet.getName();
}
}

View File

@@ -17,46 +17,38 @@
package org.springframework.batch.item.excel;
/**
* Interface to wrap different Excel implementations like JExcel, JXL or Apache POI.
*
* Interface to wrap different Excel implementations like JExcel, JXL or Apache POI.
*
* @author Marten Deinum
* @since 0.5.0
*
*/
public interface Sheet {
/**
* Get the number of rows in this sheet.
*
*
* @return the number of rows.
*/
int getNumberOfRows();
/**
* Get the name of the sheet.
*
*
* @return the name of the sheet.
*/
String getName();
/**
* Get the row as a String[]. Returns null if the row doesn't exist.
*
*
* @param rowNumber the row number to read.
* @return a String[] or null
*/
String[] getRow(int rowNumber);
/**
* Gets the first row of the sheet and use it as header.
*
* @return String[] of row 0
*/
String[] getHeader();
/**
* The number of columns in this sheet.
*
*
* @return number of columns
*/
int getNumberOfColumns();

View File

@@ -28,11 +28,10 @@ import org.springframework.util.ClassUtils;
* {@link org.springframework.batch.item.ItemReader} implementation which uses the JExcelApi to read an Excel
* file. It will read the file sheet for sheet and row for row. It is based on
* the {@link org.springframework.batch.item.file.FlatFileItemReader}
*
* @author Marten Deinum
* @since 0.5.0
*
* @param <T> the type
* @author Marten Deinum
* @since 0.5.0
*/
public class JxlItemReader<T> extends AbstractExcelItemReader<T> {

View File

@@ -22,7 +22,7 @@ import org.springframework.batch.item.excel.Sheet;
/**
* {@link org.springframework.batch.item.excel.Sheet} implementation for JXL.
*
*
* @author Marten Deinum
* @since 0.5.0
*/
@@ -32,7 +32,7 @@ public class JxlSheet implements Sheet {
/**
* Constructor which takes the delegate sheet.
*
*
* @param delegate the JXL sheet
*/
JxlSheet(final jxl.Sheet delegate) {
@@ -48,25 +48,17 @@ public class JxlSheet implements Sheet {
return this.delegate.getRows();
}
/**
* {@inheritDoc}
*/
@Override
public String[] getHeader() {
return this.getRow(0);
}
/**
* {@inheritDoc}
*/
@Override
public String[] getRow(final int rowNumber) {
if (rowNumber < getNumberOfRows()) {
final Cell[] row = this.delegate.getRow(rowNumber);
return JxlUtils.extractContents(row);
} else {
return null;
}
if (rowNumber < getNumberOfRows()) {
final Cell[] row = this.delegate.getRow(rowNumber);
return JxlUtils.extractContents(row);
} else {
return null;
}
}
/**

View File

@@ -32,13 +32,15 @@ import java.util.List;
*/
public final class JxlUtils {
/** Private constructor to prevent easy instantiation. */
/**
* Private constructor to prevent easy instantiation.
*/
private JxlUtils() {
}
/**
* Checks if the given cell is emtpy. The cell is empty if it contains no characters, it will trim spaces.
*
* @param cell to check
* @return true/false
* @see org.springframework.util.StringUtils#hasText(String)
@@ -68,7 +70,7 @@ public final class JxlUtils {
/**
* Check if the given workbook has any sheets.
*
*
* @param workbook to check
* @return true/false
*/
@@ -77,8 +79,8 @@ public final class JxlUtils {
}
/**
* Extract the content from the given row.
*
* Extract the content from the given row.
*
* @param row the row
* @return the content as String[]
*/

View File

@@ -0,0 +1,323 @@
/*
* Copyright 2006-2014 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.excel.mapping;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.batch.item.excel.RowMapper;
import org.springframework.batch.item.excel.support.rowset.RowSet;
import org.springframework.batch.item.excel.support.rowset.RowSetMetaData;
import org.springframework.beans.*;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.util.Assert;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;
import java.beans.PropertyDescriptor;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
/**
* {@link RowMapper} implementation that converts a row into a new instance
* of the specified mapped target class. The mapped target class must be a
* top-level class and it must have a default or no-arg constructor.
*
* <p>Column values are mapped based on matching the column name as obtained from row set
* metadata to public setters for the corresponding properties. The names are matched either
* directly or by transforming a name separating the parts with underscores to the same name
* using "camel" case.
*
* <p>Mapping is provided for fields in the target class for many common types, e.g.:
* String, boolean, Boolean, byte, Byte, short, Short, int, Integer, long, Long,
* float, Float, double, Double, BigDecimal, {@code java.util.Date}, etc.
*
* <p>For 'null' values read from the Excel document, we will attempt to call the setter, but in the case of
* Java primitives, this causes a TypeMismatchException. This class can be configured (using the
* primitivesDefaultedForNullValue property) to trap this exception and use the primitives default value.
* Be aware that if you use the values from the generated bean to update the database the primitive value
* will have been set to the primitive's default value instead of null.
*
* <p>Please note that this class is designed to provide convenience rather than high performance.
* For best performance, consider using a custom {@link RowMapper} implementation.
*
* @author Marten Deinum
* @since 0.5.0
*/
public class BeanPropertyRowMapper<T> implements RowMapper<T>, BeanFactoryAware, InitializingBean {
/**
* Logger available to subclasses
*/
protected final Log logger = LogFactory.getLog(getClass());
/**
* The class we are mapping to
*/
private Class<T> type;
/**
* The name of the bean we are mapping to
*/
private String name;
/**
* Whether we're strictly validating
*/
private boolean checkFullyPopulated = false;
/**
* Whether we're defaulting primitives when mapping a null value
*/
private boolean primitivesDefaultedForNullValue = false;
/**
* Map of the fields we provide mapping for
*/
private Map<String, PropertyDescriptor> mappedFields;
/**
* Set of bean properties we provide mapping for
*/
private Set<String> mappedProperties;
private BeanFactory beanFactory;
/**
* Create a new BeanPropertyRowMapper for bean-style configuration.
*
* @see #setTargetType
* @see #setPrototypeBeanName
* @see #setCheckFullyPopulated
*/
public BeanPropertyRowMapper() {
}
/**
* The bean name (id) for an object that can be populated from the field set
* that will be passed into {@link #mapRow(RowSet)}. Typically a
* prototype scoped bean so that a new instance is returned for each field
* set mapped.
* <p/>
* Either this property or the type property must be specified, but not
* both.
*
* @param name the name of a prototype bean in the enclosing BeanFactory
*/
public void setPrototypeBeanName(String name) {
this.name = name;
}
/**
* Public setter for the type of bean to create instead of using a prototype
* bean. An object of this type will be created from its default constructor
* for every call to {@link #mapRow(RowSet)}.<br>
* <p/>
* Either this property or the prototype bean name must be specified, but
* not both.
*
* @param type the type to set
*/
public void setTargetType(Class<T> type) {
this.type = type;
}
/**
* Initialize the mapping metadata for the given class.
*
* @param mappedClass the mapped class.
*/
protected void initialize(Class<T> mappedClass) {
this.mappedFields = new HashMap<String, PropertyDescriptor>();
this.mappedProperties = new HashSet<String>();
PropertyDescriptor[] pds = BeanUtils.getPropertyDescriptors(mappedClass);
for (PropertyDescriptor pd : pds) {
if (pd.getWriteMethod() != null) {
this.mappedFields.put(pd.getName().toLowerCase(), pd);
String underscoredName = underscoreName(pd.getName());
if (!pd.getName().toLowerCase().equals(underscoredName)) {
this.mappedFields.put(underscoredName, pd);
}
this.mappedProperties.add(pd.getName());
}
}
}
/**
* Convert a name in camelCase to an underscored name in lower case.
* Any upper case letters are converted to lower case with a preceding underscore.
*
* @param name the string containing original name
* @return the converted name
*/
private String underscoreName(String name) {
if (!StringUtils.hasLength(name)) {
return "";
}
StringBuilder result = new StringBuilder();
result.append(name.substring(0, 1).toLowerCase());
for (int i = 1; i < name.length(); i++) {
String s = name.substring(i, i + 1);
String slc = s.toLowerCase();
if (!s.equals(slc)) {
result.append("_").append(slc);
} else {
result.append(s);
}
}
return result.toString();
}
/**
* Set whether we're strictly validating that all bean properties have been
* mapped from corresponding database fields.
* <p>Default is {@code false}, accepting unpopulated properties in the
* target bean.
*/
public void setCheckFullyPopulated(boolean checkFullyPopulated) {
this.checkFullyPopulated = checkFullyPopulated;
}
/**
* Return whether we're strictly validating that all bean properties have been
* mapped from corresponding database fields.
*/
public boolean isCheckFullyPopulated() {
return this.checkFullyPopulated;
}
/**
* Set whether we're defaulting Java primitives in the case of mapping a null value
* from corresponding database fields.
* <p>Default is {@code false}, throwing an exception when nulls are mapped to Java primitives.
*/
public void setPrimitivesDefaultedForNullValue(boolean primitivesDefaultedForNullValue) {
this.primitivesDefaultedForNullValue = primitivesDefaultedForNullValue;
}
/**
* Return whether we're defaulting Java primitives in the case of mapping a null value
* from corresponding database fields.
*/
public boolean isPrimitivesDefaultedForNullValue() {
return primitivesDefaultedForNullValue;
}
/**
* Extract the values for all columns in the current row.
* <p>Utilizes public setters and result set metadata.
*
* @see java.sql.ResultSetMetaData
*/
@Override
public T mapRow(RowSet rs) throws Exception {
T mappedObject = getBean();
BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(mappedObject);
initBeanWrapper(bw);
RowSetMetaData rsmd = rs.getMetaData();
int columnCount = rsmd.getColumnCount();
Set<String> populatedProperties = (isCheckFullyPopulated() ? new HashSet<String>() : null);
for (int index = 0; index < columnCount; index++) {
String column = rsmd.getColumnName(index);
PropertyDescriptor pd = this.mappedFields.get(column.replaceAll(" ", "").toLowerCase());
if (pd != null) {
String value = rs.getColumnValue(index);
if (logger.isDebugEnabled()) {
logger.debug("Mapping column '" + column + "' to property '" +
pd.getName() + "' of type " + pd.getPropertyType());
}
try {
bw.setPropertyValue(pd.getName(), value);
} catch (TypeMismatchException e) {
if (value == null && primitivesDefaultedForNullValue) {
logger.debug("Intercepted TypeMismatchException for row " + rs.getCurrentRowIndex() +
" on sheet " + rsmd.getSheetName() + " and column '" + column + "' with value " + value +
" when setting property '" + pd.getName() + "' of type " + pd.getPropertyType() +
" on object: " + mappedObject);
} else {
throw e;
}
}
if (populatedProperties != null) {
populatedProperties.add(pd.getName());
}
}
}
if (populatedProperties != null && !populatedProperties.equals(this.mappedProperties)) {
throw new IllegalStateException("Given RowSet does not contain all fields " +
"necessary to populate object of class [" + mappedObject.getClass() + "]: " + this.mappedProperties);
}
return mappedObject;
}
/**
* Initialize the given BeanWrapper to be used for row mapping.
* To be called for each row.
* <p>The default implementation is empty. Can be overridden in subclasses.
*
* @param bw the BeanWrapper to initialize
*/
protected void initBeanWrapper(BeanWrapper bw) {
}
private T getBean() {
if (name != null) {
return (T) beanFactory.getBean(name);
}
try {
return type.newInstance();
} catch (InstantiationException e) {
ReflectionUtils.handleReflectionException(e);
} catch (IllegalAccessException e) {
ReflectionUtils.handleReflectionException(e);
}
// should not happen
throw new IllegalStateException("Internal error: could not create bean instance for mapping.");
}
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
this.beanFactory = beanFactory;
}
@Override
public void afterPropertiesSet() throws Exception {
Assert.state(name != null || type != null, "Either name or type must be provided.");
Assert.state(name == null || type == null, "Both name and type cannot be specified together.");
initialize((Class<T>) getBean().getClass());
}
/**
* Static factory method to create a new BeanPropertyRowMapper
* (with the mapped class specified only once).
*
* @param targetType the class that each row should be mapped to
*/
public static <T> BeanPropertyRowMapper<T> newInstance(Class<T> targetType) {
BeanPropertyRowMapper<T> newInstance = new BeanPropertyRowMapper<T>();
newInstance.setTargetType(targetType);
return newInstance;
}
}

View File

@@ -1,66 +0,0 @@
/*
* Copyright 2006-2014 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.excel.mapping;
import org.springframework.batch.item.excel.RowMapper;
import org.springframework.batch.item.excel.RowSet;
import org.springframework.batch.item.excel.Sheet;
import org.springframework.batch.item.excel.transform.DefaultRowTokenizer;
import org.springframework.batch.item.excel.transform.RowTokenizer;
import org.springframework.batch.item.file.mapping.FieldSetMapper;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.util.Assert;
/**
* {@link RowMapper} implementation which delegates to a {@link RowTokenizer} and a {@link org.springframework.batch.item.file.mapping.FieldSetMapper} the mapping
* of fields and construction of objects.
*
* @author Marten Deinum
* @since 0.5.0
*
* @param <T>
*/
public class DefaultRowMapper<T> implements RowMapper<T>, InitializingBean {
private RowTokenizer rowTokenizer = new DefaultRowTokenizer();
private FieldSetMapper<T> fieldSetMapper;
@Override
public T mapRow(RowSet rs) throws Exception {
return this.fieldSetMapper.mapFieldSet(this.rowTokenizer.tokenize(rs));
}
public void setFieldSetMapper(final FieldSetMapper<T> fieldSetMapper) {
this.fieldSetMapper = fieldSetMapper;
}
/**
* Set the {@link RowTokenizer} to use to create a {@link org.springframework.batch.item.file.transform.FieldSet}. Default uses the {@link DefaultRowTokenizer}.
*
* @param rowTokenizer to use
*/
public void setRowTokenizer(final RowTokenizer rowTokenizer) {
this.rowTokenizer = rowTokenizer;
}
@Override
public void afterPropertiesSet() throws Exception {
Assert.notNull(this.rowTokenizer, "The RowTokenizer must be set");
Assert.notNull(this.fieldSetMapper, "The FieldSetMapper must be set");
}
}

View File

@@ -16,16 +16,14 @@
package org.springframework.batch.item.excel.mapping;
import org.springframework.batch.item.excel.RowMapper;
import org.springframework.batch.item.excel.RowSet;
import org.springframework.batch.item.excel.Sheet;
import org.springframework.batch.item.excel.support.rowset.RowSet;
/**
* Pass through {@link RowMapper} useful for passing the orginal String[]
* back directly rather than a mapped object.
*
*
* @author Marten Deinum
* @since 0.5.0
*
*/
public class PassThroughRowMapper implements RowMapper<String[]> {

View File

@@ -28,10 +28,9 @@ import org.springframework.core.io.Resource;
* file. It will read the file sheet for sheet and row for row. It is based on
* the {@link org.springframework.batch.item.file.FlatFileItemReader}
*
* @param <T> the type
* @author Marten Deinum
* @since 0.5.0
*
* @param <T> the type
*/
public class PoiItemReader<T> extends AbstractExcelItemReader<T> {

View File

@@ -26,7 +26,7 @@ import java.util.List;
/**
* Sheet implementation for Apache POI.
*
*
* @author Marten Deinum
* @since 0.5.0
*/
@@ -38,7 +38,7 @@ public class PoiSheet implements Sheet {
/**
* Constructor which takes the delegate sheet.
*
*
* @param delegate the apache POI sheet
*/
PoiSheet(final org.apache.poi.ss.usermodel.Sheet delegate) {
@@ -67,44 +67,36 @@ public class PoiSheet implements Sheet {
*/
@Override
public String[] getRow(final int rowNumber) {
if (rowNumber > this.delegate.getLastRowNum()) {
final Row row = this.delegate.getRow(rowNumber);
if (row == null) {
return null;
}
final Row row = this.delegate.getRow(rowNumber);
final List<String> cells = new LinkedList<String>();
for (int i =0; i< getNumberOfColumns(); i++) {
for (int i = 0; i < getNumberOfColumns(); i++) {
Cell cell = row.getCell(i);
switch (cell.getCellType()) {
case Cell.CELL_TYPE_NUMERIC:
cells.add(String.valueOf(cell.getNumericCellValue()));
break;
case Cell.CELL_TYPE_BOOLEAN:
cells.add(String.valueOf(cell.getBooleanCellValue()));
break;
case Cell.CELL_TYPE_STRING:
case Cell.CELL_TYPE_BLANK:
cells.add(cell.getStringCellValue());
break;
case Cell.CELL_TYPE_FORMULA:
FormulaEvaluator evaluator = delegate.getWorkbook().getCreationHelper().createFormulaEvaluator();
cells.add(evaluator.evaluate(cell).formatAsString());
break;
default:
throw new IllegalArgumentException("Cannot handle cells of type " + cell.getCellType());
case Cell.CELL_TYPE_NUMERIC:
cells.add(String.valueOf(cell.getNumericCellValue()));
break;
case Cell.CELL_TYPE_BOOLEAN:
cells.add(String.valueOf(cell.getBooleanCellValue()));
break;
case Cell.CELL_TYPE_STRING:
case Cell.CELL_TYPE_BLANK:
cells.add(cell.getStringCellValue());
break;
case Cell.CELL_TYPE_FORMULA:
FormulaEvaluator evaluator = delegate.getWorkbook().getCreationHelper().createFormulaEvaluator();
cells.add(evaluator.evaluate(cell).formatAsString());
break;
default:
throw new IllegalArgumentException("Cannot handle cells of type " + cell.getCellType());
}
}
return cells.toArray(new String[cells.size()]);
}
/**
* {@inheritDoc}
*/
@Override
public String[] getHeader() {
return this.getRow(0);
}
/**
* {@inheritDoc}
*/

View File

@@ -13,20 +13,18 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.batch.item.excel.transform;
package org.springframework.batch.item.excel.support.rowset;
import org.springframework.batch.item.excel.RowSet;
import org.springframework.batch.item.excel.Sheet;
import org.springframework.batch.item.file.transform.FieldSet;
/**
* Interface that is used by framework to convert a row, represented in a String[], into a
* {@link org.springframework.batch.item.file.transform.FieldSet}.
*
* @author Marten Deinum
* Contract for extracting column names for a given {@Sheet sheet}.
*
* @author Marten Deinum
* @since 0.5.0
*/
public interface RowTokenizer {
public interface ColumnNameExtractor {
String[] getColumnNames(Sheet sheet);
FieldSet tokenize(RowSet rs);
}

View File

@@ -0,0 +1,114 @@
/*
* Copyright 2006-2014 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.excel.support.rowset;
import org.springframework.batch.item.excel.Sheet;
import java.util.Properties;
/**
* Default implementation of the {@code RowSet} interface.
*
* @author Marten Deinum
* @since 0.5.0
*
* @see org.springframework.batch.item.excel.support.rowset.DefaultRowSetFactory
*/
public class DefaultRowSet implements RowSet {
private final Sheet sheet;
private final RowSetMetaData metaData;
private int currentRowIndex = -1;
private String[] currentRow;
DefaultRowSet(Sheet sheet, RowSetMetaData metaData) {
this.sheet = sheet;
this.metaData = metaData;
}
@Override
public RowSetMetaData getMetaData() {
return metaData;
}
@Override
public boolean next() {
currentRow = null;
currentRowIndex++;
if (currentRowIndex < sheet.getNumberOfRows()) {
currentRow = sheet.getRow(currentRowIndex);
return true;
}
return false;
}
/**
* The current row index represents the number of rows
* into a {@link Sheet} the current line resides
*/
@Override
public int getCurrentRowIndex() {
return this.currentRowIndex;
}
/**
* Get the data of the current row.
*
* @return a String[] for the current data
*/
@Override
public String[] getCurrentRow() {
return this.currentRow;
}
/**
* Get the value of the given column
*
* @param idx index of the column to get, 0 based
* @return the value
* @throws java.lang.ArrayIndexOutOfBoundsException
*/
@Override
public String getColumnValue(int idx) {
return currentRow[idx];
}
/**
* Construct name-value pairs from the column names and string values. Null
* values are omitted.
*
* @return some properties representing the row set.
* @throws IllegalStateException if the column name meta data is not
* available.
*/
@Override
public Properties getProperties() {
final String[] names = metaData.getColumnNames();
if (names == null) {
throw new IllegalStateException("Cannot create properties without meta data");
}
Properties props = new Properties();
for (int i = 0; i < currentRow.length; i++) {
String value = currentRow[i];
if (value != null) {
props.setProperty(names[i], value);
}
}
return props;
}
}

View File

@@ -0,0 +1,42 @@
/*
* Copyright 2006-2014 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.excel.support.rowset;
import org.springframework.batch.item.excel.Sheet;
/**
* {@code RowSetFactory} implementation which constructs a {@code DefaultRowSet} instance and
* {@code DefaultRowSetMetaData} instance. The latter will have the {@code ColumnNameExtractor} configured
* on this factory set (default {@code RowNumberColumnNameExtractor}.
*
* @author Marten Deinum
* @since 0.5.0
*/
public class DefaultRowSetFactory implements RowSetFactory {
private ColumnNameExtractor columnNameExtractor = new RowNumberColumnNameExtractor();
@Override
public RowSet create(Sheet sheet) {
DefaultRowSetMetaData metaData = new DefaultRowSetMetaData(sheet);
metaData.setColumnNameExtractor(columnNameExtractor);
return new DefaultRowSet(sheet, metaData);
}
public void setColumnNameExtractor(ColumnNameExtractor columnNameExtractor) {
this.columnNameExtractor = columnNameExtractor;
}
}

View File

@@ -0,0 +1,63 @@
/*
* Copyright 2006-2014 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.excel.support.rowset;
import org.springframework.batch.item.excel.Sheet;
/**
* Default implementation for the {@code RowSetMetaData} interface.
*
* Requires a {@code Sheet} and {@code ColumnNameExtractor} to operate correctly.
* Delegates the retrieval of the column names to the {@code ColumnNameExtractor}.
*
* @author Marten Deinum
* @since 0.5.0
*/
public class DefaultRowSetMetaData implements RowSetMetaData {
private final Sheet sheet;
private ColumnNameExtractor columnNameExtractor;
DefaultRowSetMetaData(Sheet sheet) {
this.sheet = sheet;
}
@Override
public String[] getColumnNames() {
return columnNameExtractor.getColumnNames(sheet);
}
@Override
public String getColumnName(int idx) {
String[] names = getColumnNames();
return names[idx];
}
@Override
public int getColumnCount() {
return sheet.getNumberOfColumns();
}
@Override
public String getSheetName() {
return sheet.getName();
}
public void setColumnNameExtractor(ColumnNameExtractor columnNameExtractor) {
this.columnNameExtractor = columnNameExtractor;
}
}

View File

@@ -0,0 +1,39 @@
/*
* Copyright 2006-2014 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.excel.support.rowset;
import org.springframework.batch.item.excel.Sheet;
/**
* {@code ColumnNameExtractor} which returns the values of a given row (default is 0) as the column
* names.
*
* @author Marten Deinum
* @since 0.5.0
*/
public class RowNumberColumnNameExtractor implements ColumnNameExtractor {
private int headerRowNumber;
@Override
public String[] getColumnNames(final Sheet sheet) {
return sheet.getRow(headerRowNumber);
}
public void setHeaderRowNumber(int headerRowNumber) {
this.headerRowNumber = headerRowNumber;
}
}

View File

@@ -12,30 +12,29 @@
* 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.excel.transform;
*/
package org.springframework.batch.item.excel.support.rowset;
import java.util.Properties;
/**
* Convert a column name to an attribute name and vice versa.
*
* Used by the {@code org.springframework.batch.item.excel.AbstractExcelItemReader} to abstract away
* the complexities of the underlying Excel API implementations.
*
* @author Marten Deinum
* @since 0.5.0
*/
public interface ColumnToAttributeConverter {
public interface RowSet {
/**
* Convert a column name to an attribute name.
*
* @param column to convert
* @return the attribute name
*/
String toAttribute(String column);
RowSetMetaData getMetaData();
/**
* Convert an attribute name to a column name.
*
* @param attribute to convert
* @return the column name
*/
String toColumn(String attribute);
boolean next();
int getCurrentRowIndex();
String[] getCurrentRow();
String getColumnValue(int idx);
Properties getProperties();
}

View File

@@ -0,0 +1,29 @@
/*
* Copyright 2006-2014 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.excel.support.rowset;
import org.springframework.batch.item.excel.Sheet;
/**
* Contract for factories which will construct a {@code RowSet} implementation.
*
* @author Marten Deinum
* @since 0.5.0
*/
public interface RowSetFactory {
RowSet create(Sheet sheet);
}

View File

@@ -13,24 +13,21 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.batch.item.excel.transform;
package org.springframework.batch.item.excel.support.rowset;
/**
* {@link ColumnToAttributeConverter} which simply returns the given value.
*
* Interface representing the the metadata associated with an Excel document.
*
* @author Marten Deinum
* @since 0.5.0
*/
public class PassThroughColumnToAttributeConverter implements ColumnToAttributeConverter {
public interface RowSetMetaData {
@Override
public String toAttribute(final String column) {
return column;
}
String[] getColumnNames();
@Override
public String toColumn(final String attribute) {
return attribute;
}
String getColumnName(int idx);
int getColumnCount();
String getSheetName();
}

View File

@@ -0,0 +1,41 @@
/*
* Copyright 2006-2014 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.excel.support.rowset;
import org.springframework.batch.item.excel.Sheet;
/**
* {@code ColumnNameExtractor} implementation which returns a preset String[] to use as
* the column names. Useful for those situations in which an Excel file without a header row
* is read
*
* @author Marten Deinum
* @since 0.5.0
*/
public class StaticColumnNameExtractor implements ColumnNameExtractor {
private final String[] columnNames;
public StaticColumnNameExtractor(String[] columnNames) {
this.columnNames = columnNames;
}
@Override
public String[] getColumnNames(Sheet sheet) {
return this.columnNames;
}
}

View File

@@ -1,120 +0,0 @@
/*
* Copyright 2006-2014 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.excel.transform;
import org.springframework.batch.item.excel.RowSet;
import org.springframework.batch.item.excel.Sheet;
import org.springframework.batch.item.file.transform.DefaultFieldSetFactory;
import org.springframework.batch.item.file.transform.FieldSet;
import org.springframework.batch.item.file.transform.FieldSetFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/**
* {@link RowTokenizer} which assumes the column names are on the first row in the sheet.
*
* @author Marten Deinum
* @since 0.5.0
*/
public class DefaultRowTokenizer implements RowTokenizer, InitializingBean {
private FieldSetFactory fieldSetFactory = new DefaultFieldSetFactory();
private ColumnToAttributeConverter converter = new PassThroughColumnToAttributeConverter();
private boolean useColumnHeader = true;
private boolean includeSheetName = false;
private String attributeForSheetName = null;
@Override
public FieldSet tokenize(RowSet rs) {
String[] values = rs.getCurrentRow();
if (this.includeSheetName) {
values = StringUtils.addStringToArray(values, rs.getMetaData().getSheetName());
}
if (this.useColumnHeader) {
String[] names = rs.getMetaData().getColumnNames();
if (this.includeSheetName) {
names = StringUtils.addStringToArray(names, this.attributeForSheetName);
}
for (int i = 0; i < names.length; i++) {
names[i] = this.converter.toAttribute(names[i]);
}
return this.fieldSetFactory.create(values, names);
} else {
return this.fieldSetFactory.create(values);
}
}
/**
* Set the {@link org.springframework.batch.item.file.transform.FieldSetFactory} to use. The {@link org.springframework.batch.item.file.transform.DefaultFieldSetFactory} is used by default.
*
* @param fieldSetFactory to set
*/
public void setFieldSetFactory(final FieldSetFactory fieldSetFactory) {
this.fieldSetFactory = fieldSetFactory;
}
/**
* Indication to use the column header, the default is <code>true</code>.
* @param useColumnHeader
*/
public void setUseColumnHeader(final boolean useColumnHeader) {
this.useColumnHeader = useColumnHeader;
}
/**
* Set the {@link ColumnToAttributeConverter}.
*
* @param converter to set
*/
public void setConverter(final ColumnToAttributeConverter converter) {
this.converter = converter;
}
/**
* Should the name of the sheet be included in the list of columns. Default <code>false</code>
*
* @param includeSheetName
*/
public void setIncludeSheetName(final boolean includeSheetName) {
this.includeSheetName = includeSheetName;
}
/**
* When <code>includeSheetName</code> is <code>true</code> this is the name of the property used to store the name
* of the sheet.
*
* @param attributeForSheetName
* @see #setIncludeSheetName(boolean)
*/
public void setAttributeForSheetName(final String attributeForSheetName) {
this.attributeForSheetName = attributeForSheetName;
}
public void afterPropertiesSet() throws Exception {
if (this.includeSheetName && this.useColumnHeader) {
Assert.hasText(this.attributeForSheetName,
"When using column header as attributes and including the sheetname an attribute name for the sheetname is required.");
}
}
}

View File

@@ -1,58 +0,0 @@
/*
* Copyright 2006-2014 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.excel.transform;
import org.springframework.util.ObjectUtils;
import java.util.HashMap;
import java.util.Map;
/**
* {@link ColumnToAttributeConverter} which maps the names to columns and vice versa based on the provide mapping
* configuration. If a mapping cannot be found it returns the name as is.
*
* @author Marten Deinum
* @since 0.5.0
*/
public class MappingColumnToAttributeConverter implements ColumnToAttributeConverter {
private final Map<String, String> mapping = new HashMap<String, String>();
@Override
public String toAttribute(final String column) {
if (this.mapping.containsKey(column)) {
return this.mapping.get(column);
}
return column;
}
@Override
public String toColumn(final String attribute) {
if (this.mapping.containsValue(attribute)) {
for (Map.Entry<String, String> entry : this.mapping.entrySet()) {
if (ObjectUtils.nullSafeEquals(attribute, entry.getValue())) {
return entry.getKey();
}
}
}
return attribute;
}
public void setMappings(final Map<String, String> mappings) {
this.mapping.clear();
this.mapping.putAll(mappings);
}
}

View File

@@ -0,0 +1,71 @@
package org.springframework.batch.item;
/**
* Created by in329dei on 17-9-2014.
*/
public class Player {
private String id;
private String position;
private String lastName;
private String firstName;
private long birthYear;
private int debutYear;
private String comment;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getPosition() {
return position;
}
public void setPosition(String position) {
this.position = position;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public long getBirthYear() {
return birthYear;
}
public void setBirthYear(long birthYear) {
this.birthYear = birthYear;
}
public int getDebutYear() {
return debutYear;
}
public void setDebutYear(int debutYear) {
this.debutYear = debutYear;
}
public String getComment() {
return comment;
}
public void setComment(String comment) {
this.comment = comment;
}
}

View File

@@ -22,6 +22,7 @@ import org.junit.Before;
import org.junit.Test;
import org.springframework.batch.item.ExecutionContext;
import org.springframework.batch.item.excel.mapping.PassThroughRowMapper;
import org.springframework.batch.item.excel.support.rowset.RowSet;
import org.springframework.core.io.ClassPathResource;
import org.springframework.test.util.ReflectionTestUtils;
import org.springframework.util.StringUtils;
@@ -35,7 +36,7 @@ import static org.junit.Assert.assertEquals;
*/
public abstract class AbstractExcelItemReaderTests {
private final Log logger = LogFactory.getLog(this.getClass());
protected final Log logger = LogFactory.getLog(this.getClass());
protected AbstractExcelItemReader itemReader;

View File

@@ -0,0 +1,71 @@
package org.springframework.batch.item.excel;
import org.junit.Before;
import org.junit.Test;
import org.springframework.batch.item.ExecutionContext;
import org.springframework.batch.item.Player;
import org.springframework.batch.item.excel.mapping.BeanPropertyRowMapper;
import java.util.ArrayList;
import java.util.List;
import static org.junit.Assert.*;
/**
* Created by in329dei on 17-9-2014.
*/
public class BeanPropertyItemReaderTest {
private MockExcelItemReader<Player> reader;
private ExecutionContext executionContext;
@Before
public void setup() throws Exception {
executionContext = new ExecutionContext();
List<String[]> rows = new ArrayList<String[]>();
rows.add(new String[]{"id", "lastName", "firstName", "position", "birthYear", "debutYear"});
rows.add( new String[]{"AbduKa00", "Abdul-Jabbar", "Karim", "rb", "1974", "1996"});
rows.add( new String[]{"AbduRa00", "Abdullah", "Rabih", "rb", "1975", "1999"});
MockSheet sheet = new MockSheet("players", rows);
reader = new MockExcelItemReader<Player>(sheet);
BeanPropertyRowMapper<Player> rowMapper = new BeanPropertyRowMapper<Player>();
rowMapper.setTargetType(Player.class);
rowMapper.afterPropertiesSet();
reader.setLinesToSkip(1); // Skip first row as that is the header
reader.setRowMapper(rowMapper);
reader.afterPropertiesSet();
reader.open(executionContext);
}
@Test
public void readandMapPlayers() throws Exception {
Player p1 = reader.read();
Player p2 = reader.read();
Player p3 = reader.read();
assertNotNull(p1);
assertNotNull(p2);
assertNull(p3);
// Check first player
assertEquals("AbduKa00", p1.getId());
assertEquals("Abdul-Jabbar", p1.getLastName());
assertEquals("Karim", p1.getFirstName());
assertEquals("rb", p1.getPosition());
assertEquals(1974, p1.getBirthYear());
assertEquals(1996, p1.getDebutYear());
// Check second player
assertEquals("AbduRa00", p2.getId());
assertEquals("Abdullah", p2.getLastName());
assertEquals("Rabih", p2.getFirstName());
assertEquals("rb", p2.getPosition());
assertEquals(1975, p2.getBirthYear());
assertEquals(1999, p2.getDebutYear());
}
}

View File

@@ -0,0 +1,77 @@
package org.springframework.batch.item.excel;
import org.junit.Before;
import org.junit.Test;
import org.springframework.batch.item.ExecutionContext;
import org.springframework.batch.item.Player;
import org.springframework.batch.item.excel.MockExcelItemReader;
import org.springframework.batch.item.excel.MockSheet;
import org.springframework.batch.item.excel.mapping.BeanPropertyRowMapper;
import org.springframework.batch.item.excel.support.rowset.DefaultRowSet;
import org.springframework.batch.item.excel.support.rowset.DefaultRowSetFactory;
import org.springframework.batch.item.excel.support.rowset.StaticColumnNameExtractor;
import java.util.ArrayList;
import java.util.List;
import static org.junit.Assert.*;
/**
* Created by in329dei on 17-9-2014.
*/
public class BeanPropertyWithStaticHeaderItemReaderTest {
private MockExcelItemReader<Player> reader;
private ExecutionContext executionContext;
@Before
public void setup() throws Exception {
executionContext = new ExecutionContext();
List<String[]> rows = new ArrayList<String[]>();
rows.add( new String[]{"AbduKa00", "Abdul-Jabbar", "Karim", "rb", "1974", "1996"});
rows.add( new String[]{"AbduRa00", "Abdullah", "Rabih", "rb", "1975", "1999"});
MockSheet sheet = new MockSheet("players", rows);
reader = new MockExcelItemReader<Player>(sheet);
BeanPropertyRowMapper<Player> rowMapper = new BeanPropertyRowMapper<Player>();
rowMapper.setTargetType(Player.class);
rowMapper.afterPropertiesSet();
reader.setRowMapper(rowMapper);
DefaultRowSetFactory factory = new DefaultRowSetFactory();
factory.setColumnNameExtractor(new StaticColumnNameExtractor(new String[]{"id", "lastName", "firstName", "position", "birthYear", "debutYear"}));
reader.setRowSetFactory(factory);
reader.afterPropertiesSet();
reader.open(executionContext);
}
@Test
public void readandMapPlayers() throws Exception {
Player p1 = reader.read();
Player p2 = reader.read();
Player p3 = reader.read();
assertNotNull(p1);
assertNotNull(p2);
assertNull(p3);
// Check first player
assertEquals("AbduKa00", p1.getId());
assertEquals("Abdul-Jabbar", p1.getLastName());
assertEquals("Karim", p1.getFirstName());
assertEquals("rb", p1.getPosition());
assertEquals(1974, p1.getBirthYear());
assertEquals(1996, p1.getDebutYear());
// Check second player
assertEquals("AbduRa00", p2.getId());
assertEquals("Abdullah", p2.getLastName());
assertEquals("Rabih", p2.getFirstName());
assertEquals("rb", p2.getPosition());
assertEquals(1975, p2.getBirthYear());
assertEquals(1999, p2.getDebutYear());
}
}

View File

@@ -0,0 +1,40 @@
package org.springframework.batch.item.excel;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.core.io.Resource;
import java.util.Collections;
import java.util.List;
/**
* Created by in329dei on 17-9-2014.
*/
public class MockExcelItemReader<T> extends AbstractExcelItemReader<T> {
private final List<MockSheet> sheets;
public MockExcelItemReader(MockSheet sheet) {
this(Collections.singletonList(sheet));
}
public MockExcelItemReader(List<MockSheet> sheets) {
this.sheets=sheets;
super.setResource(new ByteArrayResource(new byte[0]));
}
@Override
protected Sheet getSheet(int sheet) {
return sheets.get(sheet);
}
@Override
protected int getNumberOfSheets() {
return sheets.size();
}
@Override
protected void openExcelFile(Resource resource) throws Exception {
}
}

View File

@@ -0,0 +1,50 @@
package org.springframework.batch.item.excel;
import jxl.Cell;
import org.springframework.batch.item.excel.jxl.JxlUtils;
import java.util.List;
/**
* Sheet implementation usable for testing. Works in an {@code List} of {@xode String[]}.
*
* @author Marten Deinum
* @since 0.5.0
*/
public class MockSheet implements Sheet {
private final List<String[]> rows;
private final String name;
public MockSheet(String name, List<String[]> rows) {
this.name = name;
this.rows = rows;
}
@Override
public int getNumberOfRows() {
return rows.size();
}
@Override
public String getName() {
return this.name;
}
@Override
public String[] getRow(int rowNumber) {
if (rowNumber < getNumberOfRows()) {
return this.rows.get(rowNumber);
} else {
return null;
}
}
@Override
public int getNumberOfColumns() {
if (rows.isEmpty()) {
return 0;
}
return rows.get(0).length;
}
}

View File

@@ -0,0 +1,99 @@
package org.springframework.batch.item.excel.mapping;
import org.junit.Test;
import org.springframework.batch.item.Player;
import org.springframework.batch.item.excel.MockSheet;
import org.springframework.batch.item.excel.support.rowset.DefaultRowSetFactory;
import org.springframework.batch.item.excel.support.rowset.RowSet;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;
import java.util.ArrayList;
import java.util.List;
import static org.junit.Assert.*;
/**
* Created by in329dei on 17-9-2014.
*/
public class BeanPropertyRowMapperTest {
@Test(expected = IllegalStateException.class)
public void givenNoNameWhenInitCompleteThenIllegalStateShouldOccur() throws Exception {
BeanPropertyRowMapper mapper = new BeanPropertyRowMapper();
mapper.afterPropertiesSet();
}
@Test
public void givenAValidRowWhenMappingThenAValidPlayerShouldBeConstructed() throws Exception {
BeanPropertyRowMapper<Player> mapper = new BeanPropertyRowMapper<Player>();
mapper.setTargetType(Player.class);
mapper.afterPropertiesSet();
List<String[]> rows = new ArrayList<String[]>();
rows.add(new String[]{"id", "lastName", "firstName", "position", "birthYear", "debutYear"});
rows.add( new String[]{"AbduKa00", "Abdul-Jabbar", "Karim", "rb", "1974", "1996"});
MockSheet sheet = new MockSheet("players", rows);
RowSet rs = new DefaultRowSetFactory().create(sheet);
rs.next();
rs.next();
Player p = mapper.mapRow(rs);
assertNotNull(p);
assertEquals("AbduKa00", p.getId());
assertEquals("Abdul-Jabbar", p.getLastName());
assertEquals("Karim", p.getFirstName());
assertEquals("rb", p.getPosition());
assertEquals(1974, p.getBirthYear());
assertEquals(1996, p.getDebutYear());
assertNull(p.getComment());
}
@Test
public void givenAValidRowWhenMappingThenAValidPlayerShouldBeConstructedBasedOnPrototype() throws Exception {
ApplicationContext ctx = new AnnotationConfigApplicationContext(TestConfig.class);
BeanPropertyRowMapper<Player> mapper = new BeanPropertyRowMapper<Player>();
mapper.setPrototypeBeanName("player");
mapper.setBeanFactory(ctx);
mapper.afterPropertiesSet();
List<String[]> rows = new ArrayList<String[]>();
rows.add(new String[]{"id", "lastName", "firstName", "position", "birthYear", "debutYear"});
rows.add( new String[]{"AbduKa00", "Abdul-Jabbar", "Karim", "rb", "1974", "1996"});
MockSheet sheet = new MockSheet("players", rows);
RowSet rs = new DefaultRowSetFactory().create(sheet);
rs.next();
rs.next();
Player p = mapper.mapRow(rs);
assertNotNull(p);
assertEquals("AbduKa00", p.getId());
assertEquals("Abdul-Jabbar", p.getLastName());
assertEquals("Karim", p.getFirstName());
assertEquals("rb", p.getPosition());
assertEquals(1974, p.getBirthYear());
assertEquals(1996, p.getDebutYear());
assertEquals("comment from context", p.getComment());
}
@Configuration
public static class TestConfig {
@Bean
@Scope(value = "prototype")
public Player player() {
Player p = new Player();
p.setComment("comment from context");
return p;
}
}
}

View File

@@ -1,76 +0,0 @@
/*
* Copyright 2006-2014 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.excel.mapping;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.runners.MockitoJUnitRunner;
import org.springframework.batch.item.excel.RowSet;
import org.springframework.batch.item.excel.Sheet;
import org.springframework.batch.item.excel.transform.RowTokenizer;
import org.springframework.batch.item.file.mapping.FieldSetMapper;
import org.springframework.batch.item.file.transform.FieldSet;
import static org.mockito.Matchers.any;
/**
* Tests for {@link DefaultRowMapper}.
* @author Marten Deinum
*
*/
@RunWith(MockitoJUnitRunner.class)
public class DefaultRowMapperTests {
@Mock
private FieldSetMapper fieldSetMapper;
@Mock
private RowTokenizer rowTokenizer;
@Test(expected = IllegalArgumentException.class)
public void nullRowTokenizerShouldLeadToException() throws Exception {
final DefaultRowMapper mapper = new DefaultRowMapper();
mapper.setRowTokenizer(null);
mapper.setFieldSetMapper(this.fieldSetMapper);
mapper.afterPropertiesSet();
}
@Test(expected = IllegalArgumentException.class)
public void nullFieldSetMapperShouldLeadToException() throws Exception {
final DefaultRowMapper mapper = new DefaultRowMapper();
mapper.setRowTokenizer(this.rowTokenizer);
mapper.setFieldSetMapper(null);
mapper.afterPropertiesSet();
}
@Test
public void foo() throws Exception {
final DefaultRowMapper mapper = new DefaultRowMapper();
mapper.setRowTokenizer(this.rowTokenizer);
mapper.setFieldSetMapper(this.fieldSetMapper);
final FieldSet fs = Mockito.mock(FieldSet.class);
final Object result = new Object();
Mockito.when(this.rowTokenizer.tokenize(any(RowSet.class))).thenReturn(fs);
Mockito.when(this.fieldSetMapper.mapFieldSet(fs)).thenReturn(result);
Assert.assertEquals(result, mapper.mapRow(null));
Mockito.verify(this.rowTokenizer, Mockito.times(1)).tokenize(any(RowSet.class));
Mockito.verify(this.fieldSetMapper, Mockito.times(1)).mapFieldSet(fs);
}
}

View File

@@ -16,33 +16,31 @@
package org.springframework.batch.item.excel.mapping;
import org.junit.Test;
import org.mockito.Mockito;
import org.springframework.batch.item.excel.RowSet;
import org.springframework.batch.item.excel.Sheet;
import org.springframework.batch.item.excel.MockSheet;
import org.springframework.batch.item.excel.support.rowset.DefaultRowSetFactory;
import org.springframework.batch.item.excel.support.rowset.RowSet;
import java.util.Collections;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.*;
/**
* Tests for {@link PassThroughRowMapper}.
*
* @author Marten Deinum
*
* @author Marten Deinum
*/
public class PassThroughRowMapperTests {
public class PassThroughRowMapperTest {
private final PassThroughRowMapper rowMapper = new PassThroughRowMapper();
@Test
public void mapRowShouldReturnSameValues() throws Exception {
final String[] row = new String[] { "foo", "bar", "baz" };
Sheet sheet = mock(Sheet.class);
when(sheet.getRow(0)).thenReturn(row);
when(sheet.getNumberOfRows()).thenReturn(1);
RowSet rs = new RowSet(sheet);
assertTrue(rs.next());
final String[] row = new String[]{"foo", "bar", "baz"};
MockSheet sheet = new MockSheet("mock", Collections.singletonList( row));
RowSet rs = new DefaultRowSetFactory().create(sheet);
assertTrue(rs.next());
assertArrayEquals(row, this.rowMapper.mapRow(rs));
}

View File

@@ -20,11 +20,7 @@ import org.apache.commons.logging.LogFactory;
import org.springframework.batch.item.excel.AbstractExcelItemReader;
import org.springframework.batch.item.excel.AbstractExcelItemReaderTests;
public class PoiItemReaderXlsTests extends AbstractExcelItemReaderTests {
private final Log logger = LogFactory.getLog(this.getClass());
private PoiItemReader itemReader;
public class PoiItemReaderXlsTest extends AbstractExcelItemReaderTests {
@Override
protected AbstractExcelItemReader createExcelItemReader() {

View File

@@ -19,7 +19,7 @@ import org.springframework.batch.item.excel.AbstractExcelItemReader;
import org.springframework.batch.item.excel.AbstractExcelItemReaderTests;
import org.springframework.core.io.ClassPathResource;
public class PoiItemReaderXlsxTests extends AbstractExcelItemReaderTests {
public class PoiItemReaderXlsxTest extends AbstractExcelItemReaderTests {
@Override
protected void configureItemReader(AbstractExcelItemReader itemReader) {

View File

@@ -1,56 +0,0 @@
/*
* Copyright 2006-2014 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.excel.transform;
import org.junit.Before;
import org.junit.Test;
import java.util.HashMap;
import java.util.Map;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
public class MappingColumnToAttributeConverterTests {
private final MappingColumnToAttributeConverter converter = new MappingColumnToAttributeConverter();
@Before
public void setup() {
final Map<String, String> mappings = new HashMap<String, String>();
mappings.put("foo", "bar1");
mappings.put("baz", "bar2");
mappings.put("with spaces", "noSpaces");
this.converter.setMappings(mappings);
}
@Test
public void convertColumnToAttribtue() {
assertEquals("bar1", this.converter.toAttribute("foo"));
assertEquals("noSpaces", this.converter.toAttribute("with spaces"));
assertEquals("not existing", this.converter.toAttribute("not existing"));
assertNull(this.converter.toAttribute(null));
}
@Test
public void convertAttributeToColumn() {
assertEquals("baz", this.converter.toColumn("bar2"));
assertEquals("with spaces", this.converter.toColumn("noSpaces"));
assertEquals("not existing", this.converter.toColumn("not existing"));
assertNull(this.converter.toAttribute(null));
}
}

View File

@@ -1,41 +0,0 @@
/*
* Copyright 2006-2014 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.excel.transform;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
public class PassThroughColumnToAttributeConverterTests {
private final PassThroughColumnToAttributeConverter converter = new PassThroughColumnToAttributeConverter();
@Test
public void columnNameShouldRemainTheSame() {
final String column = "column";
assertEquals(column, this.converter.toAttribute(column));
assertNull(this.converter.toAttribute(null));
}
@Test
public void attributeNameShouldRemainTheSame() {
final String attribute = "attribute";
assertEquals(attribute, this.converter.toColumn(attribute));
assertNull(this.converter.toColumn(null));
}
}

View File

@@ -26,7 +26,7 @@
</appender>
<category name="org.springframework.batch">
<level value="DEBUG" />
<level value="DEBUG"/>
</category>
<root>