package fr.acxio.tools.agia.item; /* * Copyright 2014 Acxio * * 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. */ import java.util.ArrayList; import java.util.List; import org.springframework.batch.item.ExecutionContext; import org.springframework.batch.item.ItemReader; import org.springframework.batch.item.ItemStream; import org.springframework.batch.item.ItemStreamReader; import org.springframework.batch.item.file.transform.FieldSet; import org.springframework.util.Assert; import fr.acxio.tools.agia.expression.support.AbstractExpressionEvaluator; /** * <p> * Item reader for records made of many lines with different mappings and having * non-explicit rules for detecting the ends of the records. * </p> * <p> * This item reader delegates the real reading, parsing and mapping to its * {@code delegate}. * </p> * <p> * The {@code newRecordCondition} is a boolean expression that can use the * {@code currentVariableName} and the {@code nextVariableName}, respectively * "current" and "next" by default. * </p> * <p> * As their names are self-explanatory, these variables contain the current line * and the next line from the stream. * </p> * <p> * The delegate reader may return different FieldSets, having different fields * from one another. The condition must take this into account. * </p> * <p> * Every time the end of a record is reach in the current line, the condition * should evaluate to {@code true} and a list of * {@link org.springframework.batch.item.file.transform.FieldSet FieldSet} is * returned. * </p> * <p> * The returned FieldSets by the delegate reader may not have the same fields. * </p> * <p>Subclasses must implement {@code mapFieldSets} to transform the list * of read FieldSet into items.</p> * * @param <T> The type of the returned items * * @author pcollardez * */ public abstract class MultiLineItemReader<T> extends AbstractExpressionEvaluator implements ItemStreamReader<T> { private ItemReader<FieldSet> delegate; private FieldSet nextItem = null; private boolean delegateExhausted = false; private String newRecordCondition; private String currentVariableName = "current"; private String nextVariableName = "next"; public synchronized void setDelegate(ItemReader<FieldSet> sDelegate) { delegate = sDelegate; } public synchronized void setCurrentVariableName(String sCurrentVariableName) { Assert.hasText(sCurrentVariableName, "currentVariableName must not be empty"); currentVariableName = sCurrentVariableName; } public synchronized void setNextVariableName(String sNextVariableName) { Assert.hasText(sNextVariableName, "nextVariableName must not be empty"); nextVariableName = sNextVariableName; } public synchronized void setNewRecordCondition(String sNewRecordCondition) { newRecordCondition = sNewRecordCondition; } @Override public synchronized T read() { List<FieldSet> aTmpResult = new ArrayList<FieldSet>(); boolean aConditionResult = false; FieldSet line = readNextFieldSet(); while (!aConditionResult && (line != null)) { aTmpResult.add(line); if (nextItem != null) { updateContext(currentVariableName, (line.hasNames()) ? line.getProperties() : line.getValues(), getEvaluationContext()); updateContext(nextVariableName, (nextItem.hasNames()) ? nextItem.getProperties() : nextItem.getValues(), getEvaluationContext()); aConditionResult = getExpressionResolver().evaluate(newRecordCondition, getEvaluationContext(), Boolean.class); } if (!aConditionResult) { line = readNextFieldSet(); } } return (aTmpResult.isEmpty() ? null : mapFieldSets(aTmpResult)); } public abstract T mapFieldSets(List<FieldSet> sFieldSets); private FieldSet readNextFieldSet() { FieldSet returnItem = null; try { if (!delegateExhausted) { if (nextItem != null) { returnItem = nextItem; nextItem = null; } else { returnItem = delegate.read(); } nextItem = delegate.read(); if (nextItem == null) { delegateExhausted = true; } } } catch (Exception e) { throw new ItemReaderReadException("Cannot read next FieldSet", e); } return returnItem; } @Override public synchronized void open(ExecutionContext sExecutionContext) { delegateExhausted = false; ((ItemStream) delegate).open(sExecutionContext); } @Override public synchronized void update(ExecutionContext sExecutionContext) { ((ItemStream) delegate).update(sExecutionContext); } @Override public synchronized void close() { delegateExhausted = false; ((ItemStream) delegate).close(); } }