1 //
2 // ========================================================================
3 // Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
4 // ------------------------------------------------------------------------
5 // All rights reserved. This program and the accompanying materials
6 // are made available under the terms of the Eclipse Public License v1.0
7 // and Apache License v2.0 which accompanies this distribution.
8 //
9 // The Eclipse Public License is available at
10 // http://www.eclipse.org/legal/epl-v10.html
11 //
12 // The Apache License v2.0 is available at
13 // http://www.opensource.org/licenses/apache2.0.php
14 //
15 // You may elect to redistribute this code under either of these licenses.
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.InputStream;
24 import java.nio.ByteBuffer;
25 import java.util.Iterator;
26 import java.util.NoSuchElementException;
27
28 import org.eclipse.jetty.client.api.ContentProvider;
29 import org.eclipse.jetty.util.BufferUtil;
30 import org.eclipse.jetty.util.log.Log;
31 import org.eclipse.jetty.util.log.Logger;
32
33 /**
34 * A {@link ContentProvider} for an {@link InputStream}.
35 * <p />
36 * The input stream is read once and therefore fully consumed.
37 * Invocations to the {@link #iterator()} method after the first will return an "empty" iterator
38 * because the stream has been consumed on the first invocation.
39 * <p />
40 * However, it is possible for subclasses to override {@link #onRead(byte[], int, int)} to copy
41 * the content read from the stream to another location (for example a file), and be able to
42 * support multiple invocations of {@link #iterator()}, returning the iterator provided by this
43 * class on the first invocation, and an iterator on the bytes copied to the other location
44 * for subsequent invocations.
45 * <p />
46 * It is possible to specify, at the constructor, a buffer size used to read content from the
47 * stream, by default 4096 bytes.
48 * <p />
49 * The {@link InputStream} passed to the constructor is by default closed when is it fully
50 * consumed (or when an exception is thrown while reading it), unless otherwise specified
51 * to the {@link #InputStreamContentProvider(java.io.InputStream, int, boolean) constructor}.
52 */
53 public class InputStreamContentProvider implements ContentProvider
54 {
55 private static final Logger LOG = Log.getLogger(InputStreamContentProvider.class);
56
57 private final InputStream stream;
58 private final int bufferSize;
59 private final boolean autoClose;
60
61 public InputStreamContentProvider(InputStream stream)
62 {
63 this(stream, 4096);
64 }
65
66 public InputStreamContentProvider(InputStream stream, int bufferSize)
67 {
68 this(stream, bufferSize, true);
69 }
70
71 public InputStreamContentProvider(InputStream stream, int bufferSize, boolean autoClose)
72 {
73 this.stream = stream;
74 this.bufferSize = bufferSize;
75 this.autoClose = autoClose;
76 }
77
78 @Override
79 public long getLength()
80 {
81 return -1;
82 }
83
84 /**
85 * Callback method invoked just after having read from the stream,
86 * but before returning the iteration element (a {@link ByteBuffer}
87 * to the caller.
88 * <p />
89 * Subclasses may override this method to copy the content read from
90 * the stream to another location (a file, or in memory if the content
91 * is known to fit).
92 *
93 * @param buffer the byte array containing the bytes read
94 * @param offset the offset from where bytes should be read
95 * @param length the length of the bytes read
96 * @return a {@link ByteBuffer} wrapping the byte array
97 */
98 protected ByteBuffer onRead(byte[] buffer, int offset, int length)
99 {
100 if (length <= 0)
101 return BufferUtil.EMPTY_BUFFER;
102 return ByteBuffer.wrap(buffer, offset, length);
103 }
104
105 /**
106 * Callback method invoked when an exception is thrown while reading
107 * from the stream.
108 *
109 * @param failure the exception thrown while reading from the stream.
110 */
111 protected void onReadFailure(Throwable failure)
112 {
113 }
114
115 @Override
116 public Iterator<ByteBuffer> iterator()
117 {
118 return new InputStreamIterator();
119 }
120
121 /**
122 * Iterating over an {@link InputStream} is tricky, because {@link #hasNext()} must return false
123 * if the stream reads -1. However, we don't know what to return until we read the stream, which
124 * means that stream reading must be performed by {@link #hasNext()}, which introduces a side-effect
125 * on what is supposed to be a simple query method (with respect to the Query Command Separation
126 * Principle).
127 * <p />
128 * Alternatively, we could return {@code true} from {@link #hasNext()} even if we don't know that
129 * we will read -1, but then when {@link #next()} reads -1 it must return an empty buffer.
130 * However this is problematic, since GETs with no content indication would become GET with chunked
131 * content, and not understood by servers.
132 * <p />
133 * Therefore we need to make sure that {@link #hasNext()} does not perform any side effect (so that
134 * it can be called multiple times) until {@link #next()} is called.
135 */
136 private class InputStreamIterator implements Iterator<ByteBuffer>, Closeable
137 {
138 private Throwable failure;
139 private ByteBuffer buffer;
140 private Boolean hasNext;
141
142 @Override
143 public boolean hasNext()
144 {
145 try
146 {
147 if (hasNext != null)
148 return hasNext;
149
150 byte[] bytes = new byte[bufferSize];
151 int read = stream.read(bytes);
152 LOG.debug("Read {} bytes from {}", read, stream);
153 if (read > 0)
154 {
155 hasNext = Boolean.TRUE;
156 buffer = onRead(bytes, 0, read);
157 return true;
158 }
159 else if (read < 0)
160 {
161 hasNext = Boolean.FALSE;
162 buffer = null;
163 close();
164 return false;
165 }
166 else
167 {
168 hasNext = Boolean.TRUE;
169 buffer = BufferUtil.EMPTY_BUFFER;
170 return true;
171 }
172 }
173 catch (Throwable x)
174 {
175 LOG.debug(x);
176 if (failure == null)
177 {
178 failure = x;
179 onReadFailure(x);
180 // Signal we have more content to cause a call to
181 // next() which will throw NoSuchElementException.
182 hasNext = Boolean.TRUE;
183 buffer = null;
184 close();
185 return true;
186 }
187 throw new IllegalStateException();
188 }
189 }
190
191 @Override
192 public ByteBuffer next()
193 {
194 if (failure != null)
195 {
196 // Consume the failure so that calls to hasNext() will return false.
197 hasNext = Boolean.FALSE;
198 buffer = null;
199 throw (NoSuchElementException)new NoSuchElementException().initCause(failure);
200 }
201 if (!hasNext())
202 throw new NoSuchElementException();
203
204 ByteBuffer result = buffer;
205 if (result == null)
206 {
207 hasNext = Boolean.FALSE;
208 buffer = null;
209 throw new NoSuchElementException();
210 }
211 else
212 {
213 hasNext = null;
214 buffer = null;
215 return result;
216 }
217 }
218
219 @Override
220 public void remove()
221 {
222 throw new UnsupportedOperationException();
223 }
224
225 @Override
226 public void close()
227 {
228 if (autoClose)
229 {
230 try
231 {
232 stream.close();
233 }
234 catch (IOException x)
235 {
236 LOG.ignore(x);
237 }
238 }
239 }
240 }
241 }