| // |
| // ======================================================================== |
| // Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd. |
| // ------------------------------------------------------------------------ |
| // All rights reserved. This program and the accompanying materials |
| // are made available under the terms of the Eclipse Public License v1.0 |
| // and Apache License v2.0 which accompanies this distribution. |
| // |
| // The Eclipse Public License is available at |
| // http://www.eclipse.org/legal/epl-v10.html |
| // |
| // The Apache License v2.0 is available at |
| // http://www.opensource.org/licenses/apache2.0.php |
| // |
| // You may elect to redistribute this code under either of these licenses. |
| // ======================================================================== |
| // |
| |
| package org.eclipse.jetty.client; |
| |
| import java.io.Closeable; |
| import java.nio.ByteBuffer; |
| import java.util.Collections; |
| import java.util.Iterator; |
| |
| import org.eclipse.jetty.client.api.ContentProvider; |
| import org.eclipse.jetty.util.BufferUtil; |
| import org.eclipse.jetty.util.Callback; |
| import org.eclipse.jetty.util.log.Log; |
| import org.eclipse.jetty.util.log.Logger; |
| |
| /** |
| * {@link HttpContent} is a stateful, linear representation of the request content provided |
| * by a {@link ContentProvider} that can be traversed one-way to obtain content buffers to |
| * send to a HTTP server. |
| * <p> |
| * {@link HttpContent} offers the notion of a one-way cursor to traverse the content. |
| * The cursor starts in a virtual "before" position and can be advanced using {@link #advance()} |
| * until it reaches a virtual "after" position where the content is fully consumed. |
| * <pre> |
| * +---+ +---+ +---+ +---+ +---+ |
| * | | | | | | | | | | |
| * +---+ +---+ +---+ +---+ +---+ |
| * ^ ^ ^ ^ |
| * | | --> advance() | | |
| * | | last | |
| * | | | |
| * before | after |
| * | |
| * current |
| * </pre> |
| * At each valid (non-before and non-after) cursor position, {@link HttpContent} provides the following state: |
| * <ul> |
| * <li>the buffer containing the content to send, via {@link #getByteBuffer()}</li> |
| * <li>a copy of the content buffer that can be used for notifications, via {@link #getContent()}</li> |
| * <li>whether the buffer to write is the last one, via {@link #isLast()}</li> |
| * </ul> |
| * {@link HttpContent} may not have content, if the related {@link ContentProvider} is {@code null}, and this |
| * is reflected by {@link #hasContent()}. |
| * <p> |
| * {@link HttpContent} may have {@link AsyncContentProvider deferred content}, in which case {@link #advance()} |
| * moves the cursor to a position that provides {@code null} {@link #getByteBuffer() buffer} and |
| * {@link #getContent() content}. When the deferred content is available, a further call to {@link #advance()} |
| * will move the cursor to a position that provides non {@code null} buffer and content. |
| */ |
| public class HttpContent implements Callback, Closeable |
| { |
| private static final Logger LOG = Log.getLogger(HttpContent.class); |
| private static final ByteBuffer AFTER = ByteBuffer.allocate(0); |
| |
| private final ContentProvider provider; |
| private final Iterator<ByteBuffer> iterator; |
| private ByteBuffer buffer; |
| private volatile ByteBuffer content; |
| |
| public HttpContent(ContentProvider provider) |
| { |
| this.provider = provider; |
| this.iterator = provider == null ? Collections.<ByteBuffer>emptyIterator() : provider.iterator(); |
| } |
| |
| /** |
| * @return whether there is any content at all |
| */ |
| public boolean hasContent() |
| { |
| return provider != null; |
| } |
| |
| /** |
| * @return whether the cursor points to the last content |
| */ |
| public boolean isLast() |
| { |
| return !iterator.hasNext(); |
| } |
| |
| /** |
| * @return the {@link ByteBuffer} containing the content at the cursor's position |
| */ |
| public ByteBuffer getByteBuffer() |
| { |
| return buffer; |
| } |
| |
| /** |
| * @return a {@link ByteBuffer#slice()} of {@link #getByteBuffer()} at the cursor's position |
| */ |
| public ByteBuffer getContent() |
| { |
| return content; |
| } |
| |
| /** |
| * Advances the cursor to the next block of content. |
| * <p> |
| * The next block of content may be valid (which yields a non-null buffer |
| * returned by {@link #getByteBuffer()}), but may also be deferred |
| * (which yields a null buffer returned by {@link #getByteBuffer()}). |
| * <p> |
| * If the block of content pointed by the new cursor position is valid, this method returns true. |
| * |
| * @return true if there is content at the new cursor's position, false otherwise. |
| */ |
| public boolean advance() |
| { |
| boolean advanced; |
| boolean hasNext; |
| ByteBuffer bytes; |
| if (iterator instanceof Synchronizable) |
| { |
| synchronized (((Synchronizable)iterator).getLock()) |
| { |
| advanced = iterator.hasNext(); |
| bytes = advanced ? iterator.next() : null; |
| hasNext = advanced && iterator.hasNext(); |
| } |
| } |
| else |
| { |
| advanced = iterator.hasNext(); |
| bytes = advanced ? iterator.next() : null; |
| hasNext = advanced && iterator.hasNext(); |
| } |
| |
| if (advanced) |
| { |
| buffer = bytes; |
| content = bytes == null ? null : bytes.slice(); |
| if (LOG.isDebugEnabled()) |
| LOG.debug("Advanced content to {} chunk {}", hasNext ? "next" : "last", bytes); |
| return bytes != null; |
| } |
| else |
| { |
| if (content != AFTER) |
| { |
| content = buffer = AFTER; |
| if (LOG.isDebugEnabled()) |
| LOG.debug("Advanced content past last chunk"); |
| } |
| return false; |
| } |
| } |
| |
| /** |
| * @return whether the cursor has been advanced past the {@link #isLast() last} position. |
| */ |
| public boolean isConsumed() |
| { |
| return content == AFTER; |
| } |
| |
| @Override |
| public void succeeded() |
| { |
| if (isConsumed()) |
| return; |
| if (iterator instanceof Callback) |
| ((Callback)iterator).succeeded(); |
| } |
| |
| @Override |
| public void failed(Throwable x) |
| { |
| if (isConsumed()) |
| return; |
| if (iterator instanceof Callback) |
| ((Callback)iterator).failed(x); |
| } |
| |
| @Override |
| public void close() |
| { |
| try |
| { |
| if (iterator instanceof Closeable) |
| ((Closeable)iterator).close(); |
| } |
| catch (Throwable x) |
| { |
| LOG.ignore(x); |
| } |
| } |
| |
| @Override |
| public String toString() |
| { |
| return String.format("%s@%x - has=%b,last=%b,consumed=%b,buffer=%s", |
| getClass().getSimpleName(), |
| hashCode(), |
| hasContent(), |
| isLast(), |
| isConsumed(), |
| BufferUtil.toDetailString(getContent())); |
| } |
| } |