001/* 002 * Copyright 2006-2013 the original author or authors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * https://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package org.springframework.batch.core.step.item; 018 019import java.util.ArrayList; 020import java.util.Collection; 021import java.util.Collections; 022import java.util.Iterator; 023import java.util.List; 024 025/** 026 * Encapsulation of a list of items to be processed and possibly a list of 027 * failed items to be skipped. To mark an item as skipped clients should iterate 028 * over the chunk using the {@link #iterator()} method, and if there is a 029 * failure call {@link org.springframework.batch.core.step.item.Chunk.ChunkIterator#remove()} on the iterator. 030 * The skipped items are then available through the chunk. 031 * 032 * @author Dave Syer 033 * @since 2.0 034 */ 035public class Chunk<W> implements Iterable<W> { 036 037 private List<W> items = new ArrayList<W>(); 038 039 private List<SkipWrapper<W>> skips = new ArrayList<SkipWrapper<W>>(); 040 041 private List<Exception> errors = new ArrayList<Exception>(); 042 043 private Object userData; 044 045 private boolean end; 046 047 private boolean busy; 048 049 public Chunk() { 050 this(null, null); 051 } 052 053 public Chunk(Collection<? extends W> items) { 054 this(items, null); 055 } 056 057 public Chunk(Collection<? extends W> items, List<SkipWrapper<W>> skips) { 058 super(); 059 if (items != null) { 060 this.items = new ArrayList<W>(items); 061 } 062 if (skips != null) { 063 this.skips = new ArrayList<SkipWrapper<W>>(skips); 064 } 065 } 066 067 /** 068 * Add the item to the chunk. 069 * @param item the item to add 070 */ 071 public void add(W item) { 072 items.add(item); 073 } 074 075 /** 076 * Clear the items down to signal that we are done. 077 */ 078 public void clear() { 079 items.clear(); 080 skips.clear(); 081 userData = null; 082 } 083 084 /** 085 * @return a copy of the items to be processed as an unmodifiable list 086 */ 087 public List<W> getItems() { 088 return Collections.unmodifiableList(new ArrayList<W>(items)); 089 } 090 091 /** 092 * @return a copy of the skips as an unmodifiable list 093 */ 094 public List<SkipWrapper<W>> getSkips() { 095 return Collections.unmodifiableList(skips); 096 } 097 098 /** 099 * @return a copy of the anonymous errors as an unmodifiable list 100 */ 101 public List<Exception> getErrors() { 102 return Collections.unmodifiableList(errors); 103 } 104 105 /** 106 * Register an anonymous skip. To skip an individual item, use 107 * {@link ChunkIterator#remove()}. 108 * 109 * @param e the exception that caused the skip 110 */ 111 public void skip(Exception e) { 112 errors.add(e); 113 } 114 115 /** 116 * @return true if there are no items in the chunk 117 */ 118 public boolean isEmpty() { 119 return items.isEmpty(); 120 } 121 122 /** 123 * Get an unmodifiable iterator for the underlying items. 124 * @see java.lang.Iterable#iterator() 125 */ 126 @Override 127 public ChunkIterator iterator() { 128 return new ChunkIterator(items); 129 } 130 131 /** 132 * @return the number of items (excluding skips) 133 */ 134 public int size() { 135 return items.size(); 136 } 137 138 /** 139 * Flag to indicate if the source data is exhausted. 140 * 141 * @return true if there is no more data to process 142 */ 143 public boolean isEnd() { 144 return end; 145 } 146 147 /** 148 * Set the flag to say that this chunk represents an end of stream (there is 149 * no more data to process). 150 */ 151 public void setEnd() { 152 this.end = true; 153 } 154 155 /** 156 * Query the chunk to see if anyone has registered an interest in keeping a 157 * reference to it. 158 * 159 * @return the busy flag 160 */ 161 public boolean isBusy() { 162 return busy; 163 } 164 165 /** 166 * Register an interest in the chunk to prevent it from being cleaned up 167 * before the flag is reset to false. 168 * 169 * @param busy the flag to set 170 */ 171 public void setBusy(boolean busy) { 172 this.busy = busy; 173 } 174 175 /** 176 * Clear only the skips list. 177 */ 178 public void clearSkips() { 179 skips.clear(); 180 } 181 182 public Object getUserData() { 183 return userData; 184 } 185 186 public void setUserData(Object userData) { 187 this.userData = userData; 188 } 189 190 /* 191 * (non-Javadoc) 192 * 193 * @see java.lang.Object#toString() 194 */ 195 @Override 196 public String toString() { 197 return String.format("[items=%s, skips=%s]", items, skips); 198 } 199 200 /** 201 * Special iterator for a chunk providing the {@link #remove(Throwable)} 202 * method for dynamically removing an item and adding it to the skips. 203 * 204 * @author Dave Syer 205 * 206 */ 207 public class ChunkIterator implements Iterator<W> { 208 209 final private Iterator<W> iterator; 210 211 private W next; 212 213 public ChunkIterator(List<W> items) { 214 iterator = items.iterator(); 215 } 216 217 @Override 218 public boolean hasNext() { 219 return iterator.hasNext(); 220 } 221 222 @Override 223 public W next() { 224 next = iterator.next(); 225 return next; 226 } 227 228 public void remove(Throwable e) { 229 remove(); 230 skips.add(new SkipWrapper<W>(next, e)); 231 } 232 233 @Override 234 public void remove() { 235 if (next == null) { 236 if (iterator.hasNext()) { 237 next = iterator.next(); 238 } 239 else { 240 return; 241 } 242 } 243 iterator.remove(); 244 } 245 246 @Override 247 public String toString() { 248 return String.format("[items=%s, skips=%s]", items, skips); 249 } 250 251 } 252 253}