View Javadoc

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