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:
committed by
Mahmoud Ben Hassine
parent
e40d1df90a
commit
9bb5c79290
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
@@ -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 {
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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()];
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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()]);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
/**
|
||||
* Support classes for the JExcel library.
|
||||
*/
|
||||
package org.springframework.batch.item.excel.jxl;
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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()];
|
||||
}
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
/**
|
||||
* Default {@code RowMapper} implementations.
|
||||
*/
|
||||
package org.springframework.batch.item.excel.mapping;
|
||||
@@ -1,4 +0,0 @@
|
||||
/**
|
||||
* Core interfaces for reading Excel files
|
||||
*/
|
||||
package org.springframework.batch.item.excel;
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
/**
|
||||
* Support classes for the Apache POI library.
|
||||
*/
|
||||
package org.springframework.batch.item.excel.poi;
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
/**
|
||||
* RowSet abstraction for Excel documents
|
||||
*/
|
||||
package org.springframework.batch.item.excel.support.rowset;
|
||||
@@ -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();
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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());
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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<>();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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<>();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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<>();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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<>();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
BIN
spring-batch-excel/src/test/resources/blankRow.xlsx
Normal file
BIN
spring-batch-excel/src/test/resources/blankRow.xlsx
Normal file
Binary file not shown.
BIN
spring-batch-excel/src/test/resources/errors.xlsx
Normal file
BIN
spring-batch-excel/src/test/resources/errors.xlsx
Normal file
Binary file not shown.
@@ -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>
|
||||
13
spring-batch-excel/src/test/resources/log4j2.xml
Normal file
13
spring-batch-excel/src/test/resources/log4j2.xml
Normal 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.
Binary file not shown.
Binary file not shown.
BIN
spring-batch-excel/src/test/resources/player.xlsx
Normal file
BIN
spring-batch-excel/src/test/resources/player.xlsx
Normal file
Binary file not shown.
BIN
spring-batch-excel/src/test/resources/player_with_password.xls
Normal file
BIN
spring-batch-excel/src/test/resources/player_with_password.xls
Normal file
Binary file not shown.
Reference in New Issue
Block a user