1   package com.trendmicro.grid.acl.l0;
2   
3   import com.trendmicro.grid.acl.Limits;
4   
5   import java.io.Serializable;
6   import java.util.*;
7   
8   import static com.trendmicro.grid.acl.Limits.MAX_INCOMING_REQUEST_BATCH_SIZE;
9   import static net.sf.tinyjee.util.Assert.assertNotNull;
10  
11  /**
12   * Defines a collection that fails if more than {@link Limits#MAX_INCOMING_REQUEST_BATCH_SIZE} elements are added.
13   *
14   * @author Juergen_Kellerer, 2011-02-22
15   * @version 1.0
16   */
17  public class BatchCollection<E> extends AbstractCollection<E> implements Serializable {
18  
19  	private static final long serialVersionUID = 1600249921512556499L;
20  
21  	/**
22  	 * Defines an invoker that may be used to handle chunks of batch collections.
23  	 *
24  	 * @param <E> The type of the input collection elements.
25  	 * @param <R> The return type of the invocation.
26  	 */
27  	public interface Invoker<E, R> {
28  		/**
29  		 * Is called for every batch chunk that is handled within the method {@link #invokeOnChunksOf}
30  		 *
31  		 * @param chunk A chunk of elements that fit into one batch request.
32  		 * @return The result of the invocation on the given chunk.
33  		 * @throws WebException May be thrown when calling a service.
34  		 */
35  		public R invoke(BatchCollection<E> chunk) throws WebException;
36  	}
37  
38  	/**
39  	 * Creates a new final singleton batch collection using the given element.
40  	 *
41  	 * @param element the element to add.
42  	 * @return a new singleton batch collection using the given element.
43  	 */
44  	public static <E> BatchCollection<E> of(E element) {
45  		return new BatchCollection<E>(element);
46  	}
47  
48  	/**
49  	 * Creates a new final batch collection using the given elements.
50  	 *
51  	 * @param elements the elements to add.
52  	 * @return a new batch collection using the given elements.
53  	 * @throws BatchSizeExceededException If more elements than allowed are specified.
54  	 */
55  	public static <E> BatchCollection<E> of(E... elements) {
56  		return of(Arrays.asList(elements));
57  	}
58  
59  	/**
60  	 * Creates a new final batch collection using the given elements.
61  	 *
62  	 * @param elements the elements to add.
63  	 * @return a new batch collection using the given elements.
64  	 * @throws BatchSizeExceededException If more elements than allowed are specified.
65  	 */
66  	public static <E> BatchCollection<E> of(Collection<? extends E> elements) {
67  		return new BatchCollection<E>(elements);
68  	}
69  
70  	/**
71  	 * Creates chunks of batch collections that honor the built-in batch limit using the given elements.
72  	 *
73  	 * @param elements the elements to add (may be larger than the actual batch size).
74  	 * @return chunks of batch collections using the given elements.
75  	 */
76  	public static <E> List<BatchCollection<E>> chunksOf(List<? extends E> elements) {
77  		return chunksOf(elements, MAX_INCOMING_REQUEST_BATCH_SIZE);
78  	}
79  
80  	/**
81  	 * Creates chunks of batch collections that honor the given batch limit using the given elements.
82  	 *
83  	 * @param elements  the elements to add (may be larger than the actual batch size).
84  	 * @param batchSize Sets a custom limit for the batch size of a chunk.
85  	 * @return chunks of batch collections using the given elements.
86  	 */
87  	public static <E> List<BatchCollection<E>> chunksOf(List<? extends E> elements, int batchSize) {
88  		final List<BatchCollection<E>> collections = new ArrayList<BatchCollection<E>>();
89  		for (int i = 0, length = elements.size(); i < length; i += batchSize) {
90  			List<? extends E> chunk = elements.subList(i, Math.min(i + batchSize, length));
91  			collections.add(new BatchCollection<E>(chunk, batchSize));
92  		}
93  		return collections;
94  	}
95  
96  	/**
97  	 * Is a helper method that can be used to invoke a certain call on all batch chunks of a larger
98  	 * input list.
99  	 * <p/>
100 	 * Using this method is safe with large lists even if the batch size is unknown as remote
101 	 * exceptions are automatically caught and a request is retried with adjusted batch sizes.
102 	 * <p/>
103 	 * Example:<code><pre>
104 	 * Collection&lt;FileIdentifier&gt; identifiers = ...
105 	 * Collection&lt;FileDetails> details = BatchCollection.invokeOnChunksOf(identifiers,
106 	 *   new BatchCollection.Invoker&lt;FileIdentifier, Collection&lt;FileDetails&gt;&gt;() {
107 	 *     public Collection&lt;FileDetails&gt; invoke(BatchCollection&lt;FileIdentifier&gt; chunk) {
108 	 *       return fileService.getFileDetailsList(BatchCollection.of(chunk));
109 	 *	  }
110 	 *   });</pre></code>
111 	 *
112 	 * @param elements The elements to convert to batch chunks.
113 	 * @param invoker  The invoker implemented as anonymous inner class that walks all batch chunks
114 	 *                 to produce a final result.
115 	 * @param <E>      The type of the input collection elements.
116 	 * @param <R>      The return type of collection or list elements.
117 	 * @param <T>      The type of collection or list implementation that is returned by the invoked method.
118 	 * @return The combined results of all single invocations.
119 	 * @throws WebException May be thrown inside the invoker.
120 	 */
121 	@SuppressWarnings("unchecked")
122 	public static <E, R, T extends Collection<R>> T invokeOnChunksOf(
123 			Collection<? extends E> elements, Invoker<E, T> invoker) throws WebException {
124 
125 		T result = null;
126 		final List<? extends E> listOfElements = elements instanceof List ?
127 				(List<? extends E>) elements : new ArrayList<E>(elements);
128 
129 		while (true) {
130 			try {
131 				for (BatchCollection<E> currentChunk : chunksOf(listOfElements, defaultInvocationBatchSize)) {
132 					Collection<R> r = invoker.invoke(currentChunk);
133 					if (r != null) {
134 						if (result == null)
135 							result = (T) r;
136 						else
137 							result.addAll(r);
138 					}
139 				}
140 				break;
141 			} catch (BatchSizeExceededException e) {
142 				defaultInvocationBatchSize = e.getAllowedBatchSize();
143 			}
144 		}
145 
146 		return result;
147 	}
148 
149 	private static int defaultInvocationBatchSize = MAX_INCOMING_REQUEST_BATCH_SIZE;
150 
151 	/**
152 	 * Returns the batch size that is used to build chunks within the method {@link #invokeOnChunksOf}.
153 	 *
154 	 * @return the batch size that is used to build chunks within the method {@link #invokeOnChunksOf}.
155 	 */
156 	public static int getDefaultInvocationBatchSize() {
157 		return defaultInvocationBatchSize;
158 	}
159 
160 	/**
161 	 * Sets the batch size that is used to build chunks within the method {@link #invokeOnChunksOf}.
162 	 *
163 	 * @param defaultInvocationBatchSize the batch size that is used to build chunks
164 	 *                                   within the method {@link #invokeOnChunksOf}.
165 	 */
166 	public static void setDefaultInvocationBatchSize(int defaultInvocationBatchSize) {
167 		BatchCollection.defaultInvocationBatchSize = defaultInvocationBatchSize;
168 	}
169 
170 	private int batchSize;
171 	private Collection<E> backingStore;
172 
173 	/**
174 	 * Creates a new modifiable instance.
175 	 */
176 	public BatchCollection() {
177 		this(MAX_INCOMING_REQUEST_BATCH_SIZE);
178 	}
179 
180 	/**
181 	 * Creates a new modifiable instance with the given batch size limit.
182 	 *
183 	 * @param batchSize The non-default batch size limit to use.
184 	 */
185 	public BatchCollection(int batchSize) {
186 		this.batchSize = batchSize;
187 		backingStore = new LinkedList<E>();
188 	}
189 
190 	BatchCollection(Collection<? extends E> elements) {
191 		this(elements, MAX_INCOMING_REQUEST_BATCH_SIZE);
192 	}
193 
194 	BatchCollection(Collection<? extends E> elements, int batchSize) {
195 		assertNotNull("elements", elements);
196 
197 		this.batchSize = batchSize;
198 		assertIsWithinLimit(elements.size());
199 		backingStore = Collections.unmodifiableCollection(elements);
200 	}
201 
202 	BatchCollection(E singleElement) {
203 		assertNotNull("singleElement", singleElement);
204 		backingStore = Collections.singleton(singleElement);
205 	}
206 
207 	private void assertIsWithinLimit(int elementCountToAdd) {
208 		int size = backingStore == null ? 0 : backingStore.size();
209 		if (size + elementCountToAdd > batchSize)
210 			throw new BatchSizeExceededException(batchSize);
211 	}
212 
213 
214 	/**
215 	 * {@inheritDoc}
216 	 */
217 	@Override
218 	public boolean add(E e) {
219 		assertIsWithinLimit(1);
220 		return backingStore.add(e);
221 	}
222 
223 	/**
224 	 * {@inheritDoc}
225 	 */
226 	@Override
227 	public boolean addAll(Collection<? extends E> c) {
228 		assertIsWithinLimit(c.size());
229 		return backingStore.addAll(c);
230 	}
231 
232 	/**
233 	 * {@inheritDoc}
234 	 */
235 	@Override
236 	public Iterator<E> iterator() {
237 		return backingStore.iterator();
238 	}
239 
240 	/**
241 	 * {@inheritDoc}
242 	 */
243 	@Override
244 	public int size() {
245 		return backingStore.size();
246 	}
247 
248 	/**
249 	 * {@inheritDoc}
250 	 */
251 	@Override
252 	public boolean isEmpty() {
253 		return backingStore.isEmpty();
254 	}
255 
256 	/**
257 	 * {@inheritDoc}
258 	 */
259 	@Override
260 	public boolean contains(Object o) {
261 		return backingStore.contains(o);
262 	}
263 
264 	/**
265 	 * {@inheritDoc}
266 	 */
267 	@Override
268 	public Object[] toArray() {
269 		return backingStore.toArray();
270 	}
271 
272 	/**
273 	 * {@inheritDoc}
274 	 */
275 	@Override
276 	public <T> T[] toArray(T[] a) {
277 		return backingStore.toArray(a);
278 	}
279 
280 	/**
281 	 * {@inheritDoc}
282 	 */
283 	@Override
284 	public boolean containsAll(Collection<?> c) {
285 		return backingStore.containsAll(c);
286 	}
287 
288 	/**
289 	 * {@inheritDoc}
290 	 */
291 	@Override
292 	public boolean equals(Object o) {
293 		if (this == o) return true;
294 		if (o == null || getClass() != o.getClass()) return false;
295 		BatchCollection that = (BatchCollection) o;
296 		return backingStore.equals(that.backingStore);
297 	}
298 
299 	/**
300 	 * {@inheritDoc}
301 	 */
302 	@Override
303 	public int hashCode() {
304 		return backingStore.hashCode();
305 	}
306 }