/* * Copyright 2006-2013 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.step.item; import java.util.List; import org.springframework.batch.core.StepContribution; import org.springframework.batch.core.StepListener; import org.springframework.batch.core.listener.MulticasterBatchListener; import org.springframework.batch.item.ItemProcessor; import org.springframework.batch.item.ItemWriter; import org.springframework.beans.factory.InitializingBean; import org.springframework.util.Assert; /** * Simple implementation of the {@link ChunkProcessor} interface that handles * basic item writing and processing. Any exceptions encountered will be * rethrown. * * @see ChunkOrientedTasklet */ public class SimpleChunkProcessor<I, O> implements ChunkProcessor<I>, InitializingBean { private ItemProcessor<? super I, ? extends O> itemProcessor; private ItemWriter<? super O> itemWriter; private final MulticasterBatchListener<I, O> listener = new MulticasterBatchListener<I, O>(); /** * Default constructor for ease of configuration (both itemWriter and * itemProcessor are mandatory). */ @SuppressWarnings("unused") private SimpleChunkProcessor() { this(null, null); } public SimpleChunkProcessor(ItemProcessor<? super I, ? extends O> itemProcessor, ItemWriter<? super O> itemWriter) { this.itemProcessor = itemProcessor; this.itemWriter = itemWriter; } /** * @param itemProcessor the {@link ItemProcessor} to set */ public void setItemProcessor(ItemProcessor<? super I, ? extends O> itemProcessor) { this.itemProcessor = itemProcessor; } /** * @param itemWriter the {@link ItemWriter} to set */ public void setItemWriter(ItemWriter<? super O> itemWriter) { this.itemWriter = itemWriter; } /** * Check mandatory properties. * * @see InitializingBean#afterPropertiesSet() */ @Override public void afterPropertiesSet() throws Exception { Assert.notNull(itemWriter, "ItemWriter must be set"); Assert.notNull(itemProcessor, "ItemProcessor must be set"); } /** * Register some {@link StepListener}s with the handler. Each will get the * callbacks in the order specified at the correct stage. * * @param listeners */ public void setListeners(List<? extends StepListener> listeners) { for (StepListener listener : listeners) { registerListener(listener); } } /** * Register a listener for callbacks at the appropriate stages in a process. * * @param listener a {@link StepListener} */ public void registerListener(StepListener listener) { this.listener.register(listener); } /** * @return the listener */ protected MulticasterBatchListener<I, O> getListener() { return listener; } /** * @param item the input item * @return the result of the processing * @throws Exception */ protected final O doProcess(I item) throws Exception { if (itemProcessor == null) { @SuppressWarnings("unchecked") O result = (O) item; return result; } try { listener.beforeProcess(item); O result = itemProcessor.process(item); listener.afterProcess(item, result); return result; } catch (Exception e) { listener.onProcessError(item, e); throw e; } } /** * Surrounds the actual write call with listener callbacks. * * @param items * @throws Exception */ protected final void doWrite(List<O> items) throws Exception { if (itemWriter == null) { return; } try { listener.beforeWrite(items); writeItems(items); doAfterWrite(items); } catch (Exception e) { doOnWriteError(e, items); throw e; } } /** * Call the listener's after write method. * * @param items */ protected final void doAfterWrite(List<O> items) { listener.afterWrite(items); } protected final void doOnWriteError(Exception e, List<O> items) { listener.onWriteError(e, items); } protected void writeItems(List<O> items) throws Exception { if (itemWriter != null) { itemWriter.write(items); } } @Override public final void process(StepContribution contribution, Chunk<I> inputs) throws Exception { // Allow temporary state to be stored in the user data field initializeUserData(inputs); // If there is no input we don't have to do anything more if (isComplete(inputs)) { return; } // Make the transformation, calling remove() on the inputs iterator if // any items are filtered. Might throw exception and cause rollback. Chunk<O> outputs = transform(contribution, inputs); // Adjust the filter count based on available data contribution.incrementFilterCount(getFilterCount(inputs, outputs)); // Adjust the outputs if necessary for housekeeping purposes, and then // write them out... write(contribution, inputs, getAdjustedOutputs(inputs, outputs)); } /** * Extension point for subclasses to allow them to memorise the contents of * the inputs, in case they are needed for accounting purposes later. The * default implementation sets up some user data to remember the original * size of the inputs. If this method is overridden then some or all of * {@link #isComplete(Chunk)}, {@link #getFilterCount(Chunk, Chunk)} and * {@link #getAdjustedOutputs(Chunk, Chunk)} might also need to be, to * ensure that the user data is handled consistently. * * @param inputs the inputs for the process */ protected void initializeUserData(Chunk<I> inputs) { inputs.setUserData(inputs.size()); } /** * Extension point for subclasses to calculate the filter count. Defaults to * the difference between input size and output size. * * @param inputs the inputs after transformation * @param outputs the outputs after transformation * * @return the difference in sizes * * @see #initializeUserData(Chunk) */ protected int getFilterCount(Chunk<I> inputs, Chunk<O> outputs) { return (Integer) inputs.getUserData() - outputs.size(); } /** * Extension point for subclasses that want to store additional data in the * inputs. Default just checks if inputs are empty. * * @param inputs the input chunk * @return true if it is empty * * @see #initializeUserData(Chunk) */ protected boolean isComplete(Chunk<I> inputs) { return inputs.isEmpty(); } /** * Extension point for subclasses that want to adjust the outputs based on * additional saved data in the inputs. Default implementation just returns * the outputs unchanged. * * @param inputs the inputs for the transformation * @param outputs the result of the transformation * @return the outputs unchanged * * @see #initializeUserData(Chunk) */ protected Chunk<O> getAdjustedOutputs(Chunk<I> inputs, Chunk<O> outputs) { return outputs; } /** * Simple implementation delegates to the {@link #doWrite(List)} method and * increments the write count in the contribution. Subclasses can handle * more complicated scenarios, e.g.with fault tolerance. If output items are * skipped they should be removed from the inputs as well. * * @param contribution the current step contribution * @param inputs the inputs that gave rise to the outputs * @param outputs the outputs to write * @throws Exception if there is a problem */ protected void write(StepContribution contribution, Chunk<I> inputs, Chunk<O> outputs) throws Exception { try { doWrite(outputs.getItems()); } catch (Exception e) { /* * For a simple chunk processor (no fault tolerance) we are done * here, so prevent any more processing of these inputs. */ inputs.clear(); throw e; } contribution.incrementWriteCount(outputs.size()); } protected Chunk<O> transform(StepContribution contribution, Chunk<I> inputs) throws Exception { Chunk<O> outputs = new Chunk<O>(); for (Chunk<I>.ChunkIterator iterator = inputs.iterator(); iterator.hasNext();) { final I item = iterator.next(); O output; try { output = doProcess(item); } catch (Exception e) { /* * For a simple chunk processor (no fault tolerance) we are done * here, so prevent any more processing of these inputs. */ inputs.clear(); throw e; } if (output != null) { outputs.add(output); } else { iterator.remove(); } } return outputs; } }