Initial version 0.1.0 changes from original repository

Development of Spring Batch Excel continued in the original repository at
http://github.com/mdeinum/spring-batch-excel.git, with this commit we transfer all fixes, updates and new features in one go to the Spring Batch Excel repository.

The most important changes are:
- Moved to new location org.springframework.batch.extensions
- Removed support for JXL
- Initial support for reading XLSX files streaming
- Upgraded dependencies to most recent versions
- Support for reading password protected files
- Numerous small fixes

Fixes: 10,11,19,26,29,34
Closes: 13,14,30,35,50
This commit is contained in:
Marten Deinum
2021-03-15 08:26:05 +01:00
committed by Mahmoud Ben Hassine
parent e40d1df90a
commit 9bb5c79290
90 changed files with 3789 additions and 2699 deletions

View File

@@ -0,0 +1,298 @@
/*
* Copyright 2006-2021 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
*
* https://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.extensions.excel;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.batch.extensions.excel.support.rowset.DefaultRowSetFactory;
import org.springframework.batch.extensions.excel.support.rowset.RowSet;
import org.springframework.batch.extensions.excel.support.rowset.RowSetFactory;
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;
/**
* {@link org.springframework.batch.item.ItemReader} implementation to read an Excel file.
* It will read the file sheet for sheet and row for row. It is loosy based on the
* {@link org.springframework.batch.item.file.FlatFileItemReader}
*
* @param <T> the type
* @author Marten Deinum
* @since 0.1.0
*/
public abstract class AbstractExcelItemReader<T> extends AbstractItemCountingItemStreamItemReader<T>
implements ResourceAwareItemReaderItemStream<T>, InitializingBean {
protected final Log logger = LogFactory.getLog(getClass());
private Resource resource;
private int linesToSkip = 0;
private int currentSheet = 0;
private int endAfterBlankLines = 1;
private RowMapper<T> rowMapper;
private RowCallbackHandler skippedRowsCallback;
private boolean noInput = false;
private boolean strict = true;
private RowSetFactory rowSetFactory = new DefaultRowSetFactory();
private RowSet rs;
private String password;
public AbstractExcelItemReader() {
super();
this.setName(ClassUtils.getShortName(this.getClass()));
}
@Override
public T read() throws Exception {
T item = super.read();
int blankLines = 0;
while (item == null) {
blankLines++;
if (blankLines >= this.endAfterBlankLines) {
return null;
}
item = super.read();
if (item != null) {
return item;
}
}
return item;
}
@Override
protected T doRead() {
if (this.noInput) {
return null;
}
if (this.rs == null || !this.rs.next()) {
if (!nextSheet()) {
if (this.logger.isDebugEnabled()) {
this.logger.debug("No more sheets in '" + this.resource.getDescription() + "'.");
}
return null;
}
}
// skip all the blank row from which content has been deleted but still a valid row
while (null != this.rs.getCurrentRow() && isInvalidValidRow(this.rs)) {
this.rs.next();
}
try {
return (this.rs.getCurrentRow() != null) ? this.rowMapper.mapRow(this.rs) : null;
}
catch (Exception ex) {
throw new ExcelFileParseException("Exception parsing Excel file.", ex, this.resource.getDescription(),
this.rs.getMetaData().getSheetName(), this.rs.getCurrentRowIndex(), this.rs.getCurrentRow());
}
}
/**
* On restart this will increment rowSet to where job left off previously.
* Temporarily switch out the configured {@code RowMapper} so we can use the
* {@code #doRead} method and reuse the logic in there, but without actually map to
* instances (this to save memory and have better performance).
*/
@Override
protected void jumpToItem(final int itemIndex) {
RowMapper<T> current = this.rowMapper;
this.rowMapper = (rs) -> null;
try {
for (int i = 0; i < itemIndex; i++) {
doRead();
}
}
finally {
this.rowMapper = current;
}
}
private boolean isInvalidValidRow(RowSet rs) {
for (String str : rs.getCurrentRow()) {
if (str.length() > 0) {
return false;
}
}
return true;
}
@Override
protected void doOpen() throws Exception {
Assert.notNull(this.resource, "Input resource must be set");
this.noInput = true;
if (!this.resource.exists()) {
if (this.strict) {
throw new IllegalStateException(
"Input resource must exist (reader is in 'strict' mode): " + this.resource);
}
this.logger.warn("Input resource does not exist '" + this.resource.getDescription() + "'.");
return;
}
if (!this.resource.isReadable()) {
if (this.strict) {
throw new IllegalStateException(
"Input resource must be readable (reader is in 'strict' mode): " + this.resource);
}
this.logger.warn("Input resource is not readable '" + this.resource.getDescription() + "'.");
return;
}
this.openExcelFile(this.resource, this.password);
this.noInput = false;
if (this.logger.isDebugEnabled()) {
this.logger.debug("Opened workbook [" + this.resource.getFilename() + "] with " + this.getNumberOfSheets()
+ " sheets.");
}
}
private boolean nextSheet() {
while (this.currentSheet < this.getNumberOfSheets()) {
final Sheet sheet = this.getSheet(this.currentSheet);
this.rs = this.rowSetFactory.create(sheet);
if (this.logger.isDebugEnabled()) {
this.logger.debug("Opening sheet " + sheet.getName() + ".");
}
for (int i = 0; i < this.linesToSkip; i++) {
if (this.rs.next() && this.skippedRowsCallback != null) {
this.skippedRowsCallback.handleRow(this.rs);
}
}
if (this.logger.isDebugEnabled()) {
this.logger.debug("Openend sheet " + sheet.getName() + ", with " + sheet.getNumberOfRows() + " rows.");
}
this.currentSheet++;
if (this.rs.next()) {
return true;
}
}
return false;
}
protected void doClose() throws Exception {
this.currentSheet = 0;
this.rs = null;
}
/**
* Public setter for the input resource.
* @param resource the {@code Resource} pointing to the Excelfile
*/
public void setResource(final Resource resource) {
this.resource = resource;
}
public void afterPropertiesSet() throws Exception {
Assert.notNull(this.rowMapper, "RowMapper must be set");
}
/**
* Set the number of lines to skip. This number is applied to all worksheet in the
* excel file! default to 0
* @param linesToSkip number of lines to skip
*/
public void setLinesToSkip(final int linesToSkip) {
this.linesToSkip = linesToSkip;
}
/**
* Get the sheet based on the given sheet index.
* @param sheet the sheet index
* @return the sheet or <code>null</code> when no sheet available.
*/
protected abstract Sheet getSheet(int sheet);
/**
* The number of sheets in the underlying workbook.
* @return the number of sheets.
*/
protected abstract int getNumberOfSheets();
/**
* Opens the excel file and reads the file and sheet metadata. Uses a {@code Resource} to read the sheets,
* this file can optionally be password protected.
* @param resource {@code Resource} pointing to the Excel file to read
* @param password optional password
* @throws Exception when the Excel sheet cannot be accessed
*/
protected abstract void openExcelFile(Resource resource, String password) throws Exception;
/**
* In strict mode the reader will throw an exception on
* {@link #open(org.springframework.batch.item.ExecutionContext)} if the input
* resource does not exist.
* @param strict true by default
*/
public void setStrict(final boolean strict) {
this.strict = strict;
}
/**
* Public setter for the {@code rowMapper}. Used to map a row read from the underlying
* Excel workbook.
* @param rowMapper the {@code RowMapper} to use.
*/
public void setRowMapper(final RowMapper<T> rowMapper) {
this.rowMapper = rowMapper;
}
/**
* Public setter for the <code>rowSetFactory</code>. Used to create a {@code RowSet}
* implemenation. By default the {@code DefaultRowSetFactory} is used.
* @param rowSetFactory the {@code RowSetFactory} to use.
*/
public void setRowSetFactory(RowSetFactory rowSetFactory) {
this.rowSetFactory = rowSetFactory;
}
/**
* Set the callback handler to call when a row is being skipped.
* @param skippedRowsCallback will be called for each one of the initial skipped lines
* before any items are read.
*/
public void setSkippedRowsCallback(final RowCallbackHandler skippedRowsCallback) {
this.skippedRowsCallback = skippedRowsCallback;
}
public void setEndAfterBlankLines(final int endAfterBlankLines) {
this.endAfterBlankLines = endAfterBlankLines;
}
/**
* The password used to protect the file to open.
* @param password the password
*/
public void setPassword(String password) {
this.password = password;
}
}

View File

@@ -0,0 +1,74 @@
/*
* Copyright 2006-2021 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
*
* https://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.extensions.excel;
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 simply dependencies to make it is generic
* as possible.
*
* @author Marten Deinum
* @since 0.1.0
*/
public class ExcelFileParseException extends ParseException {
private final String filename;
private final String sheet;
private final String[] row;
private final int rowNumber;
/**
* 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 rowNumber the row number in the current sheet
* @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) {
super(message, cause);
this.filename = filename;
this.sheet = sheet;
this.rowNumber = rowNumber;
this.row = row;
}
public String getFilename() {
return this.filename;
}
public String getSheet() {
return this.sheet;
}
public int getRowNumber() {
return this.rowNumber;
}
public String[] getRow() {
return this.row;
}
}

View File

@@ -0,0 +1,40 @@
/*
* Copyright 2011 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
*
* https://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.extensions.excel;
import org.springframework.batch.extensions.excel.support.rowset.RowSet;
/**
* Callback to handle skipped lines. Useful for header/footer processing.
*
* @author Marten Deinum
*/
public interface RowCallbackHandler {
/**
* Implementations must implement this method to process each row of data in the
* {@code RowSet}.
* <p>This method should not call {@code next()} on the {@code RowSetSet}; it is only
* supposed to extract values of the current row.
* <p>Exactly what the implementation chooses to do is up to it: A trivial implementation
* might simply count rows, while another implementation might build a special header
* row.
* @param rs the {@code RowSet} to process (preset at the current row)
*/
void handleRow(RowSet rs);
}

View File

@@ -1,11 +1,11 @@
/*
* Copyright 2006-2015 the original author or authors.
* Copyright 2006-2021 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
*
* https://www.apache.org/licenses/LICENSE-2.0
* https://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,
@@ -13,28 +13,28 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.batch.item.excel;
import org.springframework.batch.item.excel.support.rowset.RowSet;
package org.springframework.batch.extensions.excel;
import org.springframework.batch.extensions.excel.support.rowset.RowSet;
/**
* Map rows from an excel sheet to an object.
*
* @param <T> the type
* @author Marten Deinum
* @since 0.5.0
* @since 0.1.0
*/
public interface RowMapper<T> {
/**
* 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.
*/
T mapRow(RowSet rs) throws Exception;
/**
* 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.
*/
T mapRow(RowSet rs) throws Exception;
}

View File

@@ -0,0 +1,53 @@
/*
* Copyright 2006-2021 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
*
* https://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.extensions.excel;
import org.springframework.lang.Nullable;
/**
* Interface to wrap different Excel implementations like JExcel or Apache POI.
*
* @author Marten Deinum
* @since 0.1.0
*/
public interface Sheet extends Iterable<String[]>, AutoCloseable {
/**
* 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 {@code String[]}. Returns {@code null} if the row doesn't exist.
* @param rowNumber the row number to read.
* @return a {@code String[]} or {@code null}
*/
@Nullable
String[] getRow(int rowNumber);
@Override
default void close() throws Exception {
}
}

View File

@@ -0,0 +1,382 @@
/*
* Copyright 2006-2021 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
*
* https://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.extensions.excel.mapping;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import org.springframework.batch.extensions.excel.RowMapper;
import org.springframework.batch.extensions.excel.support.rowset.RowSet;
import org.springframework.batch.support.DefaultPropertyEditorRegistrar;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.BeanWrapperImpl;
import org.springframework.beans.MutablePropertyValues;
import org.springframework.beans.NotWritablePropertyException;
import org.springframework.beans.PropertyAccessor;
import org.springframework.beans.PropertyAccessorUtils;
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.validation.BindException;
import org.springframework.validation.DataBinder;
/**
* {@link RowMapper} implementation based on bean property paths. The {@link RowSet} to be
* mapped should have field name meta data corresponding to bean property paths in an
* instance of the desired type. The instance is created and initialized either by
* referring to to a prototype object by bean name in the enclosing BeanFactory, or by
* providing a class to instantiate reflectively.<br>
* <br>
*
* Nested property paths, including indexed properties in maps and collections, can be
* referenced by the {@link RowSet}names. They will be converted to nested bean properties
* inside the prototype. The {@link RowSet} and the prototype are thus tightly coupled by
* the fields that are available and those that can be initialized. If some of the nested
* properties are optional (e.g. collection members) they need to be removed by a post
* processor.<br>
* <br>
*
* To customize the way that {@link RowSet} values are converted to the desired type for
* injecting into the prototype there are several choices. You can inject
* {@link java.beans.PropertyEditor} instances directly through the
* {@link #setCustomEditors(Map) customEditors} property, or you can override the
* {@link #createBinder(Object)} and {@link #initBinder(DataBinder)} methods, or you can
* provide a custom {@link RowSet} implementation.<br>
* <br>
*
* Property name matching is "fuzzy" in the sense that it tolerates close matches, as long
* as the match is unique. For instance:
*
* <ul>
* <li>Quantity = quantity (field names can be capitalised)</li>
* <li>ISIN = isin (acronyms can be lower case bean property names, as per Java Beans
* recommendations)</li>
* <li>DuckPate = duckPate (capitalisation including camel casing)</li>
* <li>ITEM_ID = itemId (capitalisation and replacing word boundary with underscore)</li>
* <li>ORDER.CUSTOMER_ID = order.customerId (nested paths are recursively checked)</li>
* </ul>
*
* The algorithm used to match a property name is to start with an exact match and then
* search successively through more distant matches until precisely one match is found. If
* more than one match is found there will be an error.
*
* @param <T> type
* @author Marten Deinum
* @since 0.1.0
*/
public class BeanWrapperRowMapper<T> extends DefaultPropertyEditorRegistrar
implements RowMapper<T>, BeanFactoryAware, InitializingBean {
private String name;
private Class<? extends T> type;
private BeanFactory beanFactory;
private final ConcurrentMap<DistanceHolder, ConcurrentMap<String, String>> propertiesMatched = new ConcurrentHashMap<>();
private int distanceLimit = 5;
private boolean strict = true;
@Override
public void setBeanFactory(BeanFactory beanFactory) {
this.beanFactory = beanFactory;
}
/**
* The maximum difference that can be tolerated in spelling between input key names
* and bean property names. Defaults to 5, but could be set lower if the field names
* match the bean names.
* @param distanceLimit the distance limit to set
*/
public void setDistanceLimit(int distanceLimit) {
this.distanceLimit = distanceLimit;
}
/**
* 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.
*
* 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>
*
* Either this property or the prototype bean name must be specified, but not both.
* @param type the type to set
*/
public void setTargetType(Class<? extends T> type) {
this.type = type;
}
/**
* Check that precisely one of type or prototype bean name is specified.
* @throws IllegalStateException if neither is set or both properties are set.
* @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet()
*/
@Override
public void afterPropertiesSet() throws Exception {
Assert.state(this.name != null || this.type != null, "Either name or type must be provided.");
Assert.state(this.name == null || this.type == null, "Both name and type cannot be specified together.");
}
/**
* Map the {@link org.springframework.batch.item.file.transform.FieldSet} to an object
* retrieved from the enclosing Spring context, or to a new instance of the required
* type if no prototype is available.
* @throws org.springframework.validation.BindException if there is a type conversion
* or other error (if the {@link org.springframework.validation.DataBinder} from
* {@link #createBinder(Object)} has errors after binding).
* @throws org.springframework.beans.NotWritablePropertyException if the
* {@link org.springframework.batch.item.file.transform.FieldSet} contains a field
* that cannot be mapped to a bean property.
* @see org.springframework.batch.item.file.mapping.FieldSetMapper#mapFieldSet(org.springframework.batch.item.file.transform.FieldSet)
*/
@Override
public T mapRow(RowSet rs) throws BindException {
T copy = getBean();
DataBinder binder = createBinder(copy);
binder.bind(new MutablePropertyValues(getBeanProperties(copy, rs.getProperties())));
if (binder.getBindingResult().hasErrors()) {
throw new BindException(binder.getBindingResult());
}
return copy;
}
/**
* Create a binder for the target object. The binder will then be used to bind the
* properties form a field set into the target object. This implementation creates a
* new {@link DataBinder} and calls out to {@link #initBinder(DataBinder)} and
* {@link #registerCustomEditors(org.springframework.beans.PropertyEditorRegistry)}.
* @param target the object to bind to.
* @return a {@link DataBinder} that can be used to bind properties to the target.
*/
protected DataBinder createBinder(Object target) {
DataBinder binder = new DataBinder(target);
binder.setIgnoreUnknownFields(!this.strict);
initBinder(binder);
registerCustomEditors(binder);
return binder;
}
/**
* Initialize a new binder instance. This hook allows customization of binder settings
* such as the {@link DataBinder#initDirectFieldAccess() direct field access}. Called
* by {@link #createBinder(Object)}.
* <p>
* Note that registration of custom property editors can be done in
* {@link #registerCustomEditors(org.springframework.beans.PropertyEditorRegistry)}.
* </p>
* @param binder new binder instance
* @see #createBinder(Object)
*/
protected void initBinder(DataBinder binder) {
}
@SuppressWarnings("unchecked")
private T getBean() {
if (this.name != null) {
return (T) this.beanFactory.getBean(this.name);
}
return BeanUtils.instantiateClass(this.type);
}
private Properties getBeanProperties(Object bean, Properties properties) {
Class<?> cls = bean.getClass();
// Map from field names to property names
DistanceHolder distanceKey = new DistanceHolder(cls, this.distanceLimit);
if (!this.propertiesMatched.containsKey(distanceKey)) {
this.propertiesMatched.putIfAbsent(distanceKey, new ConcurrentHashMap<>());
}
Map<String, String> matches = new HashMap<>(this.propertiesMatched.get(distanceKey));
@SuppressWarnings({ "unchecked", "rawtypes" })
Set<String> keys = new HashSet(properties.keySet());
for (String key : keys) {
if (matches.containsKey(key)) {
switchPropertyNames(properties, key, matches.get(key));
continue;
}
String name = findPropertyName(bean, key);
if (name != null) {
if (matches.containsValue(name)) {
throw new NotWritablePropertyException(cls, name, "Duplicate match with distance <= "
+ this.distanceLimit + " found for this property in input keys: " + keys
+ ". (Consider reducing the distance limit or changing the input key names to get a closer match.)");
}
matches.put(key, name);
switchPropertyNames(properties, key, name);
}
}
this.propertiesMatched.replace(distanceKey, new ConcurrentHashMap<>(matches));
return properties;
}
private String findPropertyName(Object bean, String key) {
if (bean == null) {
return null;
}
Class<?> cls = bean.getClass();
int index = PropertyAccessorUtils.getFirstNestedPropertySeparatorIndex(key);
String prefix;
String suffix;
// If the property name is nested recurse down through the properties
// looking for a match.
if (index > 0) {
prefix = key.substring(0, index);
suffix = key.substring(index + 1);
String nestedName = findPropertyName(bean, prefix);
if (nestedName == null) {
return null;
}
Object nestedValue = getPropertyValue(bean, nestedName);
String nestedPropertyName = findPropertyName(nestedValue, suffix);
return (nestedPropertyName != null) ? nestedName + "." + nestedPropertyName : null;
}
String name = null;
int distance = 0;
index = key.indexOf(PropertyAccessor.PROPERTY_KEY_PREFIX_CHAR);
if (index > 0) {
prefix = key.substring(0, index);
suffix = key.substring(index);
}
else {
prefix = key;
suffix = "";
}
while (name == null && distance <= this.distanceLimit) {
String[] candidates = PropertyMatches.forProperty(prefix, cls, distance).getPossibleMatches();
// If we find precisely one match, then use that one...
if (candidates.length == 1) {
String candidate = candidates[0];
if (candidate.equals(prefix)) { // if it's the same don't
// replace it...
name = key;
}
else {
name = candidate + suffix;
}
}
distance++;
}
return name;
}
private Object getPropertyValue(Object bean, String nestedName) {
BeanWrapperImpl wrapper = new BeanWrapperImpl(bean);
wrapper.setAutoGrowNestedPaths(true);
Object nestedValue = wrapper.getPropertyValue(nestedName);
if (nestedValue == null) {
nestedValue = BeanUtils.instantiateClass(wrapper.getPropertyType(nestedName));
wrapper.setPropertyValue(nestedName, nestedValue);
}
return nestedValue;
}
private void switchPropertyNames(Properties properties, String oldName, String newName) {
String value = properties.getProperty(oldName);
properties.remove(oldName);
properties.setProperty(newName, value);
}
/**
* Public setter for the 'strict' property. If true, then {@link #mapRow(RowSet)} will
* fail if the RowSet contains fields that cannot be mapped to the bean.
* @param strict fail if non-mappable properties are found
*/
public void setStrict(boolean strict) {
this.strict = strict;
}
private static class DistanceHolder {
private final Class<?> cls;
private final int distance;
DistanceHolder(Class<?> cls, int distance) {
this.cls = cls;
this.distance = distance;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
DistanceHolder other = (DistanceHolder) obj;
if (this.cls == null) {
if (other.cls != null) {
return false;
}
}
else if (!this.cls.equals(other.cls)) {
return false;
}
return this.distance == other.distance;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((this.cls == null) ? 0 : this.cls.hashCode());
result = prime * result + this.distance;
return result;
}
}
}

View File

@@ -1,11 +1,11 @@
/*
* Copyright 2006-2014 the original author or authors.
* Copyright 2006-2021 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
*
* https://www.apache.org/licenses/LICENSE-2.0
* https://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,
@@ -13,23 +13,24 @@
* 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.support.rowset.RowSet;
package org.springframework.batch.extensions.excel.mapping;
import org.springframework.batch.extensions.excel.RowMapper;
import org.springframework.batch.extensions.excel.support.rowset.RowSet;
/**
* Pass through {@link RowMapper} useful for passing the orginal String[]
* back directly rather than a mapped object.
* Pass through {@link RowMapper} useful for passing the orginal {@code String[]} back
* directly rather than a mapped object.
*
* @author Marten Deinum
* @since 0.5.0
* @since 0.1.0
*/
public class PassThroughRowMapper implements RowMapper<String[]> {
@Override
public String[] mapRow(final RowSet rs) throws Exception {
return rs.getCurrentRow();
}
@Override
public String[] mapRow(final RowSet rs) throws Exception {
return rs.getCurrentRow();
}
}

View File

@@ -0,0 +1,136 @@
/*
* Copyright 2006-2021 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
*
* https://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.extensions.excel.mapping;
import java.beans.PropertyDescriptor;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.springframework.beans.BeanUtils;
import org.springframework.util.StringUtils;
/**
* Helper class for calculating bean property matches, according to. Used by
* BeanWrapperImpl to suggest alternatives for an invalid property name.<br>
*
* Copied and slightly modified from Spring core,
*
* @author Alef Arendsen
* @author Arjen Poutsma
* @author Juergen Hoeller
* @author Dave Syer
* @since 0.1.0
* @see #forProperty(String, Class, int)
*/
final class PropertyMatches {
/**
* Create PropertyMatches for the given bean property.
* @param propertyName the name of the property to find possible matches for
* @param beanClass the bean class to search for matches
* @param maxDistance the maximum property distance allowed for matches
* @return the prepared {@code PropertyMatches}
*/
static PropertyMatches forProperty(String propertyName, Class<?> beanClass, int maxDistance) {
return new PropertyMatches(propertyName, beanClass, maxDistance);
}
private final String propertyName;
private final String[] possibleMatches;
/**
* Create a new PropertyMatches instance for the given property.
* @param propertyName the name of the property to find possible matches for
* @param beanClass the bean class to search for matches
* @param maxDistance the maximum property distance allowed for matches
*/
private PropertyMatches(String propertyName, Class<?> beanClass, int maxDistance) {
this.propertyName = propertyName;
this.possibleMatches = calculateMatches(BeanUtils.getPropertyDescriptors(beanClass), maxDistance);
}
String[] getPossibleMatches() {
return this.possibleMatches;
}
/**
* Generate possible property alternatives for the given property and class.
* Internally uses the <code>getStringDistance</code> method, which in turn uses the
* Levenshtein algorithm to determine the distance between two Strings.
* @param propertyDescriptors the JavaBeans property descriptors to search
* @param maxDistance the maximum distance to accept
* @return the calculated matches
*/
private String[] calculateMatches(PropertyDescriptor[] propertyDescriptors, int maxDistance) {
List<String> candidates = new ArrayList<>();
for (PropertyDescriptor propertyDescriptor : propertyDescriptors) {
if (propertyDescriptor.getWriteMethod() != null) {
String possibleAlternative = propertyDescriptor.getName();
int distance = calculateStringDistance(this.propertyName, possibleAlternative);
if (distance <= maxDistance) {
candidates.add(possibleAlternative);
}
}
}
Collections.sort(candidates);
return StringUtils.toStringArray(candidates);
}
/**
* Calculate the distance between the given two Strings according to the Levenshtein
* algorithm.
* @param s1 the first String
* @param s2 the second String
* @return the distance value
*/
private int calculateStringDistance(String s1, String s2) {
if (s1.length() == 0) {
return s2.length();
}
if (s2.length() == 0) {
return s1.length();
}
int[][] d = new int[s1.length() + 1][s2.length() + 1];
for (int i = 0; i <= s1.length(); i++) {
d[i][0] = i;
}
for (int j = 0; j <= s2.length(); j++) {
d[0][j] = j;
}
for (int i = 1; i <= s1.length(); i++) {
char s_i = s1.charAt(i - 1);
for (int j = 1; j <= s2.length(); j++) {
int cost;
char t_j = s2.charAt(j - 1);
if (Character.toLowerCase(s_i) == Character.toLowerCase(t_j)) {
cost = 0;
}
else {
cost = 1;
}
d[i][j] = Math.min(Math.min(d[i - 1][j] + 1, d[i][j - 1] + 1), d[i - 1][j - 1] + cost);
}
}
return d[s1.length()][s2.length()];
}
}

View File

@@ -1,11 +1,11 @@
/*
* Copyright 2006-2014 the original author or authors.
* Copyright 2006-2021 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
*
* https://www.apache.org/licenses/LICENSE-2.0
* https://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,
@@ -14,19 +14,7 @@
* limitations under the License.
*/
package org.springframework.batch.item.excel.jxl;
import org.springframework.batch.item.excel.AbstractExcelItemReader;
import org.springframework.batch.item.excel.AbstractExcelItemReaderTests;
/**
* Test
* Default {@code RowMapper} implementations.
*/
public class JxlItemReaderTest extends AbstractExcelItemReaderTests {
@Override
protected AbstractExcelItemReader createExcelItemReader() {
return new JxlItemReader();
}
}
package org.springframework.batch.extensions.excel.mapping;

View File

@@ -0,0 +1,20 @@
/*
* Copyright 2006-2021 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
*
* https://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.
*/
/**
* Core interfaces for reading Excel files.
*/
package org.springframework.batch.extensions.excel;

View File

@@ -0,0 +1,95 @@
/*
* Copyright 2006-2021 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
*
* https://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.extensions.excel.poi;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.InputStream;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.ss.usermodel.WorkbookFactory;
import org.springframework.batch.extensions.excel.AbstractExcelItemReader;
import org.springframework.batch.extensions.excel.Sheet;
import org.springframework.core.io.Resource;
/**
* {@link org.springframework.batch.item.ItemReader} implementation which uses apache POI
* 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}
*
* This class is <b>not</b> thread-safe.
*
* @param <T> the type
* @author Marten Deinum
* @since 0.1.0
*/
public class PoiItemReader<T> extends AbstractExcelItemReader<T> {
private Workbook workbook;
private InputStream inputStream;
@Override
protected Sheet getSheet(final int sheet) {
return new PoiSheet(this.workbook.getSheetAt(sheet));
}
@Override
protected int getNumberOfSheets() {
return this.workbook.getNumberOfSheets();
}
@Override
protected void doClose() throws Exception {
super.doClose();
if (this.inputStream != null) {
this.inputStream.close();
this.inputStream = null;
}
if (this.workbook != null) {
this.workbook.close();
this.workbook = null;
}
}
/**
* Open the underlying file using the {@code WorkbookFactory}. Prefer {@code File}
* based access over an {@code InputStream}. Using a file will use fewer resources
* compared to an input stream. The latter will need to cache the whole sheet
* in-memory.
* @param resource the {@code Resource} pointing to the Excel file.
* @param password the password for opening the file
* @throws Exception is thrown for any errors.
*/
@Override
protected void openExcelFile(final Resource resource, String password) throws Exception {
try {
File file = resource.getFile();
this.workbook = WorkbookFactory.create(file, password, false);
}
catch (FileNotFoundException ex) {
this.inputStream = resource.getInputStream();
this.workbook = WorkbookFactory.create(this.inputStream, password);
}
this.workbook.setMissingCellPolicy(Row.MissingCellPolicy.CREATE_NULL_AS_BLANK);
}
}

View File

@@ -0,0 +1,137 @@
/*
* Copyright 2006-2021 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
*
* https://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.extensions.excel.poi;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.CellType;
import org.apache.poi.ss.usermodel.DataFormatter;
import org.apache.poi.ss.usermodel.FormulaEvaluator;
import org.apache.poi.ss.usermodel.Row;
import org.springframework.batch.extensions.excel.Sheet;
import org.springframework.lang.Nullable;
/**
* Sheet implementation for Apache POI.
*
* @author Marten Deinum
* @since 0.1.0
*/
class PoiSheet implements Sheet {
private final DataFormatter dataFormatter = new DataFormatter();
private final org.apache.poi.ss.usermodel.Sheet delegate;
private final int numberOfRows;
private final String name;
private FormulaEvaluator evaluator;
/**
* Constructor which takes the delegate sheet.
* @param delegate the apache POI sheet
*/
PoiSheet(final org.apache.poi.ss.usermodel.Sheet delegate) {
super();
this.delegate = delegate;
this.numberOfRows = this.delegate.getLastRowNum() + 1;
this.name = this.delegate.getSheetName();
}
/**
* {@inheritDoc}
*/
@Override
public int getNumberOfRows() {
return this.numberOfRows;
}
/**
* {@inheritDoc}
*/
@Override
public String getName() {
return this.name;
}
/**
* {@inheritDoc}
*/
@Override
@Nullable
public String[] getRow(final int rowNumber) {
final Row row = this.delegate.getRow(rowNumber);
return map(row);
}
@Nullable
private String[] map(Row row) {
if (row == null) {
return null;
}
final List<String> cells = new LinkedList<>();
final int numberOfColumns = row.getLastCellNum();
for (int i = 0; i < numberOfColumns; i++) {
Cell cell = row.getCell(i);
CellType cellType = cell.getCellType();
if (cellType == CellType.FORMULA) {
cells.add(this.dataFormatter.formatCellValue(cell, getFormulaEvaluator()));
}
else {
cells.add(this.dataFormatter.formatCellValue(cell));
}
}
return cells.toArray(new String[0]);
}
/**
* Lazy getter for the {@code FormulaEvaluator}. Takes some time to create an
* instance, so if not necessary don't create it.
* @return the {@code FormulaEvaluator}
*/
private FormulaEvaluator getFormulaEvaluator() {
if (this.evaluator == null) {
this.evaluator = this.delegate.getWorkbook().getCreationHelper().createFormulaEvaluator();
}
return this.evaluator;
}
@Override
public Iterator<String[]> iterator() {
return new Iterator<String[]>() {
private final Iterator<Row> delegateIter = PoiSheet.this.delegate.iterator();
@Override
public boolean hasNext() {
return this.delegateIter.hasNext();
}
@Override
public String[] next() {
return map(this.delegateIter.next());
}
};
}
}

View File

@@ -0,0 +1,20 @@
/*
* Copyright 2006-2021 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
*
* https://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.
*/
/**
* Support classes for the Apache POI library.
*/
package org.springframework.batch.extensions.excel.poi;

View File

@@ -0,0 +1,303 @@
/*
* Copyright 2006-2021 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
*
* https://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.extensions.excel.streaming;
import java.io.Closeable;
import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import javax.xml.stream.XMLStreamConstants;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.ss.util.CellReference;
import org.apache.poi.xssf.eventusermodel.XSSFSheetXMLHandler;
import org.apache.poi.xssf.model.SharedStrings;
import org.apache.poi.xssf.model.Styles;
import org.apache.poi.xssf.usermodel.XSSFComment;
import org.xml.sax.Attributes;
import org.springframework.batch.extensions.excel.Sheet;
import org.springframework.util.StringUtils;
import org.springframework.util.xml.StaxUtils;
class StreamingSheet implements Sheet, Iterable<String[]>, Closeable {
private final Log logger = LogFactory.getLog(StreamingSheet.class);
private final String name;
private final InputStream is;
private final XMLStreamReader reader;
private final ValueRetrievingContentsHandler contentHandler;
private final XSSFSheetXMLHandler sheetHandler;
private int rowCount;
private int colCount;
StreamingSheet(String name, InputStream is, SharedStrings sharedStrings, Styles styles) {
this.name = name;
this.is = is;
this.contentHandler = new ValueRetrievingContentsHandler();
this.sheetHandler = new XSSFSheetXMLHandler(styles, sharedStrings, this.contentHandler, false);
try {
this.reader = StaxUtils.createDefensiveInputFactory().createXMLStreamReader(is);
}
catch (Exception ex) {
throw new IllegalStateException(ex);
}
}
@Override
public int getNumberOfRows() {
return this.rowCount;
}
@Override
public String getName() {
return this.name;
}
@Override
public String[] getRow(int rowNumber) {
throw new UnsupportedOperationException("Getting row by index not supported when streaming.");
}
private String[] nextRow() {
try {
while (this.reader.hasNext()) {
int type = this.reader.next();
if (type == XMLStreamConstants.START_DOCUMENT) {
this.sheetHandler.startDocument();
}
else if (type == XMLStreamConstants.END_DOCUMENT) {
this.sheetHandler.endDocument();
return null;
}
else if (type == XMLStreamConstants.CHARACTERS) {
this.sheetHandler.characters(this.reader.getTextCharacters(), this.reader.getTextStart(), this.reader.getTextLength());
}
else if (type == XMLStreamConstants.START_ELEMENT) {
String localName = this.reader.getLocalName();
if ("dimension".equals(localName)) {
String v = this.reader.getAttributeValue(null, "ref");
if (v != null && v.indexOf(':') > -1) {
CellRangeAddress range = CellRangeAddress.valueOf(v);
int rowEnd = range.getLastRow();
int rowStart = range.getFirstRow();
this.rowCount = rowEnd - rowStart + 1;
int colStart = range.getFirstColumn();
int colEnd = range.getLastColumn();
this.colCount = colEnd - colStart + 1;
}
}
else {
Attributes delegating = new AttributesAdapter(this.reader);
this.sheetHandler.startElement(null, localName, null, delegating);
}
}
else if (type == XMLStreamConstants.END_ELEMENT) {
String tag = this.reader.getLocalName();
this.sheetHandler.endElement(null, tag, null);
if ("row".equals(tag)) {
if (this.logger.isTraceEnabled()) {
this.logger.trace("Row ended, returning: "
+ StringUtils.arrayToCommaDelimitedString(this.contentHandler.getValues()));
}
return this.contentHandler.getValues();
}
}
}
}
catch (Exception ex) {
throw new IllegalStateException("Error reading file.", ex);
}
return null;
}
@Override
public void close() throws IOException {
try {
this.reader.close();
}
catch (XMLStreamException ex) {
// Ignore exception we cannot recover
}
this.is.close();
}
@Override
public Iterator<String[]> iterator() {
return new Iterator<String[]>() {
private String[] currentRow;
@Override
public boolean hasNext() {
this.currentRow = nextRow();
return this.currentRow != null;
}
@Override
public String[] next() {
return this.currentRow;
}
};
}
private class ValueRetrievingContentsHandler implements XSSFSheetXMLHandler.SheetContentsHandler {
private final Log logger = LogFactory.getLog(ValueRetrievingContentsHandler.class);
private String[] values;
@Override
public void startRow(int rowNum) {
if (this.logger.isTraceEnabled()) {
this.logger.trace("Start processing row: " + rowNum);
}
// Prepare for this row
if (this.values == null) {
this.values = new String[StreamingSheet.this.colCount];
}
Arrays.fill(this.values, "");
}
@Override
public void endRow(int rowNum) {
if (this.logger.isTraceEnabled()) {
this.logger.trace("End processing row: " + rowNum);
}
}
@Override
public void cell(String cellReference, String formattedValue, XSSFComment comment) {
int col = new CellReference(cellReference).getCol();
if (this.logger.isTraceEnabled()) {
this.logger.trace("Setting value (" + cellReference + ") = " + formattedValue);
}
// This can happen if the dimensions cannot be read properly but there are
// still rows.
// Create a copy of the existing array and append to it.
if (this.values.length <= col) {
String[] newValues = Arrays.copyOf(this.values, col + 1);
Arrays.setAll(newValues, (idx) -> (newValues[idx] != null) ? newValues[idx] : "");
this.values = newValues;
}
this.values[col] = formattedValue;
}
String[] getValues() {
return Arrays.copyOf(this.values, this.values.length);
}
}
/**
* Minimal adapter for {@code Attributes} so that it works with the
* {@code XSSFSheetXMLHandler}. Adapts an {@code XMLStreamReader} so that it can be
* used as an {@code org.xml.sax.Attributes} implementation.
*/
private static final class AttributesAdapter implements Attributes {
private final Map<String, String> attributes = new HashMap<>();
private AttributesAdapter(XMLStreamReader delegate) {
for (int i = 0; i < delegate.getAttributeCount(); i++) {
String name = delegate.getAttributeLocalName(i);
String value = delegate.getAttributeValue(i);
this.attributes.put(name, value);
}
}
@Override
public int getLength() {
return this.attributes.size();
}
@Override
public String getURI(int index) {
return null;
}
@Override
public String getLocalName(int index) {
return null;
}
@Override
public String getQName(int index) {
return null;
}
@Override
public String getType(int index) {
return null;
}
@Override
public String getValue(int index) {
return null;
}
@Override
public int getIndex(String uri, String localName) {
return 0;
}
@Override
public int getIndex(String qName) {
return 0;
}
@Override
public String getType(String uri, String localName) {
return null;
}
@Override
public String getType(String qName) {
return null;
}
@Override
public String getValue(String uri, String localName) {
return this.attributes.get(localName);
}
@Override
public String getValue(String qName) {
return this.attributes.get(qName);
}
}
}

View File

@@ -0,0 +1,115 @@
/*
* Copyright 2006-2021 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
*
* https://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.extensions.excel.streaming;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
import org.apache.poi.openxml4j.opc.OPCPackage;
import org.apache.poi.openxml4j.opc.PackageAccess;
import org.apache.poi.xssf.eventusermodel.ReadOnlySharedStringsTable;
import org.apache.poi.xssf.eventusermodel.XSSFReader;
import org.apache.poi.xssf.model.SharedStrings;
import org.apache.poi.xssf.model.Styles;
import org.xml.sax.SAXException;
import org.springframework.batch.extensions.excel.AbstractExcelItemReader;
import org.springframework.batch.extensions.excel.Sheet;
import org.springframework.core.io.Resource;
/**
* Simple streaming reader without Apache POI.
*
* @param <T> the type
* @author Marten Deinum
* @since 0.1.0
**/
public class StreamingXlsxItemReader<T> extends AbstractExcelItemReader<T> {
private final List<StreamingSheet> sheets = new ArrayList<>();
private OPCPackage pkg;
private InputStream inputStream;
@Override
protected Sheet getSheet(int sheet) {
return this.sheets.get(sheet);
}
@Override
protected int getNumberOfSheets() {
return this.sheets.size();
}
@Override
protected void openExcelFile(Resource resource, String password) throws Exception {
try {
File file = resource.getFile();
this.pkg = OPCPackage.open(file, PackageAccess.READ);
}
catch (FileNotFoundException ex) {
this.inputStream = resource.getInputStream();
this.pkg = OPCPackage.open(this.inputStream);
}
XSSFReader reader = new XSSFReader(this.pkg);
initSheets(reader, this.pkg);
}
private void initSheets(XSSFReader reader, OPCPackage pkg) throws IOException, InvalidFormatException {
XSSFReader.SheetIterator iter = (XSSFReader.SheetIterator) reader.getSheetsData();
SharedStrings sharedStrings;
try {
sharedStrings = new ReadOnlySharedStringsTable(pkg);
}
catch (SAXException ex) {
throw new IllegalStateException("Cannot read shared-strings-table.", ex);
}
Styles styles = reader.getStylesTable();
while (iter.hasNext()) {
InputStream is = iter.next();
String name = iter.getSheetName();
this.sheets.add(new StreamingSheet(name, is, sharedStrings, styles));
}
if (this.logger.isTraceEnabled()) {
this.logger.trace("Prepared " + this.sheets.size() + " sheets.");
}
}
@Override
protected void doClose() throws Exception {
this.pkg.revert();
for (StreamingSheet sheet : this.sheets) {
sheet.close();
}
this.sheets.clear();
if (this.inputStream != null) {
this.inputStream.close();
this.inputStream = null;
}
super.doClose();
}
}

View File

@@ -0,0 +1,20 @@
/*
* Copyright 2006-2021 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
*
* https://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.
*/
/**
* Streaming implementation of ItemReaders.
*/
package org.springframework.batch.extensions.excel.streaming;

View File

@@ -1,11 +1,11 @@
/*
* Copyright 2006-2015 the original author or authors.
* Copyright 2006-2021 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
*
* https://www.apache.org/licenses/LICENSE-2.0
* https://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,
@@ -13,24 +13,24 @@
* 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;
package org.springframework.batch.extensions.excel.support.rowset;
import org.springframework.batch.extensions.excel.Sheet;
/**
* Contract for extracting column names for a given {@code sheet}.
*
* @author Marten Deinum
* @since 0.5.0
* @since 0.1.0
*/
public interface ColumnNameExtractor {
/**
* Retrieves the names of the columns in the given Sheet.
*
* @param sheet the sheet
* @return the column names
*/
String[] getColumnNames(Sheet sheet);
/**
* Retrieves the names of the columns in the given {@code Sheet}.
* @param sheet the sheet
* @return the column names
*/
String[] getColumnNames(Sheet sheet);
}

View File

@@ -0,0 +1,89 @@
/*
* Copyright 2006-2021 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
*
* https://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.extensions.excel.support.rowset;
import java.util.Iterator;
import java.util.Properties;
import org.springframework.batch.extensions.excel.Sheet;
/**
* Default implementation of the {@code RowSet} interface.
*
* @author Marten Deinum
* @since 0.1.0
* @see DefaultRowSetFactory
*/
public class DefaultRowSet implements RowSet {
private final Iterator<String[]> sheetData;
private final RowSetMetaData metaData;
private int currentRowIndex = -1;
private String[] currentRow;
DefaultRowSet(Sheet sheet, RowSetMetaData metaData) {
this.sheetData = sheet.iterator();
this.metaData = metaData;
}
@Override
public RowSetMetaData getMetaData() {
return this.metaData;
}
@Override
public boolean next() {
this.currentRow = null;
this.currentRowIndex++;
if (this.sheetData.hasNext()) {
this.currentRow = this.sheetData.next();
return true;
}
return false;
}
@Override
public int getCurrentRowIndex() {
return this.currentRowIndex;
}
@Override
public String[] getCurrentRow() {
return this.currentRow;
}
@Override
public Properties getProperties() {
final String[] names = this.metaData.getColumnNames();
if (names == null) {
throw new IllegalStateException("Cannot create properties without meta data");
}
Properties props = new Properties();
for (int i = 0; i < this.currentRow.length; i++) {
String value = this.currentRow[i];
if (value != null) {
props.setProperty(names[i], value);
}
}
return props;
}
}

View File

@@ -0,0 +1,44 @@
/*
* Copyright 2006-2021 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
*
* https://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.extensions.excel.support.rowset;
import org.springframework.batch.extensions.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.1.0
*/
public class DefaultRowSetFactory implements RowSetFactory {
private ColumnNameExtractor columnNameExtractor = new RowNumberColumnNameExtractor();
@Override
public RowSet create(Sheet sheet) {
DefaultRowSetMetaData metaData = new DefaultRowSetMetaData(sheet, this.columnNameExtractor);
return new DefaultRowSet(sheet, metaData);
}
public void setColumnNameExtractor(ColumnNameExtractor columnNameExtractor) {
this.columnNameExtractor = columnNameExtractor;
}
}

View File

@@ -0,0 +1,56 @@
/*
* Copyright 2006-2021 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
*
* https://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.extensions.excel.support.rowset;
import org.springframework.batch.extensions.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.1.0
*/
public class DefaultRowSetMetaData implements RowSetMetaData {
private final Sheet sheet;
private final ColumnNameExtractor columnNameExtractor;
private String[] columnNames;
DefaultRowSetMetaData(Sheet sheet, ColumnNameExtractor columnNameExtractor) {
this.sheet = sheet;
this.columnNameExtractor = columnNameExtractor;
}
@Override
public String[] getColumnNames() {
if (this.columnNames == null) {
this.columnNames = this.columnNameExtractor.getColumnNames(this.sheet);
}
return this.columnNames;
}
@Override
public String getSheetName() {
return this.sheet.getName();
}
}

View File

@@ -1,11 +1,11 @@
/*
* Copyright 2006-2014 the original author or authors.
* Copyright 2006-2021 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
*
* https://www.apache.org/licenses/LICENSE-2.0
* https://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,
@@ -13,27 +13,29 @@
* 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;
package org.springframework.batch.extensions.excel.support.rowset;
import org.springframework.batch.extensions.excel.Sheet;
/**
* {@code ColumnNameExtractor} which returns the values of a given row (default is 0) as the column
* names.
* {@code ColumnNameExtractor} which returns the values of a given row (default is 0) as
* the column names.
*
* @author Marten Deinum
* @since 0.5.0
* @since 0.1.0
*/
public class RowNumberColumnNameExtractor implements ColumnNameExtractor {
private int headerRowNumber;
private int headerRowNumber;
@Override
public String[] getColumnNames(final Sheet sheet) {
return sheet.getRow(headerRowNumber);
}
@Override
public String[] getColumnNames(final Sheet sheet) {
return sheet.getRow(this.headerRowNumber);
}
public void setHeaderRowNumber(int headerRowNumber) {
this.headerRowNumber = headerRowNumber;
}
public void setHeaderRowNumber(int headerRowNumber) {
this.headerRowNumber = headerRowNumber;
}
}

View File

@@ -0,0 +1,63 @@
/*
* Copyright 2006-2021 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
*
* https://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.extensions.excel.support.rowset;
import java.util.Properties;
/**
* 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.1.0
*/
public interface RowSet {
/**
* Retrieves the meta data (name of the sheet, number of columns, names) of this row
* set.
* @return a corresponding {@code RowSetMetaData} instance.
*/
RowSetMetaData getMetaData();
/**
* Move to the next row in the document.
* @return {@code true} if the row is valid, {@code false} if there are no more rows
*/
boolean next();
/**
* Returns the current row number.
* @return the current row number
*/
int getCurrentRowIndex();
/**
* Return the current row as a {@code String[]}.
* @return the row as a {@code String[]}
*/
String[] getCurrentRow();
/**
* Construct name-value pairs from the column names and string values. {@code null}
* values are omitted.
* @return some properties representing the row set.
* @throws IllegalStateException if the column name meta data is not available.
*/
Properties getProperties();
}

View File

@@ -1,11 +1,11 @@
/*
* Copyright 2006-2014 the original author or authors.
* Copyright 2006-2021 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
*
* https://www.apache.org/licenses/LICENSE-2.0
* https://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,
@@ -13,23 +13,24 @@
* 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;
package org.springframework.batch.extensions.excel.support.rowset;
import org.springframework.batch.extensions.excel.Sheet;
/**
* Contract for factories which will construct a {@code RowSet} implementation.
*
* @author Marten Deinum
* @since 0.5.0
* @since 0.1.0
*/
public interface RowSetFactory {
/**
* Create a rowset instance.
*
* @param sheet an Excel sheet.
* @return a RowSet instance.
*/
RowSet create(Sheet sheet);
/**
* Create a rowset instance.
* @param sheet an Excel sheet.
* @return a {@code RowSet} instance.
*/
RowSet create(Sheet sheet);
}

View File

@@ -0,0 +1,39 @@
/*
* Copyright 2006-2021 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
*
* https://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.extensions.excel.support.rowset;
/**
* Interface representing the the metadata associated with an Excel document.
*
* @author Marten Deinum
* @since 0.1.0
*/
public interface RowSetMetaData {
/**
* Retrieves the names of the columns for the current sheet.
* @return the column names.
*/
String[] getColumnNames();
/**
* Retrieves the name of the sheet the RowSet is based on.
* @return the name of the sheet
*/
String getSheetName();
}

View File

@@ -1,11 +1,11 @@
/*
* Copyright 2006-2014 the original author or authors.
* Copyright 2006-2021 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
*
* https://www.apache.org/licenses/LICENSE-2.0
* https://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,
@@ -13,29 +13,32 @@
* 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;
package org.springframework.batch.extensions.excel.support.rowset;
import java.util.Arrays;
import org.springframework.batch.extensions.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
* 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
* @author Marten Deinum
* @since 0.1.0
*/
public class StaticColumnNameExtractor implements ColumnNameExtractor {
private final String[] columnNames;
private final String[] columnNames;
public StaticColumnNameExtractor(String[] columnNames) {
this.columnNames = columnNames;
}
public StaticColumnNameExtractor(String[] columnNames) {
this.columnNames = columnNames;
}
@Override
public String[] getColumnNames(Sheet sheet) {
return this.columnNames;
}
@Override
public String[] getColumnNames(Sheet sheet) {
return Arrays.copyOf(this.columnNames, this.columnNames.length);
}
}

View File

@@ -1,11 +1,11 @@
/*
* Copyright 2006-2014 the original author or authors.
* Copyright 2006-2021 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
*
* https://www.apache.org/licenses/LICENSE-2.0
* https://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,
@@ -14,18 +14,7 @@
* limitations under the License.
*/
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
* RowSet abstraction for Excel documents.
*/
public interface RowCallbackHandler {
void handleRow(RowSet rs);
}
package org.springframework.batch.extensions.excel.support.rowset;

View File

@@ -1,218 +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
*
* https://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;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.batch.item.excel.support.rowset.DefaultRowSetFactory;
import org.springframework.batch.item.excel.support.rowset.RowSet;
import org.springframework.batch.item.excel.support.rowset.RowSetFactory;
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;
/**
* {@link org.springframework.batch.item.ItemReader} implementation to read an Excel
* file. It will read the file sheet for sheet and row for row. It is loosy based on
* the {@link org.springframework.batch.item.file.FlatFileItemReader}
*
* @param <T> the type
* @author Marten Deinum
* @since 0.5.0
*/
public abstract class AbstractExcelItemReader<T> extends AbstractItemCountingItemStreamItemReader<T> implements
ResourceAwareItemReaderItemStream<T>, InitializingBean {
protected final Log logger = LogFactory.getLog(getClass());
private Resource resource;
private int linesToSkip = 0;
private int currentSheet = 0;
private RowMapper<T> rowMapper;
private RowCallbackHandler skippedRowsCallback;
private boolean noInput = false;
private boolean strict = true;
private RowSetFactory rowSetFactory = new DefaultRowSetFactory();
private RowSet rs;
public AbstractExcelItemReader() {
super();
this.setName(ClassUtils.getShortName(this.getClass()));
}
/**
* @return string corresponding to logical record according to
* {@link #setRowMapper(RowMapper)} (might span multiple rows in file).
*/
@Override
protected T doRead() throws Exception {
if (this.noInput || this.rs == null) {
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();
}
}
}
@Override
protected void doOpen() throws Exception {
Assert.notNull(this.resource, "Input resource must be set");
this.noInput = true;
if (!this.resource.exists()) {
if (this.strict) {
throw new IllegalStateException("Input resource must exist (reader is in 'strict' mode): "
+ this.resource);
}
logger.warn("Input resource does not exist '" + this.resource.getDescription() + "'.");
return;
}
if (!this.resource.isReadable()) {
if (this.strict) {
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() + "'.");
return;
}
this.openExcelFile(this.resource);
this.openSheet();
this.noInput = false;
if (logger.isDebugEnabled()) {
logger.debug("Opened workbook [" + this.resource.getFilename() + "] with " + this.getNumberOfSheets() + " sheets.");
}
}
private void openSheet() {
final Sheet sheet = this.getSheet(this.currentSheet);
this.rs =rowSetFactory.create(sheet);
if (logger.isDebugEnabled()) {
logger.debug("Opening sheet " + sheet.getName() + ".");
}
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.");
}
}
/**
* Public setter for the input resource.
*
* @param resource the {@code Resource} pointing to the Excelfile
*/
public void setResource(final Resource resource) {
this.resource = resource;
}
public void afterPropertiesSet() throws Exception {
Assert.notNull(this.rowMapper, "RowMapper must be set");
}
/**
* Set the number of lines to skip. This number is applied to all worksheet
* in the excel file! default to 0
*
* @param linesToSkip number of lines to skip
*/
public void setLinesToSkip(final int linesToSkip) {
this.linesToSkip = linesToSkip;
}
/**
*
* @param sheet the sheet index
* @return the sheet or <code>null</code> when no sheet available.
*/
protected abstract Sheet getSheet(int sheet);
/**
* The number of sheets in the underlying workbook.
*
* @return the number of sheets.
*/
protected abstract int getNumberOfSheets();
/**
*
* @param resource {@code Resource} pointing to the Excel file to read
* @throws Exception when the Excel sheet cannot be accessed
*/
protected abstract void openExcelFile(Resource resource) throws Exception;
/**
* In strict mode the reader will throw an exception on
* {@link #open(org.springframework.batch.item.ExecutionContext)} if the input resource does not exist.
*
* @param strict true by default
*/
public void setStrict(final boolean strict) {
this.strict = strict;
}
/**
* Public setter for the {@code rowMapper}. Used to map a row read from the underlying Excel workbook.
*
* @param rowMapper the {@code RowMapper} to use.
*/
public void setRowMapper(final RowMapper<T> rowMapper) {
this.rowMapper = rowMapper;
}
/**
* Public setter for the <code>rowSetFactory</code>. Used to create a {@code RowSet} implemenation. By default the
* {@code DefaultRowSetFactory} is used.
*
* @param rowSetFactory the {@code RowSetFactory} to use.
*/
public void setRowSetFactory(RowSetFactory rowSetFactory) {
this.rowSetFactory = rowSetFactory;
}
/**
* @param skippedRowsCallback will be called for each one of the initial skipped lines before any items are read.
*/
public void setSkippedRowsCallback(final RowCallbackHandler skippedRowsCallback) {
this.skippedRowsCallback = skippedRowsCallback;
}
}

View File

@@ -1,70 +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
*
* https://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;
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
* simply dependencies to make it is generic as possible.
*
* @author Marten Deinum
* @since 0.5.0
*/
public class ExcelFileParseException extends ParseException {
private final String filename;
private final String sheet;
private final String[] row;
private final int rowNumber;
/**
* 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 rowNumber the row number in the current sheet
* @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) {
super(message, cause);
this.filename = filename;
this.sheet = sheet;
this.rowNumber = rowNumber;
this.row = row;
}
public String getFilename() {
return this.filename;
}
public String getSheet() {
return this.sheet;
}
public int getRowNumber() {
return this.rowNumber;
}
public String[] getRow() {
return this.row;
}
}

View File

@@ -1,55 +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
*
* https://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;
/**
* 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);
/**
* The number of columns in this sheet.
*
* @return number of columns
*/
int getNumberOfColumns();
}

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
*
* https://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.jxl;
import jxl.Workbook;
import jxl.read.biff.WorkbookParser;
import org.springframework.batch.item.excel.AbstractExcelItemReader;
import org.springframework.batch.item.excel.Sheet;
import org.springframework.core.io.Resource;
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}
*
* @param <T> the type
* @author Marten Deinum
* @since 0.5.0
*
* @deprecated since JExcelAPI is an abandoned project (no release since 2009, with serious bugs remaining)
*/
@Deprecated
public class JxlItemReader<T> extends AbstractExcelItemReader<T> {
private Workbook workbook;
public JxlItemReader() {
super();
this.setName(ClassUtils.getShortName(JxlItemReader.class));
}
@Override
protected void openExcelFile(final Resource resource) throws Exception {
this.workbook = WorkbookParser.getWorkbook(resource.getInputStream());
}
@Override
protected void doClose() throws Exception {
if (this.workbook != null) {
this.workbook.close();
}
}
@Override
protected Sheet getSheet(final int sheet) {
if (sheet < this.workbook.getNumberOfSheets()) {
return new JxlSheet(this.workbook.getSheet(sheet));
}
return null;
}
@Override
protected int getNumberOfSheets() {
if (this.workbook == null) {
throw new IllegalStateException("Workbook file not ready for reading!");
}
return this.workbook.getNumberOfSheets();
}
}

View File

@@ -1,88 +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
*
* https://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.jxl;
import jxl.Cell;
import org.springframework.batch.item.excel.Sheet;
/**
* {@link org.springframework.batch.item.excel.Sheet} implementation for JXL.
*
* @author Marten Deinum
* @since 0.5.0
* @deprecated since JExcelAPI is an abandoned project (no release since 2009, with serious bugs remaining)
*/
@Deprecated
public class JxlSheet implements Sheet {
private final jxl.Sheet delegate;
private final int numberOfRows;
private final int numberOfColumns;
private final String name;
/**
* Constructor which takes the delegate sheet.
*
* @param delegate the JXL sheet
*/
JxlSheet(final jxl.Sheet delegate) {
super();
this.delegate = delegate;
this.numberOfRows = this.delegate.getRows();
this.numberOfColumns = this.delegate.getNumberOfImages();
this.name=this.delegate.getName();
}
/**
* {@inheritDoc}
*/
@Override
public int getNumberOfRows() {
return this.numberOfRows;
}
/**
* {@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;
}
}
/**
* {@inheritDoc}
*/
@Override
public String getName() {
return this.name;
}
/**
* {@inheritDoc}
*/
@Override
public int getNumberOfColumns() {
return this.numberOfColumns;
}
}

View File

@@ -1,102 +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
*
* https://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.jxl;
import jxl.Cell;
import jxl.Workbook;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
import java.util.ArrayList;
import java.util.List;
/**
* Class containing utility methods to work with JXL.
*
* @author Marten Deinum
* @since 0.5.0
* @deprecated since JExcelAPI is an abandoned project (no release since 2009, with serious bugs remaining)
*/
@Deprecated
public final class JxlUtils {
/**
* 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)
*/
public static boolean isEmpty(final Cell cell) {
return cell == null || !StringUtils.hasText(cell.getContents());
}
/**
* Check if the given row (Cell[]) is empty. It is considered empty when the row is null, the array is empty or all
* the cells in the row are empty.
*
* @param row to check
* @return true/false
*/
public static boolean isEmpty(final Cell[] row) {
if (ObjectUtils.isEmpty(row)) {
return true;
}
for (final Cell cell : row) {
if (!isEmpty(cell)) {
return false;
}
}
return true;
}
/**
* Check if the given workbook has any sheets.
*
* @param workbook to check
* @return true/false
*/
public static boolean hasSheets(final Workbook workbook) {
return workbook != null && workbook.getNumberOfSheets() > 0;
}
/**
* Extract the content from the given row.
*
* @param row the row
* @return the content as String[]
*/
public static String[] extractContents(final Cell[] row) {
final List<String> values = new ArrayList<String>();
if (!ObjectUtils.isEmpty(row)) {
for (final Cell cell : row) {
if (!isEmpty(cell)) {
values.add(cell.getColumn(), cell.getContents());
} else {
values.add(cell.getColumn(), "");
}
}
}
return values.toArray(new String[values.size()]);
}
}

View File

@@ -1,4 +0,0 @@
/**
* Support classes for the JExcel library.
*/
package org.springframework.batch.item.excel.jxl;

View File

@@ -1,396 +0,0 @@
package org.springframework.batch.item.excel.mapping;
import org.springframework.batch.item.excel.RowMapper;
import org.springframework.batch.item.excel.support.rowset.RowSet;
import org.springframework.batch.support.DefaultPropertyEditorRegistrar;
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.validation.BindException;
import org.springframework.validation.DataBinder;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
/**
* {@link RowMapper} implementation based on bean property paths. The
* {@link RowSet} to be mapped should have field name meta data corresponding
* to bean property paths in an instance of the desired type. The instance is
* created and initialized either by referring to to a prototype object by bean
* name in the enclosing BeanFactory, or by providing a class to instantiate
* reflectively.<br>
* <br>
*
* Nested property paths, including indexed properties in maps and collections,
* can be referenced by the {@link RowSet}names. They will be converted to
* nested bean properties inside the prototype. The {@link RowSet} and the
* prototype are thus tightly coupled by the fields that are available and those
* that can be initialized. If some of the nested properties are optional (e.g.
* collection members) they need to be removed by a post processor.<br>
* <br>
*
* To customize the way that {@link RowSet} values are converted to the
* desired type for injecting into the prototype there are several choices. You
* can inject {@link java.beans.PropertyEditor} instances directly through the
* {@link #setCustomEditors(Map) customEditors} property, or you can override
* the {@link #createBinder(Object)} and {@link #initBinder(DataBinder)}
* methods, or you can provide a custom {@link RowSet} implementation.<br>
* <br>
*
* Property name matching is "fuzzy" in the sense that it tolerates close
* matches, as long as the match is unique. For instance:
*
* <ul>
* <li>Quantity = quantity (field names can be capitalised)</li>
* <li>ISIN = isin (acronyms can be lower case bean property names, as per Java
* Beans recommendations)</li>
* <li>DuckPate = duckPate (capitalisation including camel casing)</li>
* <li>ITEM_ID = itemId (capitalisation and replacing word boundary with
* underscore)</li>
* <li>ORDER.CUSTOMER_ID = order.customerId (nested paths are recursively
* checked)</li>
* </ul>
*
* The algorithm used to match a property name is to start with an exact match
* and then search successively through more distant matches until precisely one
* match is found. If more than one match is found there will be an error.
*
*
* @author Marten Deinum
* @since 0.5.0
*/
public class BeanWrapperRowMapper<T> extends DefaultPropertyEditorRegistrar implements RowMapper<T>, BeanFactoryAware, InitializingBean {
private String name;
private Class<? extends T> type;
private BeanFactory beanFactory;
private ConcurrentMap<DistanceHolder, ConcurrentMap<String, String>> propertiesMatched = new ConcurrentHashMap<DistanceHolder, ConcurrentMap<String, String>>();
private int distanceLimit = 5;
private boolean strict = true;
/*
* (non-Javadoc)
*
* @see
* org.springframework.beans.factory.BeanFactoryAware#setBeanFactory(org
* .springframework.beans.factory.BeanFactory)
*/
@Override
public void setBeanFactory(BeanFactory beanFactory) {
this.beanFactory = beanFactory;
}
/**
* The maximum difference that can be tolerated in spelling between input
* key names and bean property names. Defaults to 5, but could be set lower
* if the field names match the bean names.
*
* @param distanceLimit the distance limit to set
*/
public void setDistanceLimit(int distanceLimit) {
this.distanceLimit = distanceLimit;
}
/**
* The bean name (id) for an object that can be populated from the field set
* that will be passed into {@link #mapRow(org.springframework.batch.item.excel.support.rowset.RowSet)}. Typically a
* prototype scoped bean so that a new instance is returned for each field
* set mapped.
*
* 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(org.springframework.batch.item.excel.support.rowset.RowSet)}.<br>
*
* Either this property or the prototype bean name must be specified, but
* not both.
*
* @param type the type to set
*/
public void setTargetType(Class<? extends T> type) {
this.type = type;
}
/**
* Check that precisely one of type or prototype bean name is specified.
*
* @throws IllegalStateException if neither is set or both properties are
* set.
* @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet()
*/
@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.");
}
/**
* Map the {@link org.springframework.batch.item.file.transform.FieldSet} to an object retrieved from the enclosing Spring
* context, or to a new instance of the required type if no prototype is
* available.
*
* @throws org.springframework.validation.BindException if there is a type conversion or other error (if
* the {@link org.springframework.validation.DataBinder} from {@link #createBinder(Object)} has errors
* after binding).
* @throws org.springframework.beans.NotWritablePropertyException if the {@link org.springframework.batch.item.file.transform.FieldSet} contains a
* field that cannot be mapped to a bean property.
* @see org.springframework.batch.item.file.mapping.FieldSetMapper#mapFieldSet(org.springframework.batch.item.file.transform.FieldSet)
*/
@Override
public T mapRow(RowSet rs) throws BindException {
T copy = getBean();
DataBinder binder = createBinder(copy);
binder.bind(new MutablePropertyValues(getBeanProperties(copy, rs.getProperties())));
if (binder.getBindingResult().hasErrors()) {
throw new BindException(binder.getBindingResult());
}
return copy;
}
/**
* Create a binder for the target object. The binder will then be used to
* bind the properties form a field set into the target object. This
* implementation creates a new {@link DataBinder} and calls out to
* {@link #initBinder(DataBinder)} and
* {@link #registerCustomEditors(org.springframework.beans.PropertyEditorRegistry)}.
*
* @param target the object to bind to.
* @return a {@link DataBinder} that can be used to bind properties to the
* target.
*/
protected DataBinder createBinder(Object target) {
DataBinder binder = new DataBinder(target);
binder.setIgnoreUnknownFields(!this.strict);
initBinder(binder);
registerCustomEditors(binder);
return binder;
}
/**
* Initialize a new binder instance. This hook allows customization of
* binder settings such as the {@link DataBinder#initDirectFieldAccess()
* direct field access}. Called by {@link #createBinder(Object)}.
* <p>
* Note that registration of custom property editors can be done in
* {@link #registerCustomEditors(org.springframework.beans.PropertyEditorRegistry)}.
* </p>
*
* @param binder new binder instance
* @see #createBinder(Object)
*/
protected void initBinder(DataBinder binder) {
}
@SuppressWarnings("unchecked")
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.");
}
/**
* @param bean
* @param properties
* @return
*/
private Properties getBeanProperties(Object bean, Properties properties) {
Class<?> cls = bean.getClass();
// Map from field names to property names
DistanceHolder distanceKey = new DistanceHolder(cls, distanceLimit);
if (!propertiesMatched.containsKey(distanceKey)) {
propertiesMatched.putIfAbsent(distanceKey, new ConcurrentHashMap<String, String>());
}
Map<String, String> matches = new HashMap<String, String>(propertiesMatched.get(distanceKey));
@SuppressWarnings({"unchecked", "rawtypes"})
Set<String> keys = new HashSet(properties.keySet());
for (String key : keys) {
if (matches.containsKey(key)) {
switchPropertyNames(properties, key, matches.get(key));
continue;
}
String name = findPropertyName(bean, key);
if (name != null) {
if (matches.containsValue(name)) {
throw new NotWritablePropertyException(
cls,
name,
"Duplicate match with distance <= "
+ distanceLimit
+ " found for this property in input keys: "
+ keys
+ ". (Consider reducing the distance limit or changing the input key names to get a closer match.)");
}
matches.put(key, name);
switchPropertyNames(properties, key, name);
}
}
propertiesMatched.replace(distanceKey, new ConcurrentHashMap<String, String>(matches));
return properties;
}
private String findPropertyName(Object bean, String key) {
if (bean == null) {
return null;
}
Class<?> cls = bean.getClass();
int index = PropertyAccessorUtils.getFirstNestedPropertySeparatorIndex(key);
String prefix;
String suffix;
// If the property name is nested recurse down through the properties
// looking for a match.
if (index > 0) {
prefix = key.substring(0, index);
suffix = key.substring(index + 1, key.length());
String nestedName = findPropertyName(bean, prefix);
if (nestedName == null) {
return null;
}
Object nestedValue = getPropertyValue(bean, nestedName);
String nestedPropertyName = findPropertyName(nestedValue, suffix);
return nestedPropertyName == null ? null : nestedName + "." + nestedPropertyName;
}
String name = null;
int distance = 0;
index = key.indexOf(PropertyAccessor.PROPERTY_KEY_PREFIX_CHAR);
if (index > 0) {
prefix = key.substring(0, index);
suffix = key.substring(index);
} else {
prefix = key;
suffix = "";
}
while (name == null && distance <= distanceLimit) {
String[] candidates = PropertyMatches.forProperty(prefix, cls, distance).getPossibleMatches();
// If we find precisely one match, then use that one...
if (candidates.length == 1) {
String candidate = candidates[0];
if (candidate.equals(prefix)) { // if it's the same don't
// replace it...
name = key;
} else {
name = candidate + suffix;
}
}
distance++;
}
return name;
}
private Object getPropertyValue(Object bean, String nestedName) {
BeanWrapperImpl wrapper = new BeanWrapperImpl(bean);
wrapper.setAutoGrowNestedPaths(true);
Object nestedValue = wrapper.getPropertyValue(nestedName);
if (nestedValue == null) {
try {
nestedValue = wrapper.getPropertyType(nestedName).newInstance();
wrapper.setPropertyValue(nestedName, nestedValue);
} catch (InstantiationException e) {
ReflectionUtils.handleReflectionException(e);
} catch (IllegalAccessException e) {
ReflectionUtils.handleReflectionException(e);
}
}
return nestedValue;
}
private void switchPropertyNames(Properties properties, String oldName, String newName) {
String value = properties.getProperty(oldName);
properties.remove(oldName);
properties.setProperty(newName, value);
}
/**
* Public setter for the 'strict' property. If true, then
* {@link #mapRow(RowSet)} will fail if the RowSet contains fields
* that cannot be mapped to the bean.
*
* @param strict fail if non-mappable properties are found
*/
public void setStrict(boolean strict) {
this.strict = strict;
}
private static class DistanceHolder {
private final Class<?> cls;
private final int distance;
public DistanceHolder(Class<?> cls, int distance) {
this.cls = cls;
this.distance = distance;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((cls == null) ? 0 : cls.hashCode());
result = prime * result + distance;
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
DistanceHolder other = (DistanceHolder) obj;
if (cls == null) {
if (other.cls != null)
return false;
} else if (!cls.equals(other.cls))
return false;
if (distance != other.distance)
return false;
return true;
}
}
}

View File

@@ -1,175 +0,0 @@
package org.springframework.batch.item.excel.mapping;
import org.springframework.beans.BeanUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
import java.beans.PropertyDescriptor;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* Helper class for calculating bean property matches, according to.
* Used by BeanWrapperImpl to suggest alternatives for an invalid property name.<br>
*
* Copied and slightly modified from Spring core,
*
* @author Alef Arendsen
* @author Arjen Poutsma
* @author Juergen Hoeller
* @author Dave Syer
*
* @since 1.0
* @see #forProperty(String, Class)
*/
final class PropertyMatches {
//---------------------------------------------------------------------
// Static section
//---------------------------------------------------------------------
/** Default maximum property distance: 2 */
public static final int DEFAULT_MAX_DISTANCE = 2;
/**
* Create PropertyMatches for the given bean property.
* @param propertyName the name of the property to find possible matches for
* @param beanClass the bean class to search for matches
*/
public static PropertyMatches forProperty(String propertyName, Class<?> beanClass) {
return forProperty(propertyName, beanClass, DEFAULT_MAX_DISTANCE);
}
/**
* Create PropertyMatches for the given bean property.
* @param propertyName the name of the property to find possible matches for
* @param beanClass the bean class to search for matches
* @param maxDistance the maximum property distance allowed for matches
*/
public static PropertyMatches forProperty(String propertyName, Class<?> beanClass, int maxDistance) {
return new PropertyMatches(propertyName, beanClass, maxDistance);
}
//---------------------------------------------------------------------
// Instance section
//---------------------------------------------------------------------
private final String propertyName;
private String[] possibleMatches;
/**
* Create a new PropertyMatches instance for the given property.
*/
private PropertyMatches(String propertyName, Class<?> beanClass, int maxDistance) {
this.propertyName = propertyName;
this.possibleMatches = calculateMatches(BeanUtils.getPropertyDescriptors(beanClass), maxDistance);
}
/**
* Return the calculated possible matches.
*/
public String[] getPossibleMatches() {
return possibleMatches;
}
/**
* Build an error message for the given invalid property name,
* indicating the possible property matches.
*/
public String buildErrorMessage() {
StringBuffer buf = new StringBuffer();
buf.append("Bean property '");
buf.append(this.propertyName);
buf.append("' is not writable or has an invalid setter method. ");
if (ObjectUtils.isEmpty(this.possibleMatches)) {
buf.append("Does the parameter type of the setter match the return type of the getter?");
}
else {
buf.append("Did you mean ");
for (int i = 0; i < this.possibleMatches.length; i++) {
buf.append('\'');
buf.append(this.possibleMatches[i]);
if (i < this.possibleMatches.length - 2) {
buf.append("', ");
}
else if (i == this.possibleMatches.length - 2){
buf.append("', or ");
}
}
buf.append("'?");
}
return buf.toString();
}
/**
* Generate possible property alternatives for the given property and
* class. Internally uses the <code>getStringDistance</code> method, which
* in turn uses the Levenshtein algorithm to determine the distance between
* two Strings.
* @param propertyDescriptors the JavaBeans property descriptors to search
* @param maxDistance the maximum distance to accept
*/
private String[] calculateMatches(PropertyDescriptor[] propertyDescriptors, int maxDistance) {
List<String> candidates = new ArrayList<String>();
for (int i = 0; i < propertyDescriptors.length; i++) {
if (propertyDescriptors[i].getWriteMethod() != null) {
String possibleAlternative = propertyDescriptors[i].getName();
int distance = calculateStringDistance(this.propertyName, possibleAlternative);
if (distance <= maxDistance) {
candidates.add(possibleAlternative);
}
}
}
Collections.sort(candidates);
return StringUtils.toStringArray(candidates);
}
/**
* Calculate the distance between the given two Strings
* according to the Levenshtein algorithm.
* @param s1 the first String
* @param s2 the second String
* @return the distance value
*/
private int calculateStringDistance(String s1, String s2) {
if (s1.length() == 0) {
return s2.length();
}
if (s2.length() == 0) {
return s1.length();
}
int d[][] = new int[s1.length() + 1][s2.length() + 1];
for (int i = 0; i <= s1.length(); i++) {
d[i][0] = i;
}
for (int j = 0; j <= s2.length(); j++) {
d[0][j] = j;
}
for (int i = 1; i <= s1.length(); i++) {
char s_i = s1.charAt(i - 1);
for (int j = 1; j <= s2.length(); j++) {
int cost;
char t_j = s2.charAt(j - 1);
if (Character.toLowerCase(s_i) == Character.toLowerCase(t_j)) {
cost = 0;
} else {
cost = 1;
}
d[i][j] = Math.min(Math.min(d[i - 1][j] + 1, d[i][j - 1] + 1),
d[i - 1][j - 1] + cost);
}
}
return d[s1.length()][s2.length()];
}
}

View File

@@ -1,4 +0,0 @@
/**
* Default {@code RowMapper} implementations.
*/
package org.springframework.batch.item.excel.mapping;

View File

@@ -1,4 +0,0 @@
/**
* Core interfaces for reading Excel files
*/
package org.springframework.batch.item.excel;

View File

@@ -1,88 +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
*
* https://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.poi;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.ss.usermodel.WorkbookFactory;
import org.springframework.batch.item.excel.AbstractExcelItemReader;
import org.springframework.batch.item.excel.Sheet;
import org.springframework.core.io.Resource;
import java.io.Closeable;
import java.io.InputStream;
import java.io.PushbackInputStream;
/**
* {@link org.springframework.batch.item.ItemReader} implementation which uses apache POI 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}
*
* @param <T> the type
* @author Marten Deinum
* @since 0.5.0
*/
public class PoiItemReader<T> extends AbstractExcelItemReader<T> {
private Workbook workbook;
private InputStream workbookStream;
@Override
protected Sheet getSheet(final int sheet) {
return new PoiSheet(this.workbook.getSheetAt(sheet));
}
@Override
protected int getNumberOfSheets() {
return this.workbook.getNumberOfSheets();
}
@Override
protected void doClose() throws Exception {
// As of Apache POI 3.11 there is a close method on the Workbook, prior version
// lack this method.
if (workbook instanceof Closeable) {
this.workbook.close();
}
if (workbookStream != null) {
workbookStream.close();
}
this.workbook=null;
this.workbookStream=null;
}
/**
* Open the underlying file using the {@code WorkbookFactory}. We keep track of the used {@code InputStream} so that
* it can be closed cleanly on the end of reading the file. This to be able to release the resources used by
* Apache POI.
*
* @param resource the {@code Resource} pointing to the Excel file.
* @throws Exception is thrown for any errors.
*/
@Override
protected void openExcelFile(final Resource resource) throws Exception {
workbookStream = resource.getInputStream();
if (!workbookStream.markSupported() && !(workbookStream instanceof PushbackInputStream)) {
throw new IllegalStateException("InputStream MUST either support mark/reset, or be wrapped as a PushbackInputStream");
}
this.workbook = WorkbookFactory.create(workbookStream);
this.workbook.setMissingCellPolicy(Row.CREATE_NULL_AS_BLANK);
}
}

View File

@@ -1,128 +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
*
* https://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.poi;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.DateUtil;
import org.apache.poi.ss.usermodel.FormulaEvaluator;
import org.apache.poi.ss.usermodel.Row;
import org.springframework.batch.item.excel.Sheet;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;
/**
* Sheet implementation for Apache POI.
*
* @author Marten Deinum
* @since 0.5.0
*/
public class PoiSheet implements Sheet {
private final org.apache.poi.ss.usermodel.Sheet delegate;
private final int numberOfRows;
private final String name;
private int numberOfColumns = -1;
private FormulaEvaluator evaluator;
/**
* Constructor which takes the delegate sheet.
*
* @param delegate the apache POI sheet
*/
PoiSheet(final org.apache.poi.ss.usermodel.Sheet delegate) {
super();
this.delegate = delegate;
this.numberOfRows = this.delegate.getLastRowNum() + 1;
this.name=this.delegate.getSheetName();
}
/**
* {@inheritDoc}
*/
@Override
public int getNumberOfRows() {
return this.numberOfRows;
}
/**
* {@inheritDoc}
*/
@Override
public String getName() {
return this.name;
}
/**
* {@inheritDoc}
*/
@Override
public String[] getRow(final int rowNumber) {
final Row row = this.delegate.getRow(rowNumber);
if (row == null) {
return null;
}
final List<String> cells = new LinkedList<String>();
for (int i = 0; i < getNumberOfColumns(); i++) {
Cell cell = row.getCell(i);
switch (cell.getCellType()) {
case Cell.CELL_TYPE_NUMERIC:
if (DateUtil.isCellDateFormatted(cell)) {
Date date = cell.getDateCellValue();
cells.add(String.valueOf(date.getTime()));
} else {
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:
cells.add(getFormulaEvaluator().evaluate(cell).formatAsString());
break;
default:
throw new IllegalArgumentException("Cannot handle cells of type " + cell.getCellType());
}
}
return cells.toArray(new String[cells.size()]);
}
private FormulaEvaluator getFormulaEvaluator() {
if (this.evaluator == null) {
this.evaluator = delegate.getWorkbook().getCreationHelper().createFormulaEvaluator();
}
return this.evaluator;
}
/**
* {@inheritDoc}
*/
@Override
public int getNumberOfColumns() {
if (numberOfColumns < 0) {
numberOfColumns = this.delegate.getRow(0).getLastCellNum();
}
return numberOfColumns;
}
}

View File

@@ -1,4 +0,0 @@
/**
* Support classes for the Apache POI library.
*/
package org.springframework.batch.item.excel.poi;

View File

@@ -1,90 +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
*
* https://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;
}
@Override
public int getCurrentRowIndex() {
return this.currentRowIndex;
}
@Override
public String[] getCurrentRow() {
return this.currentRow;
}
@Override
public String getColumnValue(int idx) {
return currentRow[idx];
}
@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

@@ -1,42 +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
*
* https://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

@@ -1,63 +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
*
* https://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

@@ -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
*
* https://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 java.util.Properties;
/**
* 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 RowSet {
/**
* Retrieves the meta data (name of the sheet, number of columns, names) of this row set.
*
* @return a corresponding {@code RowSetMetaData} instance.
*/
RowSetMetaData getMetaData();
/**
* Move to the next row in the document.
*
* @return true if the row is valid, false if there are no more rows
*/
boolean next();
/**
* Returns the current row number
*
* @return the current row number
*/
int getCurrentRowIndex();
/**
* Return the current row as a String[].
*
* @return the row as a String[]
*/
String[] getCurrentRow();
/**
* Retrieves the value of the indicated column in the current row as a String object.
*
* @param idx the column index, 0 based
* @return a String objeect respresenting the column value.
*/
String getColumnValue(int 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.
*/
Properties getProperties();
}

View File

@@ -1,54 +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
*
* https://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;
/**
* Interface representing the the metadata associated with an Excel document.
*
* @author Marten Deinum
* @since 0.5.0
*/
public interface RowSetMetaData {
/**
* Retrieves the names of the columns for the current sheet.
*
* @return the column names.
*/
String[] getColumnNames();
/**
* Retrieves the column name for the indicatd column.
*
* @param idx the index of the column, 0 based
* @return the column name
*/
String getColumnName(int idx);
/**
* Retrieves the number of columns in the RowSet.
*
* @return the number of columns
*/
int getColumnCount();
/**
* Retrieves the name of the sheet the RowSet is based on.
*
* @return the name of the sheet
*/
String getSheetName();
}

View File

@@ -1,4 +0,0 @@
/**
* RowSet abstraction for Excel documents
*/
package org.springframework.batch.item.excel.support.rowset;

View File

@@ -0,0 +1,95 @@
/*
* Copyright 2002-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
*
* https://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.extensions.excel;
import java.util.Arrays;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.batch.extensions.excel.mapping.PassThroughRowMapper;
import org.springframework.batch.item.ExecutionContext;
import org.springframework.core.io.ClassPathResource;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
/**
* Base class for testing Excel based item readers.
*
* @author Marten Deinum
* @since 0.1.0
*/
public abstract class AbstractExcelItemReaderTests {
protected final Log logger = LogFactory.getLog(this.getClass());
protected AbstractExcelItemReader<String[]> itemReader;
@BeforeEach
public void setup() throws Exception {
this.itemReader = createExcelItemReader();
this.itemReader.setLinesToSkip(1); // First line is column names
this.itemReader.setResource(new ClassPathResource("player.xls"));
this.itemReader.setRowMapper(new PassThroughRowMapper());
this.itemReader.setSkippedRowsCallback((rs) -> this.logger.info("Skipping: " + Arrays.toString(rs.getCurrentRow())));
configureItemReader(this.itemReader);
this.itemReader.afterPropertiesSet();
ExecutionContext executionContext = new ExecutionContext();
this.itemReader.open(executionContext);
}
protected void configureItemReader(AbstractExcelItemReader<String[]> itemReader) {
}
@AfterEach
public void after() {
this.itemReader.close();
}
@Test
public void readExcelFile() throws Exception {
assertThat(this.itemReader.getNumberOfSheets()).isEqualTo(3);
String[] row;
do {
row = this.itemReader.read();
if (this.logger.isTraceEnabled()) {
this.logger.trace("Read: " + Arrays.toString(row));
}
if (row != null) {
assertThat(row).hasSize(6);
}
}
while (row != null);
Integer readCount = (Integer) ReflectionTestUtils.getField(this.itemReader, "currentItemCount");
assertThat(readCount).isEqualTo(4321);
}
@Test
public void testRequiredProperties() {
assertThatThrownBy(() -> {
final AbstractExcelItemReader<String[]> reader = createExcelItemReader();
reader.afterPropertiesSet();
}).isInstanceOf(IllegalArgumentException.class);
}
protected abstract AbstractExcelItemReader<String[]> createExcelItemReader();
}

View File

@@ -0,0 +1,91 @@
/*
* Copyright 2002-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
*
* https://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.extensions.excel;
import java.util.ArrayList;
import java.util.List;
import org.assertj.core.api.SoftAssertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.batch.extensions.excel.mapping.BeanWrapperRowMapper;
import org.springframework.batch.item.ExecutionContext;
import static org.assertj.core.api.Assertions.assertThat;
/**
* @author Marten Deinum
* @since 0.1.0
*/
public class BeanPropertyItemReaderTest {
private MockExcelItemReader<Player> reader;
@BeforeEach
public void setup() throws Exception {
ExecutionContext executionContext = new ExecutionContext();
List<String[]> rows = new ArrayList<>();
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);
this.reader = new MockExcelItemReader<>(sheet);
BeanWrapperRowMapper<Player> rowMapper = new BeanWrapperRowMapper<>();
rowMapper.setTargetType(Player.class);
rowMapper.afterPropertiesSet();
this.reader.setLinesToSkip(1); // Skip first row as that is the header
this.reader.setRowMapper(rowMapper);
this.reader.afterPropertiesSet();
this.reader.open(executionContext);
}
@Test
public void readandMapPlayers() throws Exception {
Player p1 = this.reader.read();
Player p2 = this.reader.read();
Player p3 = this.reader.read();
assertThat(p1).isNotNull();
assertThat(p2).isNotNull();
assertThat(p3).isNull();
SoftAssertions softly = new SoftAssertions();
// Check first player
softly.assertThat(p1.getId()).isEqualTo("AbduKa00");
softly.assertThat("Abdul-Jabbar").isEqualTo(p1.getLastName());
softly.assertThat("Karim").isEqualTo(p1.getFirstName());
softly.assertThat("rb").isEqualTo(p1.getPosition());
softly.assertThat(1974).isEqualTo(p1.getBirthYear());
softly.assertThat(1996).isEqualTo(p1.getDebutYear());
// Check second player
softly.assertThat("AbduRa00").isEqualTo(p2.getId());
softly.assertThat("Abdullah").isEqualTo(p2.getLastName());
softly.assertThat("Rabih").isEqualTo(p2.getFirstName());
softly.assertThat("rb").isEqualTo(p2.getPosition());
softly.assertThat(1975).isEqualTo(p2.getBirthYear());
softly.assertThat(1999).isEqualTo(p2.getDebutYear());
softly.assertAll();
}
}

View File

@@ -0,0 +1,96 @@
/*
* Copyright 2006-2021 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
*
* https://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.extensions.excel;
import java.util.ArrayList;
import java.util.List;
import org.assertj.core.api.SoftAssertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.batch.extensions.excel.mapping.BeanWrapperRowMapper;
import org.springframework.batch.extensions.excel.support.rowset.DefaultRowSetFactory;
import org.springframework.batch.extensions.excel.support.rowset.StaticColumnNameExtractor;
import org.springframework.batch.item.ExecutionContext;
import static org.assertj.core.api.Assertions.assertThat;
/**
* @author Marten Deinum
* @since 0.1.0
*/
public class BeanPropertyWithStaticHeaderItemReaderTest {
private MockExcelItemReader<Player> reader;
@BeforeEach
public void setup() throws Exception {
ExecutionContext executionContext = new ExecutionContext();
List<String[]> rows = new ArrayList<>();
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);
this.reader = new MockExcelItemReader<>(sheet);
BeanWrapperRowMapper<Player> rowMapper = new BeanWrapperRowMapper<>();
rowMapper.setTargetType(Player.class);
rowMapper.afterPropertiesSet();
this.reader.setRowMapper(rowMapper);
DefaultRowSetFactory factory = new DefaultRowSetFactory();
factory.setColumnNameExtractor(new StaticColumnNameExtractor(
new String[] { "id", "lastName", "firstName", "position", "birthYear", "debutYear" }));
this.reader.setRowSetFactory(factory);
this.reader.afterPropertiesSet();
this.reader.open(executionContext);
}
@Test
public void readandMapPlayers() throws Exception {
Player p1 = this.reader.read();
Player p2 = this.reader.read();
Player p3 = this.reader.read();
assertThat(p1).isNotNull();
assertThat(p2).isNotNull();
assertThat(p3).isNull();
SoftAssertions softly = new SoftAssertions();
// Check first player
softly.assertThat(p1.getId()).isEqualTo("AbduKa00");
softly.assertThat("Abdul-Jabbar").isEqualTo(p1.getLastName());
softly.assertThat("Karim").isEqualTo(p1.getFirstName());
softly.assertThat("rb").isEqualTo(p1.getPosition());
softly.assertThat(1974).isEqualTo(p1.getBirthYear());
softly.assertThat(1996).isEqualTo(p1.getDebutYear());
// Check second player
softly.assertThat("AbduRa00").isEqualTo(p2.getId());
softly.assertThat("Abdullah").isEqualTo(p2.getLastName());
softly.assertThat("Rabih").isEqualTo(p2.getFirstName());
softly.assertThat("rb").isEqualTo(p2.getPosition());
softly.assertThat(1975).isEqualTo(p2.getBirthYear());
softly.assertThat(1999).isEqualTo(p2.getDebutYear());
softly.assertAll();
}
}

View File

@@ -0,0 +1,62 @@
/*
* Copyright 2006-2021 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
*
* https://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.extensions.excel;
import java.util.Collections;
import java.util.List;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.core.io.Resource;
/**
* @author Marten Deinum
* @since 0.1.0
*/
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 this.sheets.get(sheet);
}
@Override
protected int getNumberOfSheets() {
return this.sheets.size();
}
@Override
protected void openExcelFile(Resource resource, String password) throws Exception {
}
@Override
protected void doClose() throws Exception {
this.sheets.clear();
}
}

View File

@@ -0,0 +1,64 @@
/*
* Copyright 2006-2021 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
*
* https://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.extensions.excel;
import java.util.Iterator;
import java.util.List;
/**
* Sheet implementation usable for testing. Works in an {@code List} of {@xode String[]}.
*
* @author Marten Deinum
* @since 0.1.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 this.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 Iterator<String[]> iterator() {
return this.rows.iterator();
}
}

View File

@@ -0,0 +1,95 @@
/*
* Copyright 2002-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
*
* https://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.extensions.excel;
/**
* @author Marten Deinum
* @since 0.1.0
*/
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 this.id;
}
public void setId(String id) {
this.id = id;
}
public String getPosition() {
return this.position;
}
public void setPosition(String position) {
this.position = position;
}
public String getLastName() {
return this.lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getFirstName() {
return this.firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public long getBirthYear() {
return this.birthYear;
}
public void setBirthYear(long birthYear) {
this.birthYear = birthYear;
}
public int getDebutYear() {
return this.debutYear;
}
public void setDebutYear(int debutYear) {
this.debutYear = debutYear;
}
public String getComment() {
return this.comment;
}
public void setComment(String comment) {
this.comment = comment;
}
}

View File

@@ -0,0 +1,49 @@
/*
* Copyright 2006-2021 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
*
* https://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.extensions.excel;
import java.lang.reflect.Field;
import org.springframework.lang.Nullable;
import org.springframework.util.ReflectionUtils;
/**
* Simplified version of {@code ReflectionTestUtils} from Spring. This to prevent a
* unneeded dependency on the Spring Test module.
*
* @author Marten Deinum
* @since 0.1.0
*/
public final class ReflectionTestUtils {
private ReflectionTestUtils() { }
@Nullable
public static Object getField(Object targetObject, String name) {
Class<?> targetClass = targetObject.getClass();
Field field = ReflectionUtils.findField(targetClass, name);
if (field == null) {
throw new IllegalArgumentException(String.format("Could not find field '%s' on %s or target class [%s]",
name, targetObject, targetClass));
}
ReflectionUtils.makeAccessible(field);
return ReflectionUtils.getField(field, targetObject);
}
}

View File

@@ -0,0 +1,129 @@
/*
* Copyright 2002-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
*
* https://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.extensions.excel.mapping;
import java.util.ArrayList;
import java.util.List;
import org.assertj.core.api.Assertions;
import org.assertj.core.api.SoftAssertions;
import org.junit.jupiter.api.Test;
import org.springframework.batch.extensions.excel.MockSheet;
import org.springframework.batch.extensions.excel.Player;
import org.springframework.batch.extensions.excel.support.rowset.DefaultRowSetFactory;
import org.springframework.batch.extensions.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;
/**
* @author Marten Deinum
* @since 0.1.0
*/
public class BeanWrapperRowMapperTest {
@Test
public void givenNoNameWhenInitCompleteThenIllegalStateShouldOccur() {
Assertions.assertThatThrownBy(() -> {
BeanWrapperRowMapper<Player> mapper = new BeanWrapperRowMapper<>();
mapper.afterPropertiesSet();
}).isInstanceOf(IllegalStateException.class);
}
@Test
public void givenAValidRowWhenMappingThenAValidPlayerShouldBeConstructed() throws Exception {
BeanWrapperRowMapper<Player> mapper = new BeanWrapperRowMapper<>();
mapper.setTargetType(Player.class);
mapper.afterPropertiesSet();
List<String[]> rows = new ArrayList<>();
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);
SoftAssertions softly = new SoftAssertions();
softly.assertThat(p).isNotNull();
softly.assertThat(p.getId()).isEqualTo("AbduKa00");
softly.assertThat("Abdul-Jabbar").isEqualTo(p.getLastName());
softly.assertThat("Karim").isEqualTo(p.getFirstName());
softly.assertThat("rb").isEqualTo(p.getPosition());
softly.assertThat(1974).isEqualTo(p.getBirthYear());
softly.assertThat(1996).isEqualTo(p.getDebutYear());
softly.assertThat(p.getComment()).isNull();
softly.assertAll();
}
@Test
public void givenAValidRowWhenMappingThenAValidPlayerShouldBeConstructedBasedOnPrototype() throws Exception {
ApplicationContext ctx = new AnnotationConfigApplicationContext(TestConfig.class);
BeanWrapperRowMapper<Player> mapper = ctx.getBean("playerRowMapper", BeanWrapperRowMapper.class);
List<String[]> rows = new ArrayList<>();
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);
SoftAssertions softly = new SoftAssertions();
softly.assertThat(p).isNotNull();
softly.assertThat(p.getId()).isEqualTo("AbduKa00");
softly.assertThat("Abdul-Jabbar").isEqualTo(p.getLastName());
softly.assertThat("Karim").isEqualTo(p.getFirstName());
softly.assertThat("rb").isEqualTo(p.getPosition());
softly.assertThat(1974).isEqualTo(p.getBirthYear());
softly.assertThat(1996).isEqualTo(p.getDebutYear());
softly.assertThat(p.getComment()).isEqualTo("comment from context");
softly.assertAll();
}
@Configuration
public static class TestConfig {
@Bean
public BeanWrapperRowMapper<Player> playerRowMapper() {
BeanWrapperRowMapper<Player> mapper = new BeanWrapperRowMapper<>();
mapper.setPrototypeBeanName("player");
return mapper;
}
@Bean
@Scope("prototype")
public Player player() {
Player p = new Player();
p.setComment("comment from context");
return p;
}
}
}

View File

@@ -0,0 +1,73 @@
/*
* Copyright 2002-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
*
* https://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.extensions.excel.poi;
import java.util.Arrays;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.batch.extensions.excel.ReflectionTestUtils;
import org.springframework.batch.extensions.excel.mapping.PassThroughRowMapper;
import org.springframework.batch.item.ExecutionContext;
import org.springframework.core.io.ClassPathResource;
import static org.assertj.core.api.Assertions.assertThat;
/**
* @author mishrk3
* @author Marten Deinum
* @since 0.1.0
*/
public class PoiItemReaderWithBlankRowSheetTest {
private final Log logger = LogFactory.getLog(this.getClass());
private PoiItemReader<String[]> itemReader;
@BeforeEach
public void setup() throws Exception {
this.itemReader = new PoiItemReader<>();
this.itemReader.setResource(new ClassPathResource("blankRow.xlsx"));
this.itemReader.setLinesToSkip(1); // First line is column names
this.itemReader.setRowMapper(new PassThroughRowMapper());
this.itemReader.setSkippedRowsCallback((rs) -> this.logger.info("Skipping: " + Arrays.toString(rs.getCurrentRow())));
this.itemReader.afterPropertiesSet();
ExecutionContext executionContext = new ExecutionContext();
this.itemReader.open(executionContext);
}
@Test
public void readExcelFileWithBlankRow() throws Exception {
assertThat(this.itemReader.getNumberOfSheets()).isEqualTo(1);
String[] row;
do {
row = this.itemReader.read();
this.logger.debug("Read: " + Arrays.toString(row));
if (row != null) {
assertThat(row).hasSize(4);
}
}
while (row != null);
Integer readCount = (Integer) ReflectionTestUtils.getField(this.itemReader, "currentItemCount");
assertThat(readCount).isEqualTo(7);
}
}

View File

@@ -0,0 +1,77 @@
/*
* Copyright 2002-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
*
* https://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.extensions.excel.poi;
import java.util.Arrays;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.poi.ss.usermodel.FormulaError;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.batch.extensions.excel.ReflectionTestUtils;
import org.springframework.batch.extensions.excel.mapping.PassThroughRowMapper;
import org.springframework.batch.item.ExecutionContext;
import org.springframework.core.io.ClassPathResource;
import static org.assertj.core.api.Assertions.assertThat;
/**
* @author Marten Deinum
* @since 0.1.0
*/
public class PoiItemReaderWithErrorsTest {
private final Log logger = LogFactory.getLog(this.getClass());
private PoiItemReader<String[]> itemReader;
@BeforeEach
public void setup() throws Exception {
this.itemReader = new PoiItemReader<>();
this.itemReader.setResource(new ClassPathResource("errors.xlsx"));
this.itemReader.setLinesToSkip(1); // First line is column names
this.itemReader.setRowMapper(new PassThroughRowMapper());
this.itemReader.setSkippedRowsCallback((rs) -> this.logger.info("Skipping: " + Arrays.toString(rs.getCurrentRow())));
this.itemReader.afterPropertiesSet();
ExecutionContext executionContext = new ExecutionContext();
this.itemReader.open(executionContext);
}
@Test
public void readExcelFileWithBlankRow() throws Exception {
assertThat(this.itemReader.getNumberOfSheets()).isEqualTo(1);
String[] row;
String[] lastRow = null;
do {
row = this.itemReader.read();
this.logger.debug("Read: " + Arrays.toString(row));
if (row != null) {
lastRow = row;
assertThat(row).hasSize(3);
}
}
while (row != null);
Integer readCount = (Integer) ReflectionTestUtils.getField(this.itemReader, "currentItemCount");
assertThat(readCount).isEqualTo(3);
assertThat(lastRow[2]).isEqualTo(FormulaError.DIV0.getString());
}
}

View File

@@ -0,0 +1,39 @@
/*
* Copyright 2002-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
*
* https://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.extensions.excel.poi;
import org.springframework.batch.extensions.excel.AbstractExcelItemReader;
import org.springframework.batch.extensions.excel.AbstractExcelItemReaderTests;
import org.springframework.core.io.ClassPathResource;
/**
* @author Marten Deinum
* @since 0.1.0
*/
public class PoiItemReaderXlsPasswordTests extends AbstractExcelItemReaderTests {
@Override
protected void configureItemReader(AbstractExcelItemReader<String[]> itemReader) {
itemReader.setResource(new ClassPathResource("player_with_password.xls"));
itemReader.setPassword("readme");
}
@Override
protected AbstractExcelItemReader<String[]> createExcelItemReader() {
return new PoiItemReader<>();
}
}

View File

@@ -0,0 +1,33 @@
/*
* Copyright 2002-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
*
* https://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.extensions.excel.poi;
import org.springframework.batch.extensions.excel.AbstractExcelItemReader;
import org.springframework.batch.extensions.excel.AbstractExcelItemReaderTests;
/**
* @author Marten Deinum
* @since 0.1.0
*/
public class PoiItemReaderXlsTests extends AbstractExcelItemReaderTests {
@Override
protected AbstractExcelItemReader<String[]> createExcelItemReader() {
return new PoiItemReader<>();
}
}

View File

@@ -0,0 +1,39 @@
/*
* Copyright 2002-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
*
* https://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.extensions.excel.poi;
import org.springframework.batch.extensions.excel.AbstractExcelItemReader;
import org.springframework.batch.extensions.excel.AbstractExcelItemReaderTests;
import org.springframework.core.io.ClassPathResource;
/**
* @author Marten Deinum
* @since 0.1.0
*/
public class PoiItemReaderXlsxTests extends AbstractExcelItemReaderTests {
@Override
protected void configureItemReader(AbstractExcelItemReader<String[]> itemReader) {
itemReader.setResource(new ClassPathResource("player.xlsx"));
}
@Override
protected AbstractExcelItemReader<String[]> createExcelItemReader() {
return new PoiItemReader<>();
}
}

View File

@@ -0,0 +1,39 @@
/*
* Copyright 2006-2021 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
*
* https://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.extensions.excel.streaming;
import org.springframework.batch.extensions.excel.AbstractExcelItemReader;
import org.springframework.batch.extensions.excel.AbstractExcelItemReaderTests;
import org.springframework.core.io.ClassPathResource;
/**
* @author Marten Deinum
* @since 0.1.0
*/
class StreamingXlsxItemReaderTest extends AbstractExcelItemReaderTests {
@Override
protected void configureItemReader(AbstractExcelItemReader<String[]> itemReader) {
itemReader.setResource(new ClassPathResource("player.xlsx"));
}
@Override
protected AbstractExcelItemReader<String[]> createExcelItemReader() {
return new StreamingXlsxItemReader<>();
}
}

View File

@@ -0,0 +1,80 @@
/*
* Copyright 2002-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
*
* https://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.extensions.excel.support.rowset;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
import org.springframework.batch.extensions.excel.Sheet;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
/**
* Tests for {@link DefaultRowSetMetaData}
*
* @author Marten Deinum
* @since 0.1.0
*/
public class DefaultRowSetMetaDataTest {
private static final String[] COLUMNS = { "col1", "col2", "col3" };
private DefaultRowSetMetaData rowSetMetaData;
private Sheet sheet;
private ColumnNameExtractor columnNameExtractor;
@BeforeEach
public void setup() {
this.sheet = Mockito.mock(Sheet.class);
this.columnNameExtractor = Mockito.mock(ColumnNameExtractor.class);
this.rowSetMetaData = new DefaultRowSetMetaData(this.sheet, this.columnNameExtractor);
}
@Test
public void shouldReturnColumnsFromColumnNameExtractor() {
given(this.columnNameExtractor.getColumnNames(this.sheet)).willReturn(COLUMNS);
String[] names = this.rowSetMetaData.getColumnNames();
assertThat(names).isEqualTo(new String[] { "col1", "col2", "col3" });
verify(this.columnNameExtractor, times(1)).getColumnNames(this.sheet);
verifyNoMoreInteractions(this.sheet, this.columnNameExtractor);
}
@Test
public void shouldGetAndReturnNameOfTheSheet() {
given(this.sheet.getName()).willReturn("testing123");
String name = this.rowSetMetaData.getSheetName();
assertThat(name).isEqualTo("testing123");
verify(this.sheet, times(1)).getName();
verifyNoMoreInteractions(this.sheet);
}
}

View File

@@ -0,0 +1,52 @@
/*
* Copyright 2006-2021 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
*
* https://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.extensions.excel.support.rowset;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
/**
* @author Marten Deinum
* @since 0.1.0
*/
public class StaticColumnNameExtractorTest {
private static final String[] COLUMNS = { "col1", "col2", "col3", "foo", "bar" };
@Test
public void shouldReturnSameHeadersAsPassedIn() {
StaticColumnNameExtractor columnNameExtractor = new StaticColumnNameExtractor(COLUMNS);
String[] names = columnNameExtractor.getColumnNames(null);
assertThat(names)
.isEqualTo(new String[] { "col1", "col2", "col3", "foo", "bar" })
.isNotSameAs(COLUMNS);
}
@Test
public void shouldReturnACopyOfTheHeaders() {
StaticColumnNameExtractor columnNameExtractor = new StaticColumnNameExtractor(COLUMNS);
String[] names = columnNameExtractor.getColumnNames(null);
assertThat(names)
.isEqualTo(new String[] { "col1", "col2", "col3", "foo", "bar" })
.isNotSameAs(COLUMNS);
}
}

View File

@@ -1,71 +0,0 @@
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

@@ -1,94 +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
*
* https://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;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.junit.After;
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;
import static org.junit.Assert.assertEquals;
/**
* Base class for testing Excel based item readers.
*
* @author Marten Deinum
*/
public abstract class AbstractExcelItemReaderTests {
protected final Log logger = LogFactory.getLog(this.getClass());
protected AbstractExcelItemReader itemReader;
private ExecutionContext executionContext;
@Before
public void setup() throws Exception {
this.itemReader = createExcelItemReader();
this.itemReader.setLinesToSkip(1); //First line is column names
this.itemReader.setResource(new ClassPathResource("org/springframework/batch/item/excel/player.xls"));
this.itemReader.setRowMapper(new PassThroughRowMapper());
this.itemReader.setSkippedRowsCallback(new RowCallbackHandler() {
public void handleRow(RowSet rs) {
logger.info("Skipping: " + StringUtils.arrayToCommaDelimitedString(rs.getCurrentRow()));
}
});
configureItemReader(this.itemReader);
this.itemReader.afterPropertiesSet();
executionContext = new ExecutionContext();
this.itemReader.open(executionContext);
}
protected void configureItemReader(AbstractExcelItemReader itemReader) {
}
@After
public void after() throws Exception {
this.itemReader.close();
}
@Test
public void readExcelFile() throws Exception {
assertEquals(3, this.itemReader.getNumberOfSheets());
String[] row;
do {
row = (String[]) this.itemReader.read();
this.logger.debug("Read: " + StringUtils.arrayToCommaDelimitedString(row));
if (row != null) {
assertEquals(6, row.length);
}
} while (row != null);
int readCount = (Integer) ReflectionTestUtils.getField(this.itemReader, "currentItemCount" );
assertEquals(4321, readCount);
}
@Test(expected = IllegalArgumentException.class)
public void testRequiredProperties() throws Exception {
final AbstractExcelItemReader reader = createExcelItemReader();
reader.afterPropertiesSet();
}
protected abstract AbstractExcelItemReader createExcelItemReader();
}

View File

@@ -1,71 +0,0 @@
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.BeanWrapperRowMapper;
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);
BeanWrapperRowMapper<Player> rowMapper = new BeanWrapperRowMapper<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

@@ -1,74 +0,0 @@
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.BeanWrapperRowMapper;
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);
BeanWrapperRowMapper<Player> rowMapper = new BeanWrapperRowMapper<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

@@ -1,45 +0,0 @@
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 {
}
@Override
protected void doClose() throws Exception {
sheets.clear();
}
}

View File

@@ -1,50 +0,0 @@
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

@@ -1,90 +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
*
* https://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.jxl;
import jxl.Cell;
import jxl.Workbook;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mockito;
/**
* Tests for {@link org.springframework.batch.item.excel.jxl.JxlUtils}.
*
* @author Marten Deinum
*
*/
public class JxlUtilsTests {
private final Cell cell1 = Mockito.mock(Cell.class);
private final Cell cell2 = Mockito.mock(Cell.class);
private final Cell cell3 = Mockito.mock(Cell.class);
private final Cell cell4 = Mockito.mock(Cell.class);
private final Workbook workbook = Mockito.mock(Workbook.class);
@Before
public void setup() {
Mockito.when(this.cell1.getContents()).thenReturn("foo");
Mockito.when(this.cell2.getContents()).thenReturn(" ");
Mockito.when(this.cell3.getContents()).thenReturn("");
Mockito.when(this.cell4.getContents()).thenReturn(null);
}
/**
* Test the {@link org.springframework.batch.item.excel.jxl.JxlUtils#isEmpty( jxl.Cell)} method.
*/
@Test
public void checkIfCellsAreEmpty() {
Assert.assertFalse("Cell[1] should not be empty", JxlUtils.isEmpty(this.cell1));
Assert.assertTrue("Cell[2] should be empty", JxlUtils.isEmpty(this.cell2));
Assert.assertTrue("Cell[3] should be empty", JxlUtils.isEmpty(this.cell3));
Assert.assertTrue("Cell[4] should be empty", JxlUtils.isEmpty(this.cell4));
Assert.assertTrue("[null] should be empty", JxlUtils.isEmpty((Cell) null));
}
/**
* Test the {@link JxlUtils#isEmpty( jxl.Cell[])} method.
*/
@Test
public void checkIfRowIsEmpty() {
Assert.assertTrue("[null] should be empty", JxlUtils.isEmpty((Cell[]) null));
Assert.assertTrue("[null] should be empty", JxlUtils.isEmpty(new Cell[0]));
Assert.assertFalse("Cell[1] should not be empty",
JxlUtils.isEmpty(new Cell[]{this.cell1, this.cell2, this.cell3}));
Assert.assertTrue("Cell[2] should be empty", JxlUtils.isEmpty(new Cell[]{this.cell2, this.cell3, null}));
}
/**
* Test the {@link JxlUtils#hasSheets( jxl.Workbook)} method.
*/
@Test
public void checkIfWorkbookHasSheets() {
Assert.assertFalse("[null] doesn't have sheets.", JxlUtils.hasSheets(null));
Mockito.when(this.workbook.getNumberOfSheets()).thenReturn(5);
Assert.assertTrue("Workbook should have sheets.", JxlUtils.hasSheets(this.workbook));
Mockito.when(this.workbook.getNumberOfSheets()).thenReturn(0);
Assert.assertFalse("Workbook shouldn't have sheets.", JxlUtils.hasSheets(this.workbook));
}
@Test
public void extractingContent() {
Assert.assertTrue("[null] should give empty array", JxlUtils.extractContents(null).length == 0);
}
}

View File

@@ -1,100 +0,0 @@
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.*;
/**
* @author Marten Deinum
* @since 0.5.0
*/
public class BeanWrapperRowMapperTest {
@Test(expected = IllegalStateException.class)
public void givenNoNameWhenInitCompleteThenIllegalStateShouldOccur() throws Exception {
BeanWrapperRowMapper mapper = new BeanWrapperRowMapper();
mapper.afterPropertiesSet();
}
@Test
public void givenAValidRowWhenMappingThenAValidPlayerShouldBeConstructed() throws Exception {
BeanWrapperRowMapper<Player> mapper = new BeanWrapperRowMapper<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);
BeanWrapperRowMapper<Player> mapper = new BeanWrapperRowMapper<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,47 +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
*
* https://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.Test;
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.assertTrue;
/**
* Tests for {@link PassThroughRowMapper}.
*
* @author Marten Deinum
*/
public class PassThroughRowMapperTest {
private final PassThroughRowMapper rowMapper = new PassThroughRowMapper();
@Test
public void mapRowShouldReturnSameValues() throws Exception {
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

@@ -1,30 +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
*
* https://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.poi;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.batch.item.excel.AbstractExcelItemReader;
import org.springframework.batch.item.excel.AbstractExcelItemReaderTests;
public class PoiItemReaderXlsTest extends AbstractExcelItemReaderTests {
@Override
protected AbstractExcelItemReader createExcelItemReader() {
return new PoiItemReader();
}
}

View File

@@ -1,33 +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
*
* https://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.poi;
import org.springframework.batch.item.excel.AbstractExcelItemReader;
import org.springframework.batch.item.excel.AbstractExcelItemReaderTests;
import org.springframework.core.io.ClassPathResource;
public class PoiItemReaderXlsxTest extends AbstractExcelItemReaderTests {
@Override
protected void configureItemReader(AbstractExcelItemReader itemReader) {
itemReader.setResource(new ClassPathResource("org/springframework/batch/item/excel/player.xlsx"));
}
@Override
protected AbstractExcelItemReader createExcelItemReader() {
return new PoiItemReader();
}
}

Binary file not shown.

Binary file not shown.

View File

@@ -1,37 +0,0 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!--
~ 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
~
~ https://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.
-->
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
<log4j:configuration debug="true"
xmlns:log4j='http://jakarta.apache.org/log4j/'>
<appender name="console" class="org.apache.log4j.ConsoleAppender">
<layout class="org.apache.log4j.PatternLayout">
<param name="ConversionPattern" value="%d %p %t [%c] - %m %n"/>
</layout>
</appender>
<category name="org.springframework.batch">
<level value="DEBUG"/>
</category>
<root>
<level value="INFO"/>
<appender-ref ref="console"/>
</root>
</log4j:configuration>

View File

@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN">
<Appenders>
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
</Console>
</Appenders>
<Loggers>
<Root level="debug">
<AppenderRef ref="Console"/>
</Root>
</Loggers>
</Configuration>

Binary file not shown.