diff --git a/pom.xml b/pom.xml
index 3fb0596fd6..59e789df14 100644
--- a/pom.xml
+++ b/pom.xml
@@ -521,7 +521,25 @@
dhgarrette@gmail.com
+
+
+
+ com.springsource.repository.bundles.release
+ SpringSource Enterprise Bundle Repository - SpringSource Bundle Releases
+ http://repository.springsource.com/maven/bundles/release
+
+
+ foundrylogic.vpp
+ Repository used to download foundrylogic.vpp
+ http://objectstyle.org/maven2
+
+
+
+ com.springsource.repository.bundles.release
+ SpringSource Enterprise Bundle Repository - SpringSource Bundle Releases
+ http://repository.springsource.com/maven/bundles/release
+
com.springsource.repository.bundles.milestone
SpringSource Enterprise Bundle Repository - SpringSource Bundle Milestones
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/partition/support/FlatFilePartitioner.java b/spring-batch-core/src/main/java/org/springframework/batch/core/partition/support/FlatFilePartitioner.java
new file mode 100644
index 0000000000..40e100508c
--- /dev/null
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/partition/support/FlatFilePartitioner.java
@@ -0,0 +1,185 @@
+/*
+ * Copyright 2006-2007 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.batch.core.partition.support;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.batch.item.ExecutionContext;
+import org.springframework.batch.item.util.FileUtils;
+import org.springframework.core.io.Resource;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+/**
+ * Creates a set of partitions for a flat file.
+ *
+ * By default, assumes that each record is stored on one and only
+ * one line. First computes the number of items and then split them
+ * in the number of requested partition(s).
+ *
+ * The {@link org.springframework.batch.item.file.MultiThreadedFlatFileItemReader}
+ * can be used to read the file concurrently, each using a startAt
+ * cursor and a number of items to read as defined by the itemsCount
+ * property.
+ *
+ * @author Stephane Nicoll
+ */
+public class FlatFilePartitioner implements Partitioner {
+
+ /**
+ * The number of items to partition should skip on startup.
+ */
+ public static final String START_AT_KEY = "startAt";
+
+ /**
+ * The number of items to read in the partition.
+ */
+ public static final String ITEMS_COUNT_KEY = "itemsCount";
+
+ /**
+ * The common partition prefix name to use.
+ */
+ public static final String PARTITION_PREFIX = "partition-";
+
+ private final Logger logger = LoggerFactory.getLogger(FlatFilePartitioner.class);
+
+ private Resource resource;
+
+ /**
+ * Creates a set of {@link ExecutionContext} according to the provided
+ * gridSize if there are enough elements.
+ *
+ * First computes the total number of items to process for the resource
+ * and then split equality these in each partition. The returned context
+ * hold the {@link #START_AT_KEY} and {@link #ITEMS_COUNT_KEY} properties
+ * defining the number of elements to skip and the number of elements to
+ * read respectively.
+ *
+ * @param gridSize the requested size of the grid
+ * @return the execution contexts
+ * @see #countItems(org.springframework.core.io.Resource)
+ */
+ public Map partition(int gridSize) {
+ final String partitionNumberFormat = "%0" + String.valueOf(gridSize).length() + "d";
+ final Map result = new LinkedHashMap();
+
+ checkResource(this.resource);
+ if (logger.isDebugEnabled()) {
+ logger.debug("Splitting [" + resource.getDescription() + "]");
+ }
+
+ final long lines = countItems(resource);
+ if (lines == 0) {
+ logger.info("Empty input file [" + resource.getDescription() + "] no partition will be created.");
+ return result;
+ }
+
+ final long linesPerFile = lines / gridSize;
+
+ // Check the case that the set is to small for the number of request partition(s)
+ if (linesPerFile == 0) {
+ logger.info("Not enough lines (" + lines + ") for the requested gridSize [" + gridSize + "]");
+ final String partitionName = PARTITION_PREFIX + String.format(partitionNumberFormat, 0);
+ result.put(partitionName, createExecutionContext(partitionName, 0, lines));
+ return result;
+ }
+
+ if (logger.isDebugEnabled()) {
+ logger.debug("Has to split [" + lines + "] line(s) in [" + gridSize + "] " +
+ "grid(s) (" + linesPerFile + " each)");
+ }
+
+ for (int i = 0; i < gridSize; i++) {
+ final String partitionName = PARTITION_PREFIX + String.format(partitionNumberFormat, i);
+
+ // Start at i * COUNT items (0, COUNT*1, COUNT*2, etc)
+ final long startAt = i * linesPerFile;
+ long itemsCount = linesPerFile;
+ // If this is the last partition, it gets all remaining items
+ if (i == gridSize - 1) {
+ itemsCount = lines - ((gridSize - 1) * linesPerFile);
+ }
+ result.put(partitionName, createExecutionContext(partitionName, startAt, itemsCount));
+ }
+ return result;
+ }
+
+ /**
+ * Creates a standard {@link ExecutionContext} with the specified parameters.
+ *
+ * @param partitionName the name of the partition
+ * @param startAt the number of items to skip
+ * @param itemsCount the number of items to read
+ * @return the execution context
+ */
+ protected ExecutionContext createExecutionContext(String partitionName, long startAt, long itemsCount) {
+ final ExecutionContext executionContext = new ExecutionContext();
+ executionContext.putLong(START_AT_KEY, startAt);
+ executionContext.putLong(ITEMS_COUNT_KEY, itemsCount);
+ if (logger.isDebugEnabled()) {
+ logger.debug("Added partition [" + partitionName + "] with [" + executionContext + "]");
+ }
+ return executionContext;
+ }
+
+ /**
+ * Returns the number of elements in the specified {@link Resource}.
+ *
+ * @param resource the resource
+ * @return the number of items contained in the resource
+ */
+ protected long countItems(Resource resource) {
+ try {
+ final InputStream in = resource.getInputStream();
+ try {
+ return FileUtils.countLines( in );
+ } finally {
+ in.close();
+ }
+ } catch (IOException e) {
+ throw new IllegalStateException("Unexpected IO exception while counting items for ["
+ + resource.getDescription() + "]", e);
+ }
+ }
+
+ /**
+ * Checks whether the specified {@link Resource} is valid.
+ *
+ * @param resource the resource to check
+ * @throws IllegalStateException if the resource is invalid
+ */
+ protected void checkResource(Resource resource) {
+ if (!resource.exists()) {
+ throw new IllegalStateException("Input resource must exist: " + resource);
+ }
+ if (!resource.isReadable()) {
+ throw new IllegalStateException("Input resource must be readable: " + resource);
+ }
+ }
+
+ /**
+ * Sets the input {@link Resource} to use.
+ *
+ * @param resource the resource to partition
+ */
+ public void setResource(Resource resource) {
+ this.resource = resource;
+ }
+}
diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/file/MultiThreadedFlatFileItemReader.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/file/MultiThreadedFlatFileItemReader.java
new file mode 100644
index 0000000000..5e86a398f2
--- /dev/null
+++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/file/MultiThreadedFlatFileItemReader.java
@@ -0,0 +1,129 @@
+/*
+ * Copyright 2006-2007 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.batch.item.file;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.batch.item.ExecutionContext;
+import org.springframework.batch.item.ItemStreamException;
+import org.springframework.batch.item.util.ExecutionContextUserSupport;
+import org.springframework.util.ClassUtils;
+
+/**
+ * A multi-threaded aware {@link FlatFileItemReader} implementation
+ * that uses start and end boundaries to delimit the portion of the
+ * file that should be read.
+ *
+ * Reads all the file by default.
+ *
+ * @author Stephane Nicoll
+ */
+public class MultiThreadedFlatFileItemReader extends FlatFileItemReader {
+
+ // Protected field would alleviate copy/paste here
+ private static final String READ_COUNT = "read.count";
+
+ private final Logger logger = LoggerFactory.getLogger(MultiThreadedFlatFileItemReader.class);
+
+ private int startAt = 0;
+ private int itemsCount = Integer.MAX_VALUE;
+
+ // Would be better if the base ecSupport was protected somehow.
+ private final ExecutionContextUserSupport ecSupport;
+
+ public MultiThreadedFlatFileItemReader() {
+ this.ecSupport = new ExecutionContextUserSupport();
+ setName(ClassUtils.getShortName(MultiThreadedFlatFileItemReader.class));
+ }
+
+
+ @Override
+ public void open(ExecutionContext executionContext) throws ItemStreamException {
+ super.open(executionContext);
+
+ /*
+ Since we are dealing with multiple chunk in the same area, let's make
+ sure we will jump to the right item.
+
+ The problem is that the maxItemCount and currentItemCount do not take this
+ notion into account. Say a chunk starts at item #1000 and must read 100
+ elements, the currentItemCount should be 1000 at beginning (ok) but the
+ maxItemCount must be 1100 (and not 100 like it should be)
+ */
+ if (!executionContext.containsKey(ecSupport.getKey(READ_COUNT))) {
+ // We need to jump and this is a fresh start (nothing in the context)
+ if (startAt > 0) {
+ if (logger.isDebugEnabled()) {
+ logger.debug("Skipping to item [" + startAt + "]");
+ }
+ // Make sure to register the maxItemCount properly
+ final int maxItemCount = startAt + itemsCount;
+ setMaxItemCount(maxItemCount);
+ try {
+ for (int i = 0; i < startAt; i++) {
+ read();
+ }
+ } catch (Exception e) {
+ throw new ItemStreamException(
+ "Could not move to stored position on restart", e);
+ }
+ if (logger.isDebugEnabled()) {
+ logger.debug("Ready to read from [" + getCurrentItemCount() + "] to [" + maxItemCount + "]");
+ }
+ } else {
+ // Fresh start on the first item so let's state the max item count is simply the
+ // itemsCount
+ setMaxItemCount(itemsCount);
+ if (logger.isDebugEnabled()) {
+ logger.debug("Ready to read from [" + getCurrentItemCount() + "] to [" + itemsCount + "]");
+ }
+ }
+ }
+ }
+
+ /**
+ * Sets the item number at which this instance should start reading. Set
+ * to 0 by default so that this instance starts at the first item.
+ *
+ * @param startAt the number of the item at which this instance should
+ * start reading
+ */
+ public void setStartAt(int startAt) {
+ this.startAt = startAt;
+ }
+
+ /**
+ * Sets the number of items this instance should read.
+ *
+ * @param itemsCount the number of items to read
+ */
+ public void setItemsCount(int itemsCount) {
+ this.itemsCount = itemsCount;
+ }
+
+ @Override
+ public void setName(String name) {
+ super.setName(name);
+ // default constructor of the parent calls this before the instance is
+ // actually initialized. With a protected ecSupport, this can go away
+ // altogether.
+ if (ecSupport != null) {
+ ecSupport.setName(name);
+ }
+ }
+}
+
diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/util/FileUtils.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/util/FileUtils.java
index 8b5df89701..b19cfba573 100644
--- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/util/FileUtils.java
+++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/util/FileUtils.java
@@ -16,8 +16,10 @@
package org.springframework.batch.item.util;
+import java.io.BufferedInputStream;
import java.io.File;
import java.io.IOException;
+import java.io.InputStream;
import org.springframework.batch.item.ItemStreamException;
import org.springframework.util.Assert;
@@ -110,4 +112,32 @@ public static boolean createNewFile(File file) throws IOException {
}
+ /**
+ * Returns the number of lines found in the specified stream.
+ *
+ * The caller is responsible to close the stream.
+ *
+ * @param in the input stream to use
+ * @return the number of lines found in the stream
+ * @throws IOException if an error occurred
+ */
+ public static long countLines(InputStream in) throws IOException {
+ final InputStream is = new BufferedInputStream(in);
+ byte[] c = new byte[1024];
+ long count = 0;
+ int readChars;
+ while ((readChars = is.read(c)) != -1) {
+ for (int i = 0; i < readChars; ++i) {
+ // We're dealing with the char here, it's \n on Unix and \r\n on Windows
+ if (c[i] == '\n')
+ ++count;
+ }
+ }
+ // Last line
+ if (count > 0) {
+ count++;
+ }
+ return count;
+ }
+
}