1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.eclipse.jetty.client.util;
20
21 import java.io.Closeable;
22 import java.io.IOException;
23 import java.io.InterruptedIOException;
24 import java.nio.ByteBuffer;
25 import java.util.ArrayList;
26 import java.util.Iterator;
27 import java.util.List;
28 import java.util.NoSuchElementException;
29 import java.util.Objects;
30 import java.util.Queue;
31 import java.util.concurrent.atomic.AtomicBoolean;
32 import java.util.concurrent.atomic.AtomicReference;
33
34 import org.eclipse.jetty.client.AsyncContentProvider;
35 import org.eclipse.jetty.client.api.ContentProvider;
36 import org.eclipse.jetty.client.api.Request;
37 import org.eclipse.jetty.client.api.Response;
38 import org.eclipse.jetty.util.ArrayQueue;
39 import org.eclipse.jetty.util.BufferUtil;
40 import org.eclipse.jetty.util.Callback;
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88 public class DeferredContentProvider implements AsyncContentProvider, Callback, Closeable
89 {
90 private static final Chunk CLOSE = new Chunk(BufferUtil.EMPTY_BUFFER, Callback.Adapter.INSTANCE);
91
92 private final Object lock = this;
93 private final Queue<Chunk> chunks = new ArrayQueue<>(4, 64, lock);
94 private final AtomicReference<Listener> listener = new AtomicReference<>();
95 private final DeferredContentProviderIterator iterator = new DeferredContentProviderIterator();
96 private final AtomicBoolean closed = new AtomicBoolean();
97 private long length = -1;
98 private int size;
99 private Throwable failure;
100
101
102
103
104
105
106 public DeferredContentProvider(ByteBuffer... buffers)
107 {
108 for (ByteBuffer buffer : buffers)
109 offer(buffer);
110 }
111
112 @Override
113 public void setListener(Listener listener)
114 {
115 if (!this.listener.compareAndSet(null, listener))
116 throw new IllegalStateException(String.format("The same %s instance cannot be used in multiple requests",
117 AsyncContentProvider.class.getName()));
118
119 if (isClosed())
120 {
121 synchronized (lock)
122 {
123 long total = 0;
124 for (Chunk chunk : chunks)
125 total += chunk.buffer.remaining();
126 length = total;
127 }
128 }
129 }
130
131 @Override
132 public long getLength()
133 {
134 return length;
135 }
136
137
138
139
140
141
142
143
144 public boolean offer(ByteBuffer buffer)
145 {
146 return offer(buffer, Callback.Adapter.INSTANCE);
147 }
148
149 public boolean offer(ByteBuffer buffer, Callback callback)
150 {
151 return offer(new Chunk(buffer, callback));
152 }
153
154 private boolean offer(Chunk chunk)
155 {
156 Throwable failure;
157 boolean result = false;
158 synchronized (lock)
159 {
160 failure = this.failure;
161 if (failure == null)
162 {
163 result = chunks.offer(chunk);
164 if (result && chunk != CLOSE)
165 ++size;
166 }
167 }
168 if (failure != null)
169 chunk.callback.failed(failure);
170 else if (result)
171 notifyListener();
172 return result;
173 }
174
175 private void clear()
176 {
177 synchronized (lock)
178 {
179 chunks.clear();
180 }
181 }
182
183 public void flush() throws IOException
184 {
185 synchronized (lock)
186 {
187 try
188 {
189 while (true)
190 {
191 if (failure != null)
192 throw new IOException(failure);
193 if (size == 0)
194 break;
195 lock.wait();
196 }
197 }
198 catch (InterruptedException x)
199 {
200 throw new InterruptedIOException();
201 }
202 }
203 }
204
205
206
207
208
209 public void close()
210 {
211 if (closed.compareAndSet(false, true))
212 offer(CLOSE);
213 }
214
215 public boolean isClosed()
216 {
217 return closed.get();
218 }
219
220 @Override
221 public void succeeded()
222 {
223 }
224
225 @Override
226 public void failed(Throwable failure)
227 {
228 iterator.failed(failure);
229 }
230
231 private void notifyListener()
232 {
233 Listener listener = this.listener.get();
234 if (listener != null)
235 listener.onContent();
236 }
237
238 @Override
239 public Iterator<ByteBuffer> iterator()
240 {
241 return iterator;
242 }
243
244 private class DeferredContentProviderIterator implements Iterator<ByteBuffer>, Callback
245 {
246 private Chunk current;
247
248 @Override
249 public boolean hasNext()
250 {
251 synchronized (lock)
252 {
253 return chunks.peek() != CLOSE;
254 }
255 }
256
257 @Override
258 public ByteBuffer next()
259 {
260 synchronized (lock)
261 {
262 Chunk chunk = current = chunks.poll();
263 if (chunk == CLOSE)
264 throw new NoSuchElementException();
265 return chunk == null ? null : chunk.buffer;
266 }
267 }
268
269 @Override
270 public void remove()
271 {
272 throw new UnsupportedOperationException();
273 }
274
275 @Override
276 public void succeeded()
277 {
278 Chunk chunk;
279 synchronized (lock)
280 {
281 chunk = current;
282 if (chunk != null)
283 {
284 --size;
285 lock.notify();
286 }
287 }
288 if (chunk != null)
289 chunk.callback.succeeded();
290 }
291
292 @Override
293 public void failed(Throwable x)
294 {
295 List<Chunk> chunks = new ArrayList<>();
296 synchronized (lock)
297 {
298 failure = x;
299
300 Chunk chunk = current;
301 current = null;
302 if (chunk != null)
303 chunks.add(chunk);
304 chunks.addAll(DeferredContentProvider.this.chunks);
305 clear();
306 lock.notify();
307 }
308 for (Chunk chunk : chunks)
309 chunk.callback.failed(x);
310 }
311 }
312
313 public static class Chunk
314 {
315 public final ByteBuffer buffer;
316 public final Callback callback;
317
318 public Chunk(ByteBuffer buffer, Callback callback)
319 {
320 this.buffer = Objects.requireNonNull(buffer);
321 this.callback = Objects.requireNonNull(callback);
322 }
323
324 @Override
325 public String toString()
326 {
327 return String.format("%s@%x", getClass().getSimpleName(), hashCode());
328 }
329 }
330 }