View Javadoc

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   *   |           | --&gt; 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      private static final ByteBuffer CLOSE = ByteBuffer.allocate(0);
71  
72      private final ContentProvider provider;
73      private final Iterator<ByteBuffer> iterator;
74      private ByteBuffer buffer;
75      private ByteBuffer content;
76      private boolean last;
77  
78      public HttpContent(ContentProvider provider)
79      {
80          this.provider = provider;
81          this.iterator = provider == null ? Collections.<ByteBuffer>emptyIterator() : provider.iterator();
82      }
83  
84      /**
85       * @return whether there is any content at all
86       */
87      public boolean hasContent()
88      {
89          return provider != null;
90      }
91  
92      /**
93       * @return whether the cursor points to the last content
94       */
95      public boolean isLast()
96      {
97          return last;
98      }
99  
100     /**
101      * @return the {@link ByteBuffer} containing the content at the cursor's position
102      */
103     public ByteBuffer getByteBuffer()
104     {
105         return buffer;
106     }
107 
108     /**
109      * @return a {@link ByteBuffer#slice()} of {@link #getByteBuffer()} at the cursor's position
110      */
111     public ByteBuffer getContent()
112     {
113         return content;
114     }
115 
116     /**
117      * Advances the cursor to the next block of content.
118      * <p>
119      * The next block of content may be valid (which yields a non-null buffer
120      * returned by {@link #getByteBuffer()}), but may also be deferred
121      * (which yields a null buffer returned by {@link #getByteBuffer()}).
122      * <p>
123      * If the block of content pointed by the new cursor position is valid, this method returns true.
124      *
125      * @return true if there is content at the new cursor's position, false otherwise.
126      */
127     public boolean advance()
128     {
129         if (iterator instanceof Synchronizable)
130         {
131             synchronized (((Synchronizable)iterator).getLock())
132             {
133                 return advance(iterator);
134             }
135         }
136         else
137         {
138             return advance(iterator);
139         }
140     }
141 
142     private boolean advance(Iterator<ByteBuffer> iterator)
143     {
144         boolean hasNext = iterator.hasNext();
145         ByteBuffer bytes = hasNext ? iterator.next() : null;
146         boolean hasMore = hasNext && iterator.hasNext();
147         boolean wasLast = last;
148         last = !hasMore;
149 
150         if (hasNext)
151         {
152             buffer = bytes;
153             content = bytes == null ? null : bytes.slice();
154             if (LOG.isDebugEnabled())
155                 LOG.debug("Advanced content to {} chunk {}", hasMore ? "next" : "last", String.valueOf(bytes));
156             return bytes != null;
157         }
158         else
159         {
160             // No more content, but distinguish between last and consumed.
161             if (wasLast)
162             {
163                 buffer = content = AFTER;
164                 if (LOG.isDebugEnabled())
165                     LOG.debug("Advanced content past last chunk");
166             }
167             else
168             {
169                 buffer = content = CLOSE;
170                 if (LOG.isDebugEnabled())
171                     LOG.debug("Advanced content to last chunk");
172             }
173             return false;
174         }
175     }
176 
177     /**
178      * @return whether the cursor has been advanced past the {@link #isLast() last} position.
179      */
180     public boolean isConsumed()
181     {
182         return buffer == AFTER;
183     }
184 
185     @Override
186     public void succeeded()
187     {
188         if (isConsumed())
189             return;
190         if (buffer == CLOSE)
191             return;
192         if (iterator instanceof Callback)
193             ((Callback)iterator).succeeded();
194     }
195 
196     @Override
197     public void failed(Throwable x)
198     {
199         if (isConsumed())
200             return;
201         if (buffer == CLOSE)
202             return;
203         if (iterator instanceof Callback)
204             ((Callback)iterator).failed(x);
205     }
206 
207     @Override
208     public void close()
209     {
210         try
211         {
212             if (iterator instanceof Closeable)
213                 ((Closeable)iterator).close();
214         }
215         catch (Throwable x)
216         {
217             LOG.ignore(x);
218         }
219     }
220 
221     @Override
222     public String toString()
223     {
224         return String.format("%s@%x - has=%b,last=%b,consumed=%b,buffer=%s",
225                 getClass().getSimpleName(),
226                 hashCode(),
227                 hasContent(),
228                 isLast(),
229                 isConsumed(),
230                 BufferUtil.toDetailString(getContent()));
231     }
232 }