1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.springframework.batch.item.file;
18
19 import java.io.BufferedReader;
20 import java.io.IOException;
21 import java.nio.charset.Charset;
22
23 import org.apache.commons.logging.Log;
24 import org.apache.commons.logging.LogFactory;
25 import org.springframework.batch.item.ItemReader;
26 import org.springframework.batch.item.ReaderNotOpenException;
27 import org.springframework.batch.item.file.separator.RecordSeparatorPolicy;
28 import org.springframework.batch.item.file.separator.SimpleRecordSeparatorPolicy;
29 import org.springframework.batch.item.support.AbstractItemCountingItemStreamItemReader;
30 import org.springframework.beans.factory.InitializingBean;
31 import org.springframework.core.io.Resource;
32 import org.springframework.util.Assert;
33 import org.springframework.util.ClassUtils;
34 import org.springframework.util.StringUtils;
35
36
37
38
39
40
41
42
43
44 public class FlatFileItemReader<T> extends AbstractItemCountingItemStreamItemReader<T> implements
45 ResourceAwareItemReaderItemStream<T>, InitializingBean {
46
47 private static final Log logger = LogFactory.getLog(FlatFileItemReader.class);
48
49
50 public static final String DEFAULT_CHARSET = Charset.defaultCharset().name();
51
52 private RecordSeparatorPolicy recordSeparatorPolicy = new SimpleRecordSeparatorPolicy();
53
54 private Resource resource;
55
56 private BufferedReader reader;
57
58 private int lineCount = 0;
59
60 private String[] comments = new String[] { "#" };
61
62 private boolean noInput = false;
63
64 private String encoding = DEFAULT_CHARSET;
65
66 private LineMapper<T> lineMapper;
67
68 private int linesToSkip = 0;
69
70 private LineCallbackHandler skippedLinesCallback;
71
72 private boolean strict = true;
73
74 private BufferedReaderFactory bufferedReaderFactory = new DefaultBufferedReaderFactory();
75
76 public FlatFileItemReader() {
77 setName(ClassUtils.getShortName(FlatFileItemReader.class));
78 }
79
80
81
82
83
84
85 public void setStrict(boolean strict) {
86 this.strict = strict;
87 }
88
89
90
91
92 public void setSkippedLinesCallback(LineCallbackHandler skippedLinesCallback) {
93 this.skippedLinesCallback = skippedLinesCallback;
94 }
95
96
97
98
99
100
101
102 public void setLinesToSkip(int linesToSkip) {
103 this.linesToSkip = linesToSkip;
104 }
105
106
107
108
109
110 public void setLineMapper(LineMapper<T> lineMapper) {
111 this.lineMapper = lineMapper;
112 }
113
114
115
116
117
118
119 public void setEncoding(String encoding) {
120 this.encoding = encoding;
121 }
122
123
124
125
126
127
128
129
130 public void setBufferedReaderFactory(BufferedReaderFactory bufferedReaderFactory) {
131 this.bufferedReaderFactory = bufferedReaderFactory;
132 }
133
134
135
136
137
138
139
140 public void setComments(String[] comments) {
141 this.comments = new String[comments.length];
142 System.arraycopy(comments, 0, this.comments, 0, comments.length);
143 }
144
145
146
147
148 public void setResource(Resource resource) {
149 this.resource = resource;
150 }
151
152
153
154
155
156
157
158 public void setRecordSeparatorPolicy(RecordSeparatorPolicy recordSeparatorPolicy) {
159 this.recordSeparatorPolicy = recordSeparatorPolicy;
160 }
161
162
163
164
165
166 @Override
167 protected T doRead() throws Exception {
168 if (noInput) {
169 return null;
170 }
171
172 String line = readLine();
173
174 if (line == null) {
175 return null;
176 }
177 else {
178 try {
179 return lineMapper.mapLine(line, lineCount);
180 }
181 catch (Exception ex) {
182 throw new FlatFileParseException("Parsing error at line: " + lineCount + " in resource=["
183 + resource.getDescription() + "], input=[" + line + "]", ex, line, lineCount);
184 }
185 }
186 }
187
188
189
190
191 private String readLine() {
192
193 if (reader == null) {
194 throw new ReaderNotOpenException("Reader must be open before it can be read.");
195 }
196
197 String line = null;
198
199 try {
200 line = this.reader.readLine();
201 if (line == null) {
202 return null;
203 }
204 lineCount++;
205 while (isComment(line)) {
206 line = reader.readLine();
207 if (line == null) {
208 return null;
209 }
210 lineCount++;
211 }
212
213 line = applyRecordSeparatorPolicy(line);
214 }
215 catch (IOException e) {
216
217
218 noInput = true;
219 throw new NonTransientFlatFileException("Unable to read from resource: [" + resource + "]", e, line,
220 lineCount);
221 }
222 return line;
223 }
224
225 private boolean isComment(String line) {
226 for (String prefix : comments) {
227 if (line.startsWith(prefix)) {
228 return true;
229 }
230 }
231 return false;
232 }
233
234 @Override
235 protected void doClose() throws Exception {
236 lineCount = 0;
237 if (reader != null) {
238 reader.close();
239 }
240 }
241
242 @Override
243 protected void doOpen() throws Exception {
244 Assert.notNull(resource, "Input resource must be set");
245 Assert.notNull(recordSeparatorPolicy, "RecordSeparatorPolicy must be set");
246
247 noInput = true;
248 if (!resource.exists()) {
249 if (strict) {
250 throw new IllegalStateException("Input resource must exist (reader is in 'strict' mode): " + resource);
251 }
252 logger.warn("Input resource does not exist " + resource.getDescription());
253 return;
254 }
255
256 if (!resource.isReadable()) {
257 if (strict) {
258 throw new IllegalStateException("Input resource must be readable (reader is in 'strict' mode): "
259 + resource);
260 }
261 logger.warn("Input resource is not readable " + resource.getDescription());
262 return;
263 }
264
265 reader = bufferedReaderFactory.create(resource, encoding);
266 for (int i = 0; i < linesToSkip; i++) {
267 String line = readLine();
268 if (skippedLinesCallback != null) {
269 skippedLinesCallback.handleLine(line);
270 }
271 }
272 noInput = false;
273 }
274
275 public void afterPropertiesSet() throws Exception {
276 Assert.notNull(lineMapper, "LineMapper is required");
277 }
278
279 @Override
280 protected void jumpToItem(int itemIndex) throws Exception {
281 for (int i = 0; i < itemIndex; i++) {
282 readLine();
283 }
284 }
285
286 private String applyRecordSeparatorPolicy(String line) throws IOException {
287
288 String record = line;
289 while (line != null && !recordSeparatorPolicy.isEndOfRecord(record)) {
290 line = this.reader.readLine();
291 if (line == null) {
292 if (StringUtils.hasText(record)) {
293
294
295 throw new FlatFileParseException("Unexpected end of file before record complete", record, lineCount);
296 }
297 else {
298
299
300
301 break;
302 }
303 }
304 else {
305 lineCount++;
306 }
307 record = recordSeparatorPolicy.preProcess(record) + line;
308 }
309
310 return recordSeparatorPolicy.postProcess(record);
311
312 }
313
314 }