1 //
2 // ========================================================================
3 // Copyright (c) 1995-2015 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;
20
21 import java.io.Closeable;
22 import java.nio.ByteBuffer;
23 import java.util.Collections;
24 import java.util.Iterator;
25
26 import org.eclipse.jetty.client.api.ContentProvider;
27 import org.eclipse.jetty.util.BufferUtil;
28 import org.eclipse.jetty.util.Callback;
29 import org.eclipse.jetty.util.log.Log;
30 import org.eclipse.jetty.util.log.Logger;
31
32 /**
33 * {@link HttpContent} is a stateful, linear representation of the request content provided
34 * by a {@link ContentProvider} that can be traversed one-way to obtain content buffers to
35 * send to a HTTP server.
36 * <p />
37 * {@link HttpContent} offers the notion of a one-way cursor to traverse the content.
38 * The cursor starts in a virtual "before" position and can be advanced using {@link #advance()}
39 * until it reaches a virtual "after" position where the content is fully consumed.
40 * <pre>
41 * +---+ +---+ +---+ +---+ +---+
42 * | | | | | | | | | |
43 * +---+ +---+ +---+ +---+ +---+
44 * ^ ^ ^ ^
45 * | | --> advance() | |
46 * | | last |
47 * | | |
48 * before | after
49 * |
50 * current
51 * </pre>
52 * At each valid (non-before and non-after) cursor position, {@link HttpContent} provides the following state:
53 * <ul>
54 * <li>the buffer containing the content to send, via {@link #getByteBuffer()}</li>
55 * <li>a copy of the content buffer that can be used for notifications, via {@link #getContent()}</li>
56 * <li>whether the buffer to write is the last one, via {@link #isLast()}</li>
57 * </ul>
58 * {@link HttpContent} may not have content, if the related {@link ContentProvider} is {@code null}, and this
59 * is reflected by {@link #hasContent()}.
60 * <p />
61 * {@link HttpContent} may have {@link AsyncContentProvider deferred content}, in which case {@link #advance()}
62 * moves the cursor to a position that provides {@code null} {@link #getByteBuffer() buffer} and
63 * {@link #getContent() content}. When the deferred content is available, a further call to {@link #advance()}
64 * will move the cursor to a position that provides non {@code null} buffer and content.
65 */
66 public class HttpContent implements Callback, Closeable
67 {
68 private static final Logger LOG = Log.getLogger(HttpContent.class);
69 private static final ByteBuffer AFTER = ByteBuffer.allocate(0);
70
71 private final ContentProvider provider;
72 private final Iterator<ByteBuffer> iterator;
73 private ByteBuffer buffer;
74 private volatile ByteBuffer content;
75
76 public HttpContent(ContentProvider provider)
77 {
78 this.provider = provider;
79 this.iterator = provider == null ? Collections.<ByteBuffer>emptyIterator() : provider.iterator();
80 }
81
82 /**
83 * @return whether there is any content at all
84 */
85 public boolean hasContent()
86 {
87 return provider != null;
88 }
89
90 /**
91 * @return whether the cursor points to the last content
92 */
93 public boolean isLast()
94 {
95 return !iterator.hasNext();
96 }
97
98 /**
99 * @return the {@link ByteBuffer} containing the content at the cursor's position
100 */
101 public ByteBuffer getByteBuffer()
102 {
103 return buffer;
104 }
105
106 /**
107 * @return a {@link ByteBuffer#slice()} of {@link #getByteBuffer()} at the cursor's position
108 */
109 public ByteBuffer getContent()
110 {
111 return content;
112 }
113
114 /**
115 * Advances the cursor to the next block of content.
116 * <p />
117 * The next block of content may be valid (which yields a non-null buffer
118 * returned by {@link #getByteBuffer()}), but may also be deferred
119 * (which yields a null buffer returned by {@link #getByteBuffer()}).
120 * <p />
121 * If the block of content pointed by the new cursor position is valid, this method returns true.
122 *
123 * @return true if there is content at the new cursor's position, false otherwise.
124 */
125 public boolean advance()
126 {
127 boolean advanced;
128 boolean hasNext;
129 ByteBuffer bytes;
130 if (iterator instanceof Synchronizable)
131 {
132 synchronized (((Synchronizable)iterator).getLock())
133 {
134 advanced = iterator.hasNext();
135 bytes = advanced ? iterator.next() : null;
136 hasNext = advanced && iterator.hasNext();
137 }
138 }
139 else
140 {
141 advanced = iterator.hasNext();
142 bytes = advanced ? iterator.next() : null;
143 hasNext = advanced && iterator.hasNext();
144 }
145
146 if (advanced)
147 {
148 buffer = bytes;
149 content = bytes == null ? null : bytes.slice();
150 if (LOG.isDebugEnabled())
151 LOG.debug("Advanced content to {} chunk {}", hasNext ? "next" : "last", bytes);
152 return bytes != null;
153 }
154 else
155 {
156 if (content != AFTER)
157 {
158 content = buffer = AFTER;
159 if (LOG.isDebugEnabled())
160 LOG.debug("Advanced content past last chunk");
161 }
162 return false;
163 }
164 }
165
166 /**
167 * @return whether the cursor has been advanced past the {@link #isLast() last} position.
168 */
169 public boolean isConsumed()
170 {
171 return content == AFTER;
172 }
173
174 @Override
175 public void succeeded()
176 {
177 if (isConsumed())
178 return;
179 if (iterator instanceof Callback)
180 ((Callback)iterator).succeeded();
181 }
182
183 @Override
184 public void failed(Throwable x)
185 {
186 if (isConsumed())
187 return;
188 if (iterator instanceof Callback)
189 ((Callback)iterator).failed(x);
190 }
191
192 @Override
193 public void close()
194 {
195 try
196 {
197 if (iterator instanceof Closeable)
198 ((Closeable)iterator).close();
199 }
200 catch (Exception x)
201 {
202 LOG.ignore(x);
203 }
204 }
205
206 @Override
207 public String toString()
208 {
209 return String.format("%s@%x - has=%b,last=%b,consumed=%b,buffer=%s",
210 getClass().getSimpleName(),
211 hashCode(),
212 hasContent(),
213 isLast(),
214 isConsumed(),
215 BufferUtil.toDetailString(getContent()));
216 }
217 }